股票回测引擎
欢迎使用MindGo量化交易平台,本文档详细介绍了MindGo平台的股票回测引擎,内容较多,您可通过Ctrl+F进行关键字搜索。
如果您在使用过程中遇到帮助中无法解答的问题,您可以:
编写股票交易策略step 1.导航栏中,依次点击"我的策略"—"策略创作"—"策略研究" step 2.新建"股票期货"策略,点击进入"策略编辑"页面,如下图 step 3.左侧编译环境内使用python 3.5 实现策略逻辑 交易股票:贵州茅台
买入:5日均线上穿20日均线
卖出:5日均线下穿20日均线
step 4.右侧选择回测时间区间,并进行回测
 策略源码:
#初始化账户
def init(context):
g.index='600519.SH'
def handle_bar(context,bar_dict):
close = history(g.index, ['close'], 20, '1d', False, fq = 'pre', is_panel=0)
MA5 = close['close'].values[-5:].mean()
#计算二十日均线价格
MA20 = close['close'].values.mean()
#如果五日均线大于二十日均线
if MA5 > MA20:
#使用所有现金买入证券
order_target_percent(g.index,1)
#记录本次买入
log.info("全仓买入{0}".format(g.index))
#如果五日均线小于二十日均线
if MA5 < MA20 :
#卖出所有证券
order_target_percent(g.index,0)
#记录本次卖出
log.info("全仓卖出{0}".format(g.index))
回测环境回测引擎运行在Python3.5之上, 请您使用Python3.5来实现策略逻辑 我们支持所有的Python标准库和部分常用第三方库, 具体请看: Python库 同时,我们也支持自定义Python库,只需将您的.py文件存放于“我的研究”根目录, 即可在回测中直接调用, 具体说明请参见常见问题-如何调用自定义Python库? 支持代码调试功能,具体请看:代码调试
编译运行
1.您的策略必须在init()和handle_bar()函数框架下实现: 2.完成策略编写后,选定回测开始日期和结束日期,选择初始资金、运行频率(每日或每分钟)等参数,点击"编译运行";
 3.回测引擎根据您选择的运行频率调用handle_bar函数,也就是执行该函数下的代码。回测引擎会实时显示策略当前时间的数据,如收益、风险指标、持仓等信息;
 4.回测引擎会根据您所使用的下单方式进行下单,并根据后续实际成交情况进行订单处理; 5.您可以在任何时候调用log.info函数来打印需要输出的日志;通过record函数输出自定义图形。 
#初始化账户
def init(context):
g.index='600519.SH'
def handle_bar(context,bar_dict):
close = history(g.index, ['close'], 20, '1d', False, fq = 'pre', is_panel=0)
MA5 = close['close'].values[-5:].mean()
#计算二十日均线价格
MA20 = close['close'].values.mean()
#设置交易信号
trade_signal=0
#如果五日均线大于二十日均线
if MA5 > MA20:
#使用所有现金买入证券
order_target_percent(g.index,1)
#记录本次买入
log.info("全仓买入{0}".format(g.index))
#记录买入信号
trade_signal=1
#如果五日均线小于二十日均线
if MA5 < MA20 :
#卖出所有证券
order_target_percent(g.index,0)
#记录本次卖出
log.info("全仓卖出{0}".format(g.index))
#记录卖出信号
trade_signal=-1
log.info(trade_signal)
record(trade_signal=trade_signal)
策略回测如果策略能成功完成编译运行,则说明策略代码在指定历史区间是可运行的。 一般而言,策略回测是将可运行的策略代码进行历史区间回测,并获取策略回测详情报告:交易明细、历史持仓、收益&风险指标分析、组合归因等。 步骤1.将可运行的策略代码,点击"开始回测"
 步骤2.在策略详情页面,查看收益概况、交易明细、历史持仓及相关分析结果,如下图:

