1 Star 0 Fork 6

Lotcsy/ETF轮动策略-基于Joinquant平台编写的回测

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
ETF轮动策略code.txt 11.99 KB
一键复制 编辑 原始数据 按行查看 历史
Mathmax 提交于 2021-11-15 08:39 . 策略代码,用Joinquant运行
# 克隆自聚宽文章:https://www.joinquant.com/post/24321
# 标题:无杠杆,稳定盈利的etf轮动,06年开始3000%收益
# 作者:热爱大自然
# 克隆自聚宽文章:https://www.joinquant.com/post/22857
# 标题:无杠杆,回撤更小的etf轮动
# 作者:智习
from jqdata import *
from pandas.core.frame import DataFrame
import talib
'''
原理:在8个种类的ETF中,持仓三个,ETF池相应的指数分别是
'159915.XSHE' #创业板、
'159949.XSHE' #创业板50
'510300.XSHG' #沪深300
'510500.XSHG' #中证500
'510880.XSHG' #红利ETF
'159905.XSHE' #深红利
'510180.XSHG' #上证180
'510050.XSHG' #上证50
持仓原则:
1、对泸深指数的成交量进行统计,如果连续6(lag)天成交量小于7(lag0)天成交量的,空仓处理(购买货币基金511880 银华日利或国债 511010 )
2、13个交易日内(lag1)涨幅大于1的,并且“均线差值”大于0的才进行考虑。
3、对符合考虑条件的ETF的涨幅进行排序,买涨幅最高的三个。
'''
def initialize(context):
set_params()
set_variables()
set_backtest()
run_daily(ETFtrade1, time='11:30')
if g.signal == 'sell_the_stocks':
run_daily(ETFtrade2, time='13:00')
else:
run_daily(ETFtrade2, time='14:40')
#1 设置参数
def set_params():
# 设置基准收益
set_benchmark('000300.XSHG')
g.use_dynamic_target_market = False #是否动态改变大盘热度参考指标
#g.target_market = '000300.XSHG'
g.target_market = '000001.XSHG'
g.empty_keep_stock = '511880.XSHG'#闲时买入的标的
#g.empty_keep_stock = '601318.XSHG'#闲时买入的标的
g.signal = 'KEEP' #交易信号初始化
g.emotion_rate = 0 #市场热度
g.emotion_p = 0 #大盘成交量均线突破天数
g.emotion_n = 0 #大盘成交量均线跌破天数
g.lag = 8 #大盘成交量连续跌破均线的天数,发出空仓信号
g.lag0 = 8 #大盘成交量监控周期
g.lag1 = 13 #比价均线周期
g.lag2 = 13 #价格涨幅计算周期
g.last = [] #持仓股票代码初始化
g.buy = [] #购买股票列表
g.clear = []
g.three = []
g.df = pd.DataFrame()
g.ETFList = {
#'399001.XSHE':'150019.XSHE',#银华锐进
#'399395.XSHE': '150197.XSHE',#有色B
#'399905.XSHE':'159902.XSHE',#中小板指
#'399975.XSHE':'150201.XSHE',#券商B
#'399975.XSHE':'512880.XSHG',#证券ETF
#'399632.XSHE':'159901.XSHE',#深100etf
#'162605.XSHE':'162605.XSHE',#景顺鼎益
'000016.XSHG':'510050.XSHG',#上证50
#'000010.XSHG':'510180.XSHG',#上证180
'000015.XSHG':'510880.XSHG',#红利ETF
#'399324.XSHE':'159905.XSHE',#深红利
'399006.XSHE':'159915.XSHE',#创业板
#'399006.XSHE':'150153.XSHE',#创业板B
'000300.XSHG':'510300.XSHG',#沪深300
'000905.XSHG':'510500.XSHG',#中证500
'513100.XSHG':'513100.XSHG',#纳斯达克指数
#'399673.XSHE':'159949.XSHE'#创业板50
#'512660.XSHG':'512660.XSHG',#军工
#'518880.XSHG':'518880.XSHG',#gold
#'512800.XSHG':'512800.XSHG'#银行
#'511880.XSHG':'511880.XSHG'
}
g.IdxList=dict(zip(g.ETFList.values(),g.ETFList.keys()))
stocks_info = "\n股票池:\n"
for security in g.ETFList.values():
s_info = get_security_info(security)
stocks_info+="【%s】%s 上市间间:%s\n"%(s_info.code,s_info.display_name,s_info.start_date)
log.info(stocks_info)
#设置中间变量
def set_variables():
return
#设置回测条件
def set_backtest():
set_option("avoid_future_data", True)
set_option('use_real_price', True) #用真实价格交易
log.set_level('order', 'error')
'''
=================================================
每天开盘前
=================================================
'''
#每天开盘前要做的事情
def before_trading_start(context):
set_slip_fee(context)
# 根据不同的时间段设置滑点与手续费
def set_slip_fee(context):
# 将滑点设置为0
set_slippage(FixedSlippage(0))
# 设置手续费
set_commission(PerTrade(buy_cost=0.0005, sell_cost=0.0005, min_cost=5))
'''
=================================================
每日交易时
=================================================
'''
def ETFtrade1(context):
g.three=[]
g.signal = get_signal(context)
def ETFtrade2(context):
if g.signal == 'sell_the_stocks':
for stock in context.portfolio.positions.keys():
if (stock == g.empty_keep_stock):
continue
log.info("正在卖出 %s" % stock)
order_target_value(stock, 0)
elif g.signal == 'KEEP':
log.info("交易信号:持仓不变")
elif g.signal == 'BUY':
if g.empty_keep_stock in context.portfolio.positions.keys():
order_target_value(g.empty_keep_stock, 0)
g.last = list(context.portfolio.positions.keys())
g.buy.sort()
g.last.sort()
ratio = len(g.buy)
cash = context.portfolio.total_value/ratio
for code in g.last:#先进行清仓处理,如果持仓股票不在购买清单中
if code not in g.buy:
log.info("正在清空 %s" % code)
order_target_value(code,0)
g.clear.append(code)
for code in g.clear:#从g.last删除已经卖出的标的
g.last.remove(code)
g.clear = [] #清除临时列表
for code in g.last:#如果持仓在购买清单中判断调仓
if code in g.buy:
positions_dict = context.portfolio.positions
for position in list(positions_dict.values()):
if position.value/cash > 1.5:
log.info("正在调仓 %s" % position.security)
order_target_value(position.security,cash)
for code in g.buy:
if code not in g.last:
log.info("正在买入 %s" % code)
order_value(code,cash)
g.last.append(code)
g.buy = []
current_returns = 100*context.portfolio.returns
log.info("当前收益:%.2f%%,当前持仓%s",current_returns,g.last)
if len(context.portfolio.positions)==0:
order_target_value(g.empty_keep_stock, context.portfolio.available_cash)
#获取信号
def get_signal(context):
i=0 # 计数器初始化
# 创建保持计算结果的DataFrame
g.df = pd.DataFrame()
for k in g.ETFList.values():
security = k# key()以指数计算交易信号,values()以基金计算交易信号
# 获取股票的收盘价
close_data = attribute_history(security, g.lag1, '1d', ['close'],df=False)
# 获取股票现价
current_data = get_current_data()
current_price = current_data[security].last_price
# 获取股票的阶段收盘价涨幅
cp_increase = (current_price/close_data['close'][g.lag2-g.lag1]-1)*100
std = normal_Vol(security,50)
sharp = (cp_increase/100)/std
# 取得平均价格
ma_n1 = close_data['close'].mean()
# 计算前一收盘价与均值差值
pre_price = (current_price/ma_n1-1)*100
#close_sec1w = get_bars(security, count=9, unit='1w', include_now=True, fields=['close'])['close']
#close_sec1w = get_bars(security, count=21, unit='1d', include_now=False, fields=['close'])['close']
#if len(close_sec1w) >= 9:
# 周涨幅:1周,2周,3周,4周,8周
#wzf1 = close_sec1w[-1] / close_sec1w[-5] - 1
#wzf2 = close_sec1w[-1] / close_sec1w[-10] - 1
#wzf3 = close_sec1w[-1] / close_sec1w[-15] - 1
#wzf4 = close_sec1w[-1] / close_sec1w[-20] - 1
#wzf5 = close_sec1w[-1] / close_sec1w[-9] - 1
# 计算评分
#cp = wzf1 * 0.4 + wzf2 * 0.2 + wzf3 * 0.15 + wzf4 * 0.2 + wzf5 * 0.05
#cp = wzf1 * 0.1 + wzf2 * 0.2 + wzf3 * 0.4 + wzf4 * 0.3 + wzf5 * 0
g.df.loc[i,'股票代码'] = security # 把标的股票代码添加到DataFrame
g.df.loc[i,'股票名称'] = get_security_info(security).display_name # 把标的股票名称添加到DataFrame
g.df.loc[i,'周期涨幅%'] = cp_increase # 把计算结果添加到DataFrame
g.df.loc[i,'均线差值%'] = pre_price # 把计算结果添加到DataFrame
g.df.loc[i,'波动率%'] = std*100
g.df.loc[i,'夏普'] = sharp
i=i+1
# 对计算结果表格进行从大到小排序
g.df = g.df.fillna(-100)
g.df.sort_values(by='夏普',ascending=False,inplace=True) # 按照涨幅排序 by 周期涨幅%
g.df.reset_index(drop=True, inplace=True) # 重新设置索引
if g.use_dynamic_target_market:
g.target_market = g.IdxList[str(g.df.iloc[-1,0])] #不用最高的来处理
if EmotionMonitor(context) == -1: # 大盘情绪不好发出空仓信号
log.info("交易信号:大盘成交量持续低均线,空仓")
g.last = []
g.buy = []
return 'sell_the_stocks'
log.info("\n行情统计,%s 热度%f%%:\n%s" % (g.target_market,g.emotion_rate,g.df))
#send_message("\n行情统计结果表:\n%s" % (g.df))
g.three.append(str(g.df.iloc[0,0]))
g.three.append(str(g.df.iloc[1,0]))
g.three.append(str(g.df.iloc[2,0]))
#删除不符合要求的标的
if len(g.last)>0:
if (g.last[0] in g.three):
return 'KEEP'
for t in g.df.index:
if g.df.loc[t,'周期涨幅%'] < 0 or g.df.loc[t,'均线差值%'] < 0:
g.df=g.df.drop(t)
if g.df.empty: # 均不符合买入条件 与删除条件换了个顺序
#if g.df.iloc[0,2] < 0 and g.df.iloc[0,3] < 0: # 更换为and
if len(g.last) == 0:# 持仓为空
log.info("交易信号:继续保持空仓状态")
return 'KEEP'# 持仓保持不变
else:# 持仓不为空
log.info("交易信号:空仓")
g.last = []
g.buy = []
return 'sell_the_stocks'
# 表不为空,持有排名前三名的股
if g.df.shape[0] > 10 :
g.buy.append(str(g.df.iloc[0,0]))
g.buy.append(str(g.df.iloc[1,0]))
g.buy.append(str(g.df.iloc[2,0]))
log.info("交易信号:持有 %s" % (g.buy))
return 'BUY'
# 表不为空,持有排名前两名的股
if g.df.shape[0] > 10 :# 表不为空,持有排名前两名的股
g.buy.append(str(g.df.iloc[0,0]))
g.buy.append(str(g.df.iloc[1,0]))
log.info("交易信号:持有 %s" % (g.buy))
return 'BUY'
else: # 持第一名的股
g.buy.append(str(g.df.iloc[0,0]))
log.info("交易信号:持有 %s" % (g.buy))
return 'BUY'
def normal_Vol(security,k):
data = attribute_history(security, k, '1d', ['close'], df=True)
rets=np.diff(np.log(data),axis=0)
std=rets.std()*sqrt(250)
return std
def chenk_stocks(context):
jz_pj = {}
for sec in g.stocks:
close_sec1w = get_bars(sec, count=9, unit='1w', include_now=True, fields=['close'])['close']
if len(close_sec1w) >= 9:
# 周涨幅:1周,2周,3周,4周,8周
wzf1 = close_sec1w[-1] / close_sec1w[-2] - 1
wzf2 = close_sec1w[-1] / close_sec1w[-3] - 1
wzf3 = close_sec1w[-1] / close_sec1w[-4] - 1
wzf4 = close_sec1w[-1] / close_sec1w[-5] - 1
wzf5 = close_sec1w[-1] / close_sec1w[-9] - 1
# 计算评分
cp = wzf1 * 0.25 + wzf2 * 0.25 + wzf3 * 0.25 + wzf4 * 0.25 + wzf5 * 0.05
jz_pj[sec] = cp
# 按评分从高到低排序,选择前g.num_to_buy名
# g.codes 要买入的etf
g.codes = pd.Series(jz_pj).sort_values(ascending=False).index[0]
# 大盘行情监控函数
def EmotionMonitor(context):
return 1
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/lotcsy/ETF-strategy.git
git@gitee.com:lotcsy/ETF-strategy.git
lotcsy
ETF-strategy
ETF轮动策略-基于Joinquant平台编写的回测
master

搜索帮助

0d507c66 1850385 C8b1a773 1850385