QMT自带了一些优秀策略,可以给新接触QMT的朋友练手。但是,面对百行左右的代码量,新手看了容易知难而退。其实,将策略的基本逻辑弄明白,再一句一句翻译代码,还是挺容易看懂的。
今天,我们分析一下QMT自带的《多因子选股回测示例》,逻辑在原策略中已经说明了,绿色字体的就是:

原策略中的代码注释有限,本文几乎将原策略的每一行上面都做了注释,可以降低读者阅读代码的难度。
因为原策略形成的时间较早,所以有些函数现在几乎不用了,如set_universe和get_history_data。作为一篇分析代码的文章,本文对原策略不做改动,方便读者跟原策略对照。
由于时间仓促,可能对代码的解析过程中会有疏漏和谬误,如有读者发现,请留言指出,不吝赐教!
#coding:gbk"""回测模型示例(非实盘交易策略)#HS300日线下运行,20个交易日进行 一次调仓,每次买入在买入备选中因子评分前10的股票,每支股票各分配当前可用资金的10%(权重可调整)#扩展数据需要在补完HS300成分股数据之后生成,本模型中扩展数据暂时使用VBA指标ATR和ADTM生成,命名为atr和adtm"""#导入科学计算库import pandas as pdimport numpy as np#导入时间和日期库import timeimport datetime#系统初始化函数,只在策略启动时运行一次def init(ContextInfo): #获取沪深300成分股 ContextInfo.s = ContextInfo.get_sector('000300.SH') #将上一行获取的沪深300成分股作为股票池 ContextInfo.set_universe(ContextInfo.s) #天数记数器 ContextInfo.day = 0 #新建一个字典,键为股票代码 ContextInfo.holdings = {i:0 for i in ContextInfo.s} #各支股票权重均为10% ContextInfo.weight = [0.1]*10 #设置资金分配权重 #建立一个空字典,后面将用来记录股票成交价 ContextInfo.buypoint = {} #设置ContextInfo.money变量,来获取账户现金额 ContextInfo.money = ContextInfo.capital #初始化ContextInfo.profit变量,用来记录利润值 ContextInfo.profit = 0 #设置模拟账号 ContextInfo.accountID='testS'#系统函数,每根K线运行一次def handlebar(ContextInfo): #字典,用来记录股票的第一项打分情况 rank1 = {} #字典,用来记录股票的第二项打分情况 rank2 = {} #字典,用来记录股票的总分情况 rank_total = {} #临时股票字典 tmp_stock = {} #记录K线的编号,从0开始 d = ContextInfo.barpos #获取前一个交易日的开盘价(等比向前复权) price = ContextInfo.get_history_data(1,'1d','open',3) #每月一调仓 if d > 60 and d % 20 == 0: #获取当前K线的日期 nowDate = timetag_to_datetime(ContextInfo.get_bar_timetag(d),'%Y%m%d') #打印当前K线的日期 print(nowDate) #从自定义的signal函数中获取要买入和卖出的股票及持仓量的字典 buys, sells = signal(ContextInfo) # order = {} #遍历带买入记号的股票列表 for k in list(buys.keys()): #如果买入记号为1 if buys[k] == 1: #记录股票的atr值,作为第一项的打分 rank1[k] = ext_data_rank('atr',k[-2:]+k[0:6],0,ContextInfo) #记录股票的adtm值,作为第二项的打分 rank2[k] = ext_data_rank('adtm',k[-2:]+k[0:6],0,ContextInfo) #股票的总分为第一项的打分 rank_total[k] = 1.0 * rank1[k] #因子的权重需要人为设置,此处取了0.5和-0.5 #打印投票第一项的打分 print (1111111, rank1[k]) #将股票按总分升序排列 tmp = sorted(list(rank_total.items()), key = lambda item:item[1]) #如果股票总数大于等于10 if len(tmp) >= 10: #选择10支股票 tmp_stock = {i[0] for i in tmp[:10]} #如果股票总数小于10 else: #则选择所有的股票 tmp_stock = {i[0] for i in tmp} #买入备选中若超过10只股票则选10支,不足10支则全选 #遍历带买入股票记号字典的键(股票代码) for k in list(buys.keys()): #如果股票代码未在分数合格的股票之中 if k not in tmp_stock: #标记这支股票的买入记号为0 buys[k] = 0 #如果打分的股票数量大于0 if tmp_stock: #打印:股票池:打分的股票 print('stock pool:',tmp_stock) #遍历沪深300成分股 for k in ContextInfo.s: #如果股票有持仓,并且带有卖出标记 if ContextInfo.holdings[k] > 0 and sells[k] == 1: #打印:准备卖出 print('ready to sell') #卖出股票 order_shares(k,-ContextInfo.holdings[k]*100,'fix',price[k][-1],ContextInfo,ContextInfo.accountID) #更新账户资产额 ContextInfo.money += price[k][-1] * ContextInfo.holdings[k] * 100 - 0.0003*ContextInfo.holdings[k]*100*price[k][-1] #手续费按万三设定 #更新账户利润额 ContextInfo.profit += (price[k][-1]-ContextInfo.buypoint[k]) * ContextInfo.holdings[k] * 100 - 0.0003*ContextInfo.holdings[k]*100*price[k][-1] #打印售出的股票代码 print(k) #将股票代码字典相应的股票的数量更新为0 ContextInfo.holdings[k] = 0 #新建变量,保存给要买入的各支股票分配的资金 ContextInfo.money_distribution = {k:i*ContextInfo.money for (k,i) in zip(tmp_stock,ContextInfo.weight)} #遍历准备买入的股票字典 for k in tmp_stock: #如果股票不在持仓中,并且带有买入记号 if ContextInfo.holdings[k] == 0 and buys[k] == 1: #打印:准备买入 print('ready to buy') #计算出要买入的股票的手数 order[k] = int(ContextInfo.money_distribution[k]/(price[k][-1]))/100 #下限价单 order_shares(k,order[k]*100,'fix',price[k][-1],ContextInfo,ContextInfo.accountID) #记录下单的价格 ContextInfo.buypoint[k] = price[k][-1] #更新账户现金:原账户现金-下单用去的现金 ContextInfo.money -= price[k][-1] * order[k] * 100 - 0.0003*order[k]*100*price[k][-1] #更新账户利润:原账户利润-交易手续费 ContextInfo.profit -= 0.0003*order[k]*100*price[k][-1] #打印买入的股票代码 print(k) #将买入的股票数量更新到股票代码字典中 ContextInfo.holdings[k] = order[k] #打印账户现金、账户利润、账户现金初始金额 print(ContextInfo.money,ContextInfo.profit,ContextInfo.capital) #收益率 = 利润/账户初始金额 profit = ContextInfo.profit/ContextInfo.capital #如果不是回测状态 if not ContextInfo.do_back_test: #画出收益率变动曲线 ContextInfo.paint('profit_ratio', profit, -1, 0) #自定义函数,用来发出买卖信号def signal(ContextInfo): #买入信号字典,键为沪深300成分股列表,值为0 buy = {i:0 for i in ContextInfo.s} #卖出信号字典,键为沪深300成分股列表,值为0 sell = {i:0 for i in ContextInfo.s} #取前22个交易日的K线最高价,(等比向前复权) data_high = ContextInfo.get_history_data(22,'1d','high',3) #取前2个交易日的K线最高价,(等比向前复权) data_high_pre = ContextInfo.get_history_data(2,'1d','high',3) #取前62个交易日的K线收盘价,(等比向前复权) data_close60 = ContextInfo.get_history_data(62,'1d','close',3) #遍历沪深300成分股列表 for k in ContextInfo.s: #如果有前62个交易日发收盘数据 if k in data_close60: #如果前面的数据中,每一天的数据都有 if len(data_high_pre[k]) == 2 and len(data_high[k]) == 22 and len(data_close60[k]) == 62: #如果第前2个交易日的最高价超过了前面20个交易日的最高价 if data_high_pre[k][-2] > max(data_high[k][:-2]): #给这支股票标记上买入信号 buy[k] = 1 #超过20日最高价,加入买入备选 #如果第前2个交易日的最高价未超过前面20个交易日的最高价,并且低于60日均线 elif data_high_pre[k][-2] < np.mean(data_close60[k][:-2]): #给这支股票标记上卖出信号 sell[k] = 1 #低于60日均线,加入卖出备选 #买入卖出备选字典返回 return buy,sell
收益情况:


策略中的一些知识点,本文限于篇幅就不在此讲解了,待优化完代码,另行发文时,再加入知识点讲解部分。
发布者:股市刺客,转载请注明出处:https://www.95sca.cn/archives/129436
站内所有文章皆来自网络转载或读者投稿,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。敬请谅解!