数据MindGo提供海量优质的金融数据,以便您能实现策略逻辑 运行频率在"开始回测"左侧选择运行频率参数,参数分"每日"和"分钟"两种。 选择"每日",则系统按"日回测"进行回测,即每个交易日09:30运行一次 选择"分钟",则系统按"分钟回测"进行回测,即每个交易日内每分钟都会运行一次
运行时间
1.开盘前(9:00)运行: before_trading函数 2.盘中运行: handle_bar函数 >日回测(9:31:00)运行一次 >分钟回测(9:31:00-11:30,13:01:00-15:00:00),每分钟运行一次 >tick回测(9:30:03-11:30,13:00:03-15:00:00),每3秒钟运行一次 3.收盘后(15:30)运行: after_trading函数 滑点
在实战交易中,往往最终成交价和预期价格有一定偏差,因此我们提供两种滑点模式来帮助您更好地模拟真实市场的表现: 拆分合并与分红
当股票发生拆分,合并或者分红时,股价会出现跳空缺口,为了消除这种价格变化对回测结果的影响,我们会根据个股除权除息信息对账户中的现金或持股数量进行相应的调整修正,并自动更新到您的context信息中。 为了使回测结果更加准确,更加贴近实盘场景,我们做了如下处理: 回测所用价格数据与下单所用价格数据是独立的。即在回测过程中,您可采用前复权/不复权或后复权价格来计算交易下单信号,回测引擎均采用真实价格(即不复权价格)下单; 回测引擎会在除权除息当日,调用before_trading_start函数之前,自动处理并更新您的账户信息。 前复权数据采用动态复权模式,即在回测过程中,轮循至某个股除权除息日,则按除权后价格对之前的价格数据进行调整。
订单处理
对于您在某个单位时间下的单,我们会做如下处理:
1.按天回测
A.交易价格: >市价单:开盘价+滑点。 >限价单:委托价。 B.最大成交量: >默认为下单当日总成交量的25%,该比例可通过市场参与度函数set_volume_limit进行调整。 >若下单量低于最大成交量,则按下单量成交;若下单量大于最大成交量,则按最大成交量成交。 C.撮合方式: >市价单:开盘下单,一次性撮合,不成交或未成交部分不再撮合。 >限价单:开盘下单,一次性撮合,不成交或未成交部分不再撮合。
2.分钟回测
A.交易价格: >市价单:当前分钟起始价+滑点 >限价单:委托价 B.最大成交量: >默认为下单当前分钟总成交量的25%,该比例可通过市场参与度函数set_volume_limit进行调整。 >若下单量低于最大成交量,则按下单量成交;若下单量大于最大成交量,则按最大成交量成交。 C.撮合方式: >市价单:分钟起始点下单,一次性撮合,不成交或未成交部分即刻取消委托。 >限价单:分钟起始点下单,之后每分钟均按分钟价量撮合一次,未成交部分顺延至下一分钟进行撮合,直到完全成交或者当天收盘为止。
3.注意: 1.一天结束后, 所有未完成的订单会被取消。
2.每次订单完成(完全成交)或者取消后,我们会根据成交量计算交易费(参见set_commission), 减少您的现金。
风险模型r=α+βf+μ
其中:r为股票收益率向量,β为因子暴露,μ为特质收益,f为因子收益率
当股票特质收益率与公共因子不相关时,组合预期风险可以分解为公共因子解释的风险及特质风险:
wΣw′=wβΩβ′w′+wΔw′
其中:w为组合成份股权重,Ω为因子收益率的协方差矩阵,Δ为股票的特质风险矩阵,我们假设股票的特质风险之间也互不相关,所以Δ是一个对角矩阵
组合优化器组合优化器函数查询:OptimizePort,opt.add_constraint
优化方式 组合优化器优化方式暂定为三种:最大化夏普比(MVO)、最大化信息比率、效用最大化(剔除成本影响)优化方式后期持续增加,例如L-B模型、风险平价模型等常用的资产配置模型。 MVO算法 公式:
 使用最优化算法求解,使target最大。R为一系列个股预期收益值[r1,r2,...rn],n为股票数量,为函数输入参数项,可以选择使用历史平均收益作为预期收益,需输入股票代码。W为各个股票的权重,为优化器所要计算的未知量[w1,w2,...wn],n为股票数量。∑为个股协方差矩阵(nXn),使用个股历史涨跌幅计算,取数范围为前120个周期,例如日频数据,则选取前120个交易日的涨跌幅,如为周频,则使用日频数据进行转换。
最大化信息比率 公式:
 使用最优化算法求解,使target最大。R,W,∑定义同1.1.1中描述。Wb为个股在所选基准中所占的权重,为[wb1,wb2,...wbn],n为股票数量,当个股没有在基准中时,wb为0。基准可自由选择,作为函数输入项,默认为中证500。
效用最大化 公式:
 使用最优化算法求解,使target最大。R,W,∑定义同1.1中描述。γ 为风险厌恶系数,函数输入参数项,默认为0.5。λ为交易成本,函数输入参数项,默认为0。Wi,t-1 为股票初始权重,可自定义输入,也可选择初始权重都为0。
约束条件 目前暂定七个约束条件:换手率、跟踪误差、组合风险、个股权重、风格因子主动暴露、行业因子主动暴露、行业中性。 换手率约束 约束条件: Wi,t 为函数求解值,个股优化后的权重;Wi,t-1 为股票初始权重,可自定义输入,也可选择初始权重都为0。Uplimit为输入的换手率限制值,不可小于0,最大值为100(默认单位%)。
跟踪误差 约束条件: W和Wb为组合中的股票和基准中的股票做并集后的权重向量。例如组合中有股票A,B,C,基准包含的股票为A,B,D,则W和Wb两个向量都有四个元素[WA,WB,WC,WD]和[WbA,WbB,WbC,WbD],且WD和WbC 为0。[WA,WB,WC]为组合中股票的权重,为优化器所要求解的未知量[w1,w2,...wn],n为股票数量。∑为个股协方差矩阵(nXn),n为组合和基准股票的并集,算法同1.1.1中描述。基准可自由选择,作为函数输入项,默认为中证500。Downlimt和uplimit作为输入的跟踪误差上下限,downlimt不可小于0。
组合风险 约束条件: W为各个股票的权重,为优化器所要求解的未知量[w1,w2,...wn],n为股票数量。∑为个股协方差矩阵(nXn),计算同1.1中描述。Downlimt和uplimit作为输入的组合风险上下限,downlimt不可小于0。
个股权重 约束条件: 可针对每个股票做单独的权重约束,downlimt和uplimt为所对应股票的权重上下限,可自由输入。函数输入项包含个股代码及所要约束的上下限值,输入的股票数量0<=w<=n(优化目标中所输入的股票总数)。每个股票需在优化目标中输入的股票列表中,否则报错。为内置权重约束,所有股票的权重值加起来等于1。
风格因子主动暴露度 约束条件: Downlimt和uplimit为自定义输入的某个因子的暴露度约束上下限,输入方式为因子名称、下限值、上限值。当前共有十个风格因子,如需针对全部因子做限制,则需要输入十个相对应的风格因子暴露度上下限值。Xstyle 为个股对该风格因子的暴露度,为[x1 style1,x2style1,...xnstyle1]该数据由风险模型模块计算得出(目前易源博每日会更新该数据)。W为各个股票的权重,为优化器所要求解的未知量[w1,w2,...wn]。
行业因子主动暴露度 约束条件: Downlimt和uplimit为自定义输入的某个行业因子的暴露度约束上下限,输入方式为行业因子名称、下限值、上限值。当前共有29个行业因子(中信一级),如需针对全部因子做限制,则需要输入29个相对应的行业因子暴露度上下限值。Xindustry 为个股对该行业的暴露度(即该个股是否属于该行业,属于为1,不属于为0)。W为各个股票的权重,为优化器所要求解的未知量[w1,w2,...wn]。 Windustry为该行业在基准中所占的权重,不是矩阵,为数值。
行业中性 约束条件: Xindustry 为个股对该行业的暴露度(即该个股是否属于该行业,属于为1,不属于为0)。W为各个股票的权重,为优化器所要求解的未知量[w1,w2,...wn]。 Windustry为该行业在基准中所占的权重,不是矩阵,为数值。
组合归因组合归因是对资产组合进行分析,将其回报和风险归到提前设定的可能的原因上。目前该功能可实现对股票型策略进行收益及风险拆分,主要分为Brinson分析和风格分析。 我们首先会对每一期的持仓表进行单期归因,然后将多期的结果使用进行累计。下面我们会介绍单期归因的算法和累计归因所得到的结果。 策略成功回测后,在回测详情页面点击组合归因功能,如下图:
 组合归因详解一、Brinson组合归因Brinson 是目前最为人知的归因方案。除了需要组合持仓内容之外,还设定一个基准。最初的brinson模型是把收益拆解为行业选择收益、个股选择收益和交叉收益,我们在传统模型基础上做了进一步的扩展,拆分出配置收益和交易收益。 
对于各个拆分收益的计算如下:
 上述指标的计算只针对单期的brinson拆解,为了分析策略在一个周期里的收益归类,我们使用Carino简化因子调整。
Carino简化因子的计算方式如下:
 Rt,bt为t期策略和基准的收益率;r,b为区间内策略和基准收益率。
使用简化因子对多期收益分解进行加和:
 At,St,It为t期收益的分解项。
把收益按行业拆分,查看每个策略在每个行业是否带来了主动收益。图中主动收益展示的也是多期的收益率,通过Carino因子进行调整。主动风险计算的是多期主动风险的方差,衡量多期主动收益的稳定性。主动权重是统计整个回测区间内各行业主动权重的均值。
 二、风格分析风格因子代码 | 因子名称 | 含义 | 备注 |
---|
beta | beta | 上市公司与指数之间的协同性 | 个股的Beta值,指数收益使用HS300 | bp | 估值 | 记账价值和市值的比值 | 市净率=总市值/归属于母公司所有者权益合计 | earnings_yield | 盈利 | 分析师预测与过去财年收益统计 |
| growth | 成长 | 由销售额及盈利统计。 |
| leverage | 杠杆 | 上市公司的使用杠杆的情况。 |
| liquidity | 流动性 | 由交易量和频率不同而带来的收益 |
| momentum | 动量 | 能量性指标,表示相对强度 | 从过去某一天开始,向过去再推504天,计算其每天的超额收益对数值,再针对这504天进行加权平均 | non_linear_size | 非线性市值 | 上市公司的规模处于中等的程度 |
| size | 市值 | 上市公司的规模,即该公司是大盘股的程度 | 对数市值 | volatility | 波动率 | 对大盘偏离的不确定性 |
|
选取策略组合在回测期间的四个界面查看其当期的主动收益、主动风险及主动暴露度。相关的计算公式如下:



收益&风险指标—评定策略的优劣策略收益率—Returns
策略年化收益率—Annualized Returns
基准收益率—Benchmark Returns
基准年化收益率—Annualized Benchmark Returns
超额收益率—Excess Return
非系统性风险—Alpha
系统风险—Beta
夏普比率—Sharpe
收益波动率—Volatility
信息比率—Information Ratio
最大回撤—Max Drawdown
索提诺比率—Sortino
跟踪误差—Tracking error
下行波动率—Downside Risk
调试功能 在策略编辑器左侧,点击"行号",设置断点后,点击"编译运行"启动调试程序。

 调试程序简介 1.启动调试程序后,代码会自动运行到第一个断点处,如上图,第一个断点是行7,代码当前已运行完行7(行8还未运行) 2.调试程序右上角显示的时间是回测系统的运行时间 3.调试程序上方菜单栏,从左到右共6个按钮,分别为:恢复执行代码(跳至下一个断点),执行下一步(不运行函数),执行下一步(运行函数),跳出此函数,清空console,结束调试继续编译 4.调试程序左侧即为console面板,右侧为监控属性面板 5.1分钟内不使用调试程序,则自动关闭调试程序 6.调试时,程序无视注释内容
菜单栏按钮介绍 1.恢复执行代码(跳至下一个断点):直接运行至下一个断点处 2.执行下一步(不运行函数):运行下一行代码,如果下一行代码调用函数,则直接运行完该函数,接着准备下一行代码 3.执行下一步(运行函数):运行下一行代码,如果下一行代码调用函数,则会进入函数内,执行函数内第一行代码 4.跳出此函数:如果当前行处于函数中,则直接运行完该函数 5.清空console:清空左侧console面板的所有内容 6.结束调试继续编译:关闭调试程序,策略继续编译
如何使用console面板? 输入变量,“Enter”输出变量值(无法修改参数) 变量之间运算,“Enter”输出结果 判断变量是否满足条件,“Enter”输出结果 
如何监控变量? 调试程序右侧监控属性,点击"+添加",输入变量名,点击“完成”,即可进行监控  监控面板显示当前监控的变量及变量值,继续运行代码  运行代码的过程中,监控面板实时显示监控变量及变量值,便于您进行观察,不需要print打印函数来逐一输出。 
对比功能一般而言,量化策略会存在一个或多个参数,在验证策略逻辑的过程中,宽客们往往会尝试用不同的参数进行回测,比如在2018年,对平安银行采用双均线策略,采用不同周期的两条均线会使策略收益截然不同哦~ 如何启动回测对比?
策略回测列表中,选取相应的回测,点击左上角的对比按钮,即可启动~

 如何使用回测对比?
对比功能,包括:概况、累积收益、回撤、源码。 其中概况页面,详细展示了所选回测的回测指标,我们以平安银行双均线策略为例,创建3组参数,分别为(5,20),(10,55),(20,60)。
 累积收益页面,详细展示了所选回测的收益走势,可以发现(20,60)参数组的累积收益较高,参数周期越短,交易次数越多,收益反而越差,可能与18年的暴跌行情相关。
 回撤页面,详细展示了所选回测的回撤情况,可以发现(20,60)参数组的回撤较低,参数周期越短,交易次数越多,回撤反而越大,可能与18年的暴跌行情相关。
 源码页面,详细展示了所选回测的源码,同时只能选取两个回测进行对比

python库MindGo平台支持所有Python的基础库(https://docs./3.5/library/index.html),并支持目前流行的第三方库,例如NumPy,pandas,Ta-Lib,scikit-learn,TuShare等。主要模块介绍如下表所示。 为了保障MindGo平台的安全,以下的python包和功能已被禁用,如有不便敬请见谅: 包 | 属性和方法 |
---|
os | __builtins__ | sys | __import__ | six | get_ipython | subprocess | exec | pickle | eval | socket |
| ast |
| IPython |
| ipykernel |
|
【案例】MACD交易策略#导入talib库
import talib
#初始化函数
def init(context):
#输入股票代码
g.security = '600519.SH'
#设置MACD模型参数
#短周期平滑均线参数
g.Short = 12
#长周期平滑均线参数
g.Long = 26
#DIFF的平滑均线参数
g.M = 9
#每日运行函数
def handle_bar(context, bar_dict):
#调用MACD计算函数,获取MACD值
macd = get_macd(g.security)
#判断金叉,且无持仓
if macd[-1]>0 and macd[-2]<0 and context.portfolio.market_value == 0:
#执行全仓买入
order_value(g.security,context.portfolio.available_cash)
#打印日志
log.info("买入 %s" % (g.security))
#判断死叉,且有持仓
if macd[-2]>0 and macd[-1]<0 and context.portfolio.market_value > 0:
#执行清仓
order_target(g.security,0)
#打印日志
log.info("卖出 %s" % (g.security))
#MACD计算函数
def get_macd(stock):
#获取数据
price = history(stock, ['close'], 500, '1d', True, 'pre', is_panel=1)['close']
#talib库调用MACD计算方法
DIFF, DEA, MACD = talib.MACD(price.values,
fastperiod = g.Short, slowperiod = g.Long, signalperiod = g.M)
#输出MACD值
return MACD
【案例】三因子选股模型import pandas as pd
import numpy as np
import datetime
def init(context):
# 设置最大持股数
context.max_stocks = 10
#记录天数,隔20个交易日调仓
g.day = 0
def handle_bar(context, bar_dict):
if g.day%20 !=0:
return None
g.day = g.day +1
# 每个调仓日先清仓持有的股票
for security in list(context.portfolio.positions.keys()):
order_target(security, 0)
# 首先获得当前日期
time = get_datetime()
date = time.strftime('%Y%m%d')
# 获得股票池列表
sample = get_index_stocks('000300.SH',date)
# 创建字典用于存储因子值
df = {'security':[], 1:[], 2:[], 3:[], 'score':[]}
# 因子选择
for security in sample:
q=query(
profit.roic,# 投资回报率
valuation.pb,# 市净率
valuation.ps_ttm,# 市销率
).filter(
profit.symbol==security
)
# 缺失值填充为0
fdmt = get_fundamentals(q, date=date).fillna(0)
# 判断是否有数据
if (not (fdmt['profit_roic'].empty or
fdmt['valuation_pb'].empty or
fdmt['valuation_ps_ttm'].empty)):
# 计算并填充因子值
df['security'].append(security)
df[1].append(fdmt['profit_roic'][0])# 因子1:投资回报率
df[2].append(fdmt['valuation_pb'][0])# 因子2:市净率
df[3].append(fdmt['valuation_ps_ttm'][0])#因子3:市销率
for i in range(1, 4):
# 因子极值处理,中位数去极值法
m = np.mean(df[i])
s = np.std(df[i])
for j in range(len(df[i])):
if df[i][j] <= m-3*s:
df[i][j] = m-3*s
if df[i][j] >= m+3*s:
df[i][j] = m+3*s
m = np.mean(df[i])
s = np.std(df[i])
# 因子无量纲处理,标准化法
for j in range(len(df[i])):
df[i][j] = (df[i][j]-m)/s
# 计算综合因子得分
for i in range(len(df['security'])):
# 等权重计算(注意因子方向)
s = (df[1][i]-df[2][i]-df[3][i])
df['score'].append(s)
# 按综合因子得分由大到小排序
df = pd.DataFrame(df).sort_values(by ='score', ascending=False)
# 等权重分配资金
cash = context.portfolio.available_cash/context.max_stocks
# 买入新调仓股票
for security in df[:context.max_stocks]['security']:
order_target_value(security, cash)
|