HFTrade使用文档
由small_q创建,最终由small_q 被浏览 972 用户
交易引擎介绍
HFTrade是宽邦科技推出的致力于为用户提供便捷、功能强大的高频量化交易策略编写、回测分析、模拟测试和实盘交易的工具。
支持的品种
股票、基金、期货,可转债,未来会支持期权、债券、两融
交易频率
日线、分钟、Tick、逐笔
策略编写
策略程序架构
名称 | 说明 |
---|---|
initialize | 策略初始化函数,只触发一次。可以在该函数中初始化一些变量,如读取配置等 |
before_trading | 策略盘前交易函数,每日盘前触发一次。可以在该函数中一些启动前的准备,如订阅行情等 |
handle_bar | 当根bar行情通知函数,每根bar时间周期会触发,包括日线和分钟。注册多个合约时,每个合约都会调用一次handlebar。handlebar和handle_data不能同时使用。 |
handle_data | 行情通知函数,频率支持日线和分钟。注册多个合约时,handle_data会等待所有合约数据到齐后统一触发一次。例如多合约套利时,需要同时处理多个合约建议用handle_data。handle_data和handlebar不能同时使用。 |
handle_tick | Tick快照行情通知函数,每个标的的行情有变化时则会触发。 |
handle_l2trade | 逐笔成交行情更新时的处理函数 |
handle_order | 委托回报通知函数,每个订单状态有变化时会触发。 |
handle_trade | 成交回报通知函数,有成交时会触发。 |
主要函数说明
有两个使用频率很高的函数:initialize函数和handle_data(handle_bar)函数,理解了这两个函数开发策略就再也不是什么难事了,结合下面K线图来理解这两个函数。
从图中可以看出,其实一共有26个事件,即26根K线,第一根K线既对应黑色箭头,又对应灰色箭头,其余都只对应灰色箭头。Initialize函数只在第一个事件上调用,即第一根K线,因此很多初始设置可以放在Initialize函数里面。每个K线都对应灰色箭头,表示每个事件都会调用handle_data函数,即从第一根K线到最后一根K线都会运行handle_data一次,于是很多策略逻辑部分就可以放在handle_data里。
一些通用说明
- 框架结构与我们线上现有的zipline策略框架类似(下文我们也会说明该框架与zipline框架的一些主要差异)
- 该框架对标的,均是 代码.后缀 的形式,如 000001.SZA, 600000.SHA, RB2110.SHF,注意这里期货代码时,统一为 大写产品代码+4位年月+后缀,而目前后缀主要有:DCE, CZC, SHF, INE, CFX。其中,郑商所的实际交易代码为 SR109,但在该平台中为 SR1109 或 SR2109,即补全为4位年月的数字。
- 回测时,平台内部会自动读取历史行情进行回测,当然也支持外部传入自定义历史数据进行回测。
策略典型代码结构
下图给出了一个典型策略需要的代码结构
#给每个策略取一个名字
STRATEGY_NAME = "STRATEGY_1"
def initialize(context):
"""策略初始化函数,只触发一次。可以在该函数中初始化一些变量,如读取配置和全局使用数据"""
#输出关键日志
msg = "initialize:"
context.write_log(msg, stdout=1)
def before_trading(context, data):
"""盘前处理,策略盘前交易函数,每日盘前触发一次。可以在该函数中一些启动前的准备,如订阅行情等"""
#输出关键日志
msg = "before_trading dt:{}".format(data.current_dt)
context.write_log(msg, stdout=1)
#如果需要处理tick数据,需要添加tick处理函数:handle_tick(context, tick):
#如果需要使用handle_data,需要添加处理函数:handle_data(context, data):
def handle_bar(context, bar):
"""Bars行情通知函数,每个bar时间周期会触发,包括日线和分钟"""
#输出关键日志
msg = "handle_bar dt:{}".format(bar.datetime)
context.write_log(msg, stdout=1)
def handle_order(context, order):
"""委托回报通知函数,每个订单状态有变化时会触发"""
#输出关键日志
msg = "handle_order data:{}".format(order.log_str())
context.write_log(msg, stdout=1)
def handle_trade(context, trade):
"""成交回报通知函数,有成交时会触发"""
#输出关键日志
msg = "handle_trade data:{}".format(trade.log_str())
context.write_log(msg, stdout=1)
策略回测
回测代码的编写和运行
策略逻辑编写完成后通过接口函数 M.hfbacktest.v1 (也是一个可视化模块的入口)来进行回测,如下是此函数的详细说明
M.hfbacktest.v1(
start_date, #回测开始日期
end_date, #回测结束日期
instruments=None, #回测股票/基金/期货列表
initialize=None, #初始化函数初始化函数,initialize(context)
on_stop=None, #策略运行结束处理函数,on_stop(context)
before_trading_start=None, #在每个交易日开始前的处理函数,before_trading_start(context, data)
handle_bar=None, #每个bar更新时的处理函数, handle_bar(context, bar),不能和handle_data同时注册
handle_data=None, #数据更新时的处理函数, handle_data(context, data),不能和handle_bar同时注册
handle_tick=None, #在每个Tick快照行情更新时的处理函数,handle_tick(context, tick)
handle_l2trade=None, #在每个逐笔成交行情更新时的处理函数,handle_l2trade(context, l2trade)
handle_trade=None, #在成交回报更新时的处理函数,handle_trade(context, trade)
handle_order=None, #在委托回报更新时的处理函数,handle_order(context, order)
capital_base=1000000, #初始资金,默认为 1000000
slippage_type=SlippageType.FIXED, #指定滑点模式
slippage_value=0, #指定买卖双向的滑点
volume_limit=0.25, #执行下单时控制成交量参数,若设置为0时,不进行成交量检查;默认值是0.25,下单量如果超过该K线成交量的2.5%,多余的订单量会自动取消
product_type=None, #回测产品类型,如 stock/future/option等,一般不用指定,系统自动根据合约代码判断产品类型
price_type=None, #回测复权类型,如 真实价格[real],后复权[post]
frequency='1m', #回测频率,如 1d/1m/tick/
benchmark='000300.HIX', #benchmark:回测基准数据, 可以是DataSource,DataFrame,或者股票/指数代码如000300.
plot_charts=True, #是否画回测评估图
options=None, #其他参数从这里传入,可以在 handle_data 等函数里使用
disable_cache=0, #默认0,表示不启用数据缓存。设置为1,如果本次回测的数据范围之前已经读取过则不会重复读取,这样能加快回测速度
show_debug_info=False #默认False,表示是否打印回测框架的debug信息
)
在BigQuant平台上,新建一个空白的代码策略模板,把编写好的策略逻辑代码和回测代码拷贝进去(也可以建好模板后,直接在模板的notebook里面编写代码)。 点击”运行全部”按钮就可以进行回测了。
订单撮合处理
本章节主要介绍基于历史行情的模拟撮合介绍,主要包括基于Bar行情、快照行情、逐笔行情撮合。支持上交所和深交所两大交易所上市的A股股票、基金、债券、期权、期货等品种。不支持新股申购、市值配售、增发申购、配股等交易。股票交易费用买入按0.03%计算,卖出按0.13%计算(其中默认包含0.1%的印花税),期货则按对应的品种费率计算,实际费率也可在回测模拟中设置。
一些通用规则:
- 对于没有成交的委托,或者部分成交的委托,可以撒单。当天的委托如果没有成交,收市以后自动作废,不参加下一交易日的撮合。
- 对于市价委托,未成交部分会自动撤销(即成剩撤)。
一、基于Bar行情数据撮合规则
- 成交参考价格可指定为open/close
- 成交数量最大为当次bar的成交量,日线是一般还会加一个成交率比例
- 委托量过大时,可能出现模拟撮合结果与真实情况严重失真的情况。
二、基于快照行情数据的撮合规则
主要原则是基于行情中的最新价撮合,而不是买卖盘口的价格撮合,发出订单后,使用下一笔快照行情撮合。
买入:
- 如果最新成交价等于委托价,按照委托价成交
- 如果最新成交价低于委托价,按照最新价成交
- 注意,若成交价在买一价或涨停时,不能即时成交,委托会放入撮合等待队列,并且记录当时买一量,如果阶段成交量大于买一量,可成交数量是阶段成交量和当时的买一量的差,以这种方式模拟在真实交易市场排队的情形。但未及时考虑买一上的撤单量。
- 如果涨停板被打开,价格低于委托价,则按照现价成交。
卖出:
-
如果最新成交价等于委托价,按照委托价成交
-
如果最新价高于委托价,按照最新价撮合成交 注意,若成交价在卖一价或跌停时,不能即时成交,委托会放入撮合等待队列,并且记录当时卖一量,如果阶段成交量大于卖一量,可成交数量是阶段成交量和当时的卖一量的差,以这种方式模拟在真实交易市场排队的情形。但未及时考虑卖一上的撤单量。
-
如果跌停板被打开,价格高于委托价,则按照现价成交。 当次成交量按两个快照之间的真实成交量计算,若成交量为0,则不成交。如果真实成交数量小于委托未成交数量,则部分成交,仅撮合真实交易的成交数量,剩余的委托仍保留在撮合队列,等待新的成交明细。因此早上集合竞价期间的报单,会在 09:25:00 进行一次撮合,或 09:30:00开始进行连续竞价撮合。
例如:600804(鹏博士)上午开市后涨停,用户在10:10以涨停价委托买入1 00手,此时的成交量是51 000手, 涨停板上买一的单子是5000手,如果涨停板没有被打开,只有阶段成交量大于5000手时,用户的委托才等到 可以成交。如果成交量到了56010手,则用户成交10手(5601 0-51000-5000),剩下的部分等待更多的成量。
三、基于逐笔成交数据的撮合规则 主要原则是基于逐笔行情中的最新成交价撮合,发出订单后,使用下一笔逐笔行情撮合,需要配合当前快照盘口信息(会在收到委托时,获取当前盘口)。
买入委托:
- 市价委托:
- 当前在涨停版上,不成交,自动撤销
- 成交,即成剩余,成交量也依次从卖一到卖五档
- 不支持FOK
- 委托价格 > 最新价:
成交,成交价=最新价,成交量依次从卖一到卖十档,生成多笔成交信息,FIXME:如果超出卖十档的处理,其中价格触发到涨停板的处理。
- 委托价格 == 最新价:
- 成交类型主动卖,判断当前记录的排队数量,排队数量 <= 0,则成交,成交量为当次逐笔成交量,FIXME: 更逼真模式是取 逐笔成交-排队数量
- 成交类型主动买,不成交
- 撤单成交(仅适用于深交所)
-
撤单价格 == 买一价,对订单队列中的买入委托的排队数量做减少
-
其它撤单,不做处理
-
其它不成交
\
卖出委托:
- 市价委托:
- 当前在跌停版上,不成交,自动撤销
- 成交,即成剩余,成交量也依次从买一到买五档
- 不支持FOK
- 委托价格 < 最新价:
成交,成交价=最新价,成交量依次从买一到买十档,生成多笔成交信息,FIXME:如果超出买十档的处理,其中价格触发到跌停板的处理。
- 委托价格 == 最新价:
- 成交类型主动买,判断当前记录的排队数量,排队数量 <= 0,则成交,成交量为当次逐笔成交量,FIXME: 更逼真模式是取 逐笔成交-排队数量
- 成交类型主动卖,不成交
- 撤单成交(仅适用于深交所)
- 撤单价格 == 卖一价,对订单队列中的买入委托的排队数量做减少
- 其它撤单,不做处理 其它不成交
回测结果分析
当我们完成一个策略回测时,我们会得到如下的一个图形
上图为策略回测结果图,红色矩形标记部分包含了策略的主要信息,包括 收益概况、交易详情、每日持仓及收益、输出日志 。接下来,我们详细介绍这几个部分。
收益概况 收益概况以折线图的方式显示了策略在时间序列上的收益率,黄色曲线为策略收益率。同时也显示了沪深300收益率曲线作为比较基准,蓝色曲线为基准收益率。同时,最下面的绿色曲线为持仓占比,持仓占比即仓位,10%的持仓占比表示账户里股票价值只占10%。相对收益率的曲线并没有直接绘制在图上,点击图例 相对收益率(如下图所示),就可以将其绘制出来。
不仅如此,衡量一个策略好坏的关键指标在收益概览页面也得到展示。
- 收益率:策略整个回测时间段上的总收益率。比如,如果收益率为30%,表明起始时间是1万的本金,结束时间本金就变成1.3万了,一共赚了3000元。
- 年化收益率:该策略每一年的收益率。比如,如果回测时间段为2年,总收益率为30%,那么每年的年化收益率就在15附近(不考虑复利)。
- 基准收益率:策略需要有一个比较基准,比较基准为沪深300。若基准收益率为15%,表明在整个回测时间段,大盘本身就上涨了15%,如果策略收益率小于基准收益率,说明策略表现并不好,连大盘都没有跑赢。
- 阿尔法:衡量策略的一个重要指标,该值越大越好。
- 贝塔:衡量策略的一个重要指标,该值越小越好。
- 夏普比率:衡量策略最重要的一个指标,该指标的计算不仅考虑收益率,还考虑了风险,因此比较具有参考价值,可以理解为经过风险调整后的收益率。
- 胜率:衡量策略盈利一指标,胜率越大越好。比如10次投资中有8次获利,胜率就是80%。
- 盈亏比:衡量策略盈亏能力大小比较,盈亏比越大越好。比如投资盈利时,平均每次盈利4元,亏损时,平均每次亏损2元,那么此时的盈亏比为2。
- 收益波动率:收益率的标准差,是风险的一个指标。
- 最大回撤:策略在整个时间段上亏损最严重的时候相比净值最高值下跌的百分比。如果最大回撤为20%,表明策略在某个时间点上,相比之前的净值最高点下降了20%。最大回撤是策略评估时非常关键的一个指标,通常与风险承受能力相关。
- 信息比率:信息比率也是一个常用的策略评价指标
交易详情 交易详情主要显示了策略在整个回测过程中每个交易日的买卖信息。包括买卖时间、股票代码、交易方向、交易数量、成交价格、交易成本。具体见下图:
每日持仓及收益 每日持仓及收益主要呈现每日持有股票代码、当日收盘价、持仓股票数量、持仓金额、收益等指标。具体见下图
输出日志 输出日志主要为策略运行过程中的一些日志。包括涨跌停股票不能交易、停牌估计不能交易等。该日志可以便于我们检查回测结果的正确性。
关于回测结果和指标更详细的分析可参照如下链接
\
模拟交易
目前正在开发中。
\
策略实盘
目前正在开发中。
\
API介绍
策略回调接口
initialize(context):
策略初始化函数,只触发一次。可以在该函数中初始化一些变量,如读取配置等
- context: 策略上下文对象
before_trading(context, data):
策略盘前交易函数,每日盘前触发一次。可以在该函数中一些启动前的准备,如订阅行情等
- context: 策略上下文对象
- data: BarDatas对象
handle_tick(context, tick):
Tick快照行情通知函数,每个标的的行情有变化时则会触发。
- context: 策略上下文对象
- tick: TickData对象
handle_bar(context, bar):
Bar行情通知函数,每个bar时间周期会触发,包括日线和分钟。
- context: 策略上下文对象
- bar: BarData对象
handle_data(context, data):
行情通知函数,每个时间周期会触发,包括日线和分钟。
- context: 策略上下文对象
- data: BarDatas对象
handle_l2trade(context, l2trade_data):
逐笔成交行情通知函数,每笔逐笔成交都会触发,包括上海和深圳。
- context: 策略上下文对象
- l2trade_data: L2TradeData对象
handle_l2order(context, l2order_data):
逐笔委托行情通知函数,每笔逐笔委托都会触发,只深圳有。
- context: 策略上下文对象
- l2order_data: L2OrderData对象
handle_order(context, order):
委托回报通知函数,每个订单状态有变化时会触发。
- context: 策略上下文对象
- order: OrderData对象
handle_trade(context, trade):
成交回报通知函数,有成交时会触发。
- context: 策略上下文对象
- trade: TradeData对象
subscribe_bar(symbols, period, my_handle_bar)
订阅自定义周期回调函数。每周期(period)每个标的调用自定义函数 my_handle_bar
- symbols: list 标的列表 例如 [RB2105.SHF, HC2105.SHF]
- period: str 时间周期。支持’ 5m’, ‘10m’, ‘15m’, ‘30m’, ‘60m’, ‘120m’, ‘240m’。
- myhandle_bar: func 自定义函数 my_handle_bar(context,bar),context表示策略上下文对象,bar表示 BarData对象
\
策略请求接口
order(symbol, volume, limit_price=None, offset=Offset.NONE, order_type=OrderType.MARKET)
- 下单[股票、期货],必须指定标的、下单数量、下单价格
- param:
- symbol: str 下单标的 000001.SZA/600000.SHA/510050.HOF/RB2110.SHF
- volume: int 下单数量 大于0为买,小于0为卖
- limit_price: float 下单价格
- order_type: OrderType 委托类型
- MARKET: 市价指令
- LIMIT: 限价指令(默认)
- offset: 开平方向,股票不需要指定
- Offset.NONE: 自动决定开平方向(上期所会区分平今,内部会自动处理)
- Offser.OPEN: 开仓
- Offset.CLOSE: 平仓
- Offset.CLOSETODAY: 平今
- return:int 下单状态码 0:成功,否则失败
order_percent(symbol, percent, limit_price=None, order_type=OrderType.MARKET)
- 按比例下单[股票],必须指定标的、资金仓位比例、下单价格, 只针对买单
- param:
- symbol: str 下单标的 000001.SZA/600000.SHA
- percent: float 资金仓位比例
- limit_price: float 下单价格
- order_type: 同上
- return:int 下单状态码 0:成功否则失败
order_value(symbol, value, limit_price=None, order_type=OrderType.MARKET)
- 按价值下单[股票],必须指定标的、资金、下单价格, 只针对买单
- param:
- symbol: str 下单标的 000001.SZ/600000.SH
- value: float 资金量
- limit_price: float 下单价格
- order_type: 同上
- return:int 下单状态码 0:成功,否则失败
order_target(symbol, target, limit_price=None, order_type=OrderType.MARKET)
- 下单[股票],必须指定标的、目标数量、下单价格, 多用于清空持仓
- param:
- symbol: str 下单标的 000001.SZA/600000.SHA
- target: float 目标数量
- limit_price: float 下单价格
- order_type: 同上
- return:int 下单状态码 0:成功,否则失败
order_target_percent(symbol, target_percent, limit_price=None, order_type=OrderType.MARKET)
- 按目标比例下单[股票],必须指定标的、资金仓位比例、下单价格, 只针对买单
- param:
- symbol: str 下单标的 000001.SZ/600000.SH
- target_percent: float 目标资金仓位比例
- price: float 下单价格
- order_type: 同上
- return:int 下单状态码 0:成功否则失败
\
buy_open(symbol, volume, limit_price=None, order_type=None)
- 期货下单,买开
- param:
- symbol: str 下单标的 RB2105.SHF, AP2101.CZC, JD2013.DCE, IF2103.CFE
- volume: int 下单数量
- limit_price: float 下单价格
- order_type: 同上
- return:int 下单状态码 0:成功,否则失败
buy_close(symbol, volume, limit_price=None, order_type=None)
- 期货下单,买平
- param:
- symbol: str 下单标的
- volume: int 下单数量
- limit_price: float 下单价格
- order_type: 同上
- return:int 下单状态码 0:成功,否则失败
sell_open(symbol, volume, limit_price=None, order_type=None)
- 期货下单,卖开
- param:
- symbol: str 下单标的
- volume: int 下单数量
- limit_price: float 下单价格
- order_type: 同上
- return:int 下单状态码 0:成功,否则失败
sell_close(symbol, volume, limit_price=None, order_type=None)
- 期货下单,卖平
- param:
- symbol: str 下单标的
- volume: int 下单数量
- limit_price: float 下单价格
- order_type: 同上
- return:int 下单状态码 0:成功,否则失败
cancel_order(order_param)
- 取消订单
- order_param: order_key or OrderData or OrderCancelReq
- return: int 撤单状态码 0:成功,否则失败
cancel_all()
- 取消当前账户所有未成交订单
- return:None
subscribe(symbol)
- 订阅标的的(实时)行情。需要在盘前处理函数中每日订阅当天需要的行情。
- param:
- symbol: str 订阅标的,如 000001.SZA/510050.HOF/RB2105.SHF
\
数据获取相关接口
get_trading_account()
- 获取资金账户信息,具体参考下面的介绍
- return: TradingAccount(StockTradingAccount/FutureTradingAccount)
get_account_position(symbol, direction=Direction.NONE, create_if_none=True)
- 获取指定标的的持仓
- param:
- symbol: str 代码,如600000.SHA/000001.SZA/RB2010.SHF/IF2012.CFE/AP2103.CZC
- direction: Direction,持仓方向,如 Direction.LONG/SHORT
- create_if_none: bool 仓位不存在时是否创建新的对象
- return:StockPosition/FuturePosition
get_position(symbol, direction=Direction.NONE, create_if_none=True)
- 获取指定标的的持仓
- 同 get_account_position() 具有相同功能
get_account_positions()
- 获取所有持仓
- return:Dict[symbol, Position]
get_open_orders(symbol='')
- 获取当前挂单,即未完全成交订单
- param:
* symbol: str ,指定了标的时,只获取该标的所有挂单
- return: List[OrderData]
get_orders(symbol='')
- 获取当日所有订单
- param:
* symbol: str
- return: List[OrderData]
get_trades(symbol='')
- 获取当日所有成交
- param:
* symbol: str
- return: List[TradeData]
get_dominant(product_code)
- 获取主力合约,只针对期货
- param:
- product_code:str 期货品种代码 or 期货代码,比如 a,rb,SR
- return: str IF2103.CFE, RB2105.SHF
get_universe(symbols)
- 获取股票池
- return: List[symbols]
\
其它接口
get_trading_day()
- 获取当前交易日
- param:无
- return:str: YYYYmmdd
get_conf_param(name, d=None)
- 获取策略设置,获取策略变量,如未获取到, 返回d
- param:
- name: 需要获取的设置
- d: 默认值
- return:对象的值
get_error_msg(error_id)
- 获取错误信息
- param:
- error_id: 错误代码
- return:str 错误信息
write_log(content, level="info", stdout=0)
- 记录日志
- param:
- content: str 需要记录的内容
- level: str 日志级别 debug/info/warn/error
- stdout: 是否打印到标准输出, 小于0不记录日志,等于0不打印日志到前端,等于1时还会打印日志到前端
calc_buy_volume(symbol, price, value)
- 计算指标标的可买入数量
- param:
- symbol: str 证券代码
- price: float 指定价格
- value: float 指定金额 return: int 可买入数量
\
回测专有接口
set_commission(equities_commission=None, futures_commission=None)
- 设置回测费率,一般股票和期货有不同的费率模式
- param:
- equities_commission: PerOrder or dict like {"buy_cost": 0.0002, "sell_cost": 0.0012}
- futures_commission: PerContract or dict like {"RB": (2, 2, 2)}
- return:None
get_commission(symbol)
- 获取回测费率
- param:
- symbol: str 标的代码
- return:CommissionRateData
set_margin(symbol, margin)
- 设置回测保证金率,期货专用
- param:
- symbol: str 期货品种代码,比如 RB,A,SR
- margin: float 保证金率,比如 0.05
- return:None
get_margin_rate(symbol).long_margin_ratio_by_money
- 获取多头保证金率,期货专用
- param:
- symbol: str 期货品种代码,比如 RB2201.SHF
- return:float
get_margin_rate(symbol).short_margin_ratio_by_money
- 获取空头保证金率,期货专用(一般多空保证金率一样,取其中一个即可)
- param:
- symbol: str 期货品种代码,比如 RB2201.SHF
- return:float
set_slippage(slippage)
- 设置回测撮合模型
- param:
- slippage: sub-class of SlippageModel object
- return:None
get_contract(symbol).multiplier
- 获取合约乘数,期货专用
- param:
- symbol: str 期货品种代码,比如 RB,RB2201.SHF
- return:float
get_contract(symbol).price_tick
- 获取合约最小变动价格,期货专用
- param:
- symbol: str 期货品种代码,比如 RB,RB2201.SHF
- return:float
set_slippage_value(slippage_type, slippage_value,volume_limit)
- 设置滑点和成交量比率限制
- param:
slippage_type:
SlippageType.FIXED 固定价位
SlippageType.PERCENT 固定比率
slippage_value: float 滑点值
volume_limit: float 限制最大成交量的比率,1表示不限制
- return:None
set_stock_t1(value)
- 设置股票是否是T1,默认是True
- param:
- value: bool 如果设置成 False表示支持T0
- return:None
get_slippage().price_field_buy = value
- 设置买入时间点,例如 context.get_slippage().price_field_buy = “open”
- value str:
- “open”: 表示在开盘时买入,日线和高频回测默认都是”open”
- “close”: 表示在收盘时买入
- return:None
get_slippage().price_field_sell = value
- 设置卖出时间点, 例如 context.get_slippage().price_field_sell = “open”
- value str:
- “open”: 表示在开盘是卖出,高频回测默认是”open”
- “close”: 表示在收盘时卖出,日线回测默认是”close”
- return:None
set_enable_auto_planed_order(value)
- 设置是否支持收盘时下单,此函数主要用于分钟级跨日的波段交易策略,日频回测不涉及。
- param:
- value: bool 如果设置成 True表示在收盘时(例如15:00)的下单在下一个开盘时成交,如果为 False,表示收盘的下单作废。
- return:None
常见对象说明
TradingAccount(StockTradingAccount/FutureTradingAccount)
交易账户资金相关,可访问如下属性
- trading_day: 交易日 YYYYmmdd
- portfolio_value: 总资产,主要是资金+持仓市值
- positions_value: 总持仓市值
- available: 可用资金,主要是账户资金-冻结资金
- pre_balance: 昨日账户结算净值
- balance: 账户资金
- frozen_cash: 冻结资金
- realized_pnl: 平仓盈亏
- total_used_cash: margin + commission + frozen_cash
- commission: 今日手续费
- margin: 保证金占用
- total_margin: 冻结保证金+保证金占用
- total_frozen_margin: 冻结保证金
Position(StockPosition/FuturePosition)
合约持仓数据, 可访问以下属性
- trading_day: 交易日 YYYYmmdd
- direction: 持仓方向 Direction.LONG/SHORT
- last_price: 最新价
- cost_price: 持仓均价
- current_qty: 当前数量
- avail_qty: 可用数量
- today_qty: 今持仓
- yd_qty: 昨持仓
- frozen_qty: 冻结数量
- margin: 保证金占用
- market_value: 持仓市值
- realized_pnl: 平仓盈亏
- long: 多头持仓,期货专用
- short: 空头持仓,期货专用
Portfolio
投资组合对象,主要为兼容zipline框架
- positions_value: 持仓市值
- portfolio_value: 总资产
- cash: 可用资金(即TradingAccount中的available)
- actual_cash: 实时资金(即TradingAccount中的balance)
- positions: Dict 获取持仓字典,可通过标的获取各持仓对象
OrderData
委托数据, 可访问以下属性
- trading_day: 交易日 YYYYmmdd
- instrument: 合约代码,如 000001/RB2105
- exchange: 交易所代码,如 SSE/SZSE/SHFE/CFFEX/SHFE/INE/CZCE/DCE
- symbol: 内部合约标识,如 000001.SZA, RB2105.SHF
- order_id: 本地委托编号(主要为本地生成)
- order_sysid: 柜台/交易所报单编号(服务端生成)
- bt_order_sysid: 本地唯一标识的柜台/交易所报单编号
- direction: 买卖方向 Direction.LONG/SHORT
- offset: 开平标志 Offset.OPEN/CLOSE/CLOSETODAY
- order_qty: 委托数量
- order_price: 委托限价
- filled_qty: 成交数量
- order_type: 委托类型 OrderType.LIMIT/MARKET
- order_status: 委托状态 OrderStatus.NOTTRADED/ALLTRADED/CANCELLED
- order_key: 本地订单唯一标识
- order_time: 委托时间
- insert_date: 委托日期
- status_msg: 委托状态描述
TradeData
成交数据, 可访问以下属性
- trading_day: 交易日 YYYYmmdd
- instrument: 合约代码,如 000001/RB2105
- exchange: 交易所代码,如 SSE/SZSE/SHFE/CFFEX/SHFE/INE/CZCE/DCE
- symbol: 内部合约标识,如 000001.SZA, RB2105.SHF
- order_id: 本地委托编号(主要为本地生成)
- order_sysid: 柜台/交易所报单编号(服务端生成)
- bt_order_sysid: 本地唯一标识的柜台/交易所报单编号
- trade_id: 成交编号
- bt_trade_id: 本地唯一标识的成交编号
- direction: 买卖方向 Direction.LONG/SHORT
- offset: 开平标志 Offset.OPEN/CLOSE/CLOSETODAY
- filled_price: 成交价格
- filled_qty: 成交数量
- filled_money: 成交金额
- trade_time: 成交时间
- trade_date: 成交日期
- order_key: 本地订单唯一标识
TickData
行情快照数据, 可访问以下属性
- trading_day: 交易日 YYYYmmdd
- instrument: 合约代码,如 000001/RB2105
- exchange: 交易所代码,如 SSE/SZSE/SHFE/CFFEX/SHFE/INE/CZCE/DCE
- symbol: 内部合约标识,如 000001.SZA, RB2105.SHF
- last_price: 最新成交价
- volume: 当日累计成交量
- turnover: 当日累计成交金额
- open_price: 当日开盘价
- high_price: 当日最高价
- low_price: 当日最低价
- open_interest: 最新持仓量
- bid_priceX: 买盘价格
- bid_volumeX: 买盘数量
- ask_priceX: 卖盘价格
- ask_volumeX: 卖盘数量
- pre_close: 昨收盘(股票里为调整后的价格)
- upper_limit: 涨停价
- lower_limit: 跌停价
- datetime: datetime 当前日期时间
- time: 当前更新时间 HH:MM:SS.fff
- time_int: 当前整数时间 93520500,毫秒精度
BarData
Bar行情数据, 可访问以下属性
- datetime: datetime 当前日期时间
- symbol: 内部合约标识,如 000001.SZA, RB2105.SHF
- turnover: 当根bar成交金额
- close: 当根bar收盘价
- high: 当根bar最高价
- low: 当根bar最低价
- open: 当根bar开盘价
- open_interest: 最新持仓量
- trading_day: 交易日期
- product_code: 品种代码
BarDatas
Bar行情访问(注意不是单个bar数据), 可访问以下属性
- current_dt: datetime 当前日期时间
- trading_day_dt: datetime 交易日
- current_dominant(code): 获取主力合约代码
- get_daily_value(symbol, field): 获取最新日线数据
- current(symbol, field): 获取此刻标的指定字段值
- history(symbol, fields, count, frequency): 获取此刻以前的历史数据
- symbol: 标的代码
- fields: 单个或多个字段,如 ‘close’ 或 [‘volume’, ‘open_interest‘]
- count: 要获取的bar的数量
- frequency: 频率,如 ‘1d’, ‘1m’
\
策略对象属性
- instruments:List 运行前时指定的代码列表
- account_id: str 账户号
- account: AccountEngine 策略的账户对象
- portfolio: Portfolio 账户资产组合对象,包含有当前资金和持仓
- run_mode: RunMode 当前运行模式
- trading_calendar: TradingCalendar 交易日历对象
- options: Dict 运行前时指定的'options'参数
\
常量定义说明
Direction:买卖方向/持仓方向
- LONG: 买(多)
- SHORT: 卖(空)
Offset:开平标志
- OPEN: 开仓
- CLOSE: 平仓
- CLOSETODAY: 平今
OrderType:委托类型
- LIMIT: 限价
- MARKET: 市价
OrderStatus:委托状态
- UNKNOWN: 未知
- NOTTRADED: 未成交
- PARTTRADED: 部分成交
- ALLTRADED: 全部成交
- CANCELLED: 全部撤单
- PARTCANCELLED: 部分撤单
- REJECTED: 拒单
Frequency:频率
- DAILY: 日级别
- MINUTE: 分钟级别
- TICK: Tick级别
- TICK2: Tick2级别
AdjustType:复权类型
- NONE: 不复权
- PRE: 前复权
- POST: 后复权
Product:产品类别
- NONE: 未知
- EQUITY: 股票
- FUND: 基金
- FUTURE: 期货
- OPTION: 期权
- INDEX: 指数
\
策略示例(按交易频率分类)
例1 均线突破策略(Tick级别)
策略思想:
本策略是基于tick数据的高频日内交易策略。策略每tick触发一次,根据tick数据合并成分钟K线数据,然后计算分钟K线的20均线值,若当前tick价格上穿均线,则买开;反之,则卖开。每日交易次数小于2次,14:30分后不再建仓,尾盘阶段平掉所有仓位。
策略代码如下:
from datetime import datetime,timedelta
STRATEGY_NAME = "STRATEGY_tick"
# 根据tick数据计算分钟k线的OCHL
def calc_OCHL(ticks):
tmp_open = 0.5 * (ticks[0].ask_price1 + ticks[0].bid_price1)
tmp_close = 0.5 * (ticks[-1].ask_price1 + ticks[-1].bid_price1)
tmp_high = tmp_open
tmp_low = tmp_open
tmp_volume = ticks[-1].volume - ticks[0].volume
for tick in ticks[1:]:
mid_price = 0.5 * (tick.ask_price1 + tick.bid_price1)
if tmp_high < mid_price:
tmp_high = mid_price
if tmp_low > mid_price:
tmp_low = mid_price
return {'open' : tmp_open, 'close' : tmp_close, 'high' : tmp_high, 'low' : tmp_low, 'volume' : tmp_volume, 'time_int' : ticks[-1].time_int}
def initialize(context):
"""初始化函数"""
context.symbol = context.get_conf_param("instruments")
context.order_num = context.get_conf_param("order_num") # 下单手数
context.set_universe(context.symbol ) #设置需要处理的合约
context.ma_length = 20
context.closetime_day = context.get_conf_param("closetime_day")# 日内策略白盘平仓时间,一般14:58
context.closetime_night = context.get_conf_param("closetime_night")#日内策略夜盘平仓时间,一般22:58,注意有些商品夜盘收盘时间不一样
def before_trading(context, tick):
"""盘前处理函数"""
context.tick_num_count = 0 # tick数量记录
context.trade_count = 0 # 记录每日交易次数
context.subscribe(context.symbol) #注册合约
context.tick_num = 60 * 2 # 期货1分钟 60秒 每妙2tick
context.bar_num = context.ma_length # 均线长度
context.tick_series = NumPyDeque(context.tick_num, dtype='object') # 最近120个tick组成的队列
context.bar_series = NumPyDeque(context.bar_num, dtype='object') # 20个分钟 K线 (包括OHLC)
context.bar_open_series = NumPyDeque(context.bar_num, dtype='int') # 20个分钟 K线 开盘价
context.bar_high_series = NumPyDeque(context.bar_num, dtype='int') # 20个分钟 K线 最高价
context.bar_low_series = NumPyDeque(context.bar_num, dtype='int') # 20个分钟 K线 最低价
context.bar_close_series = NumPyDeque(context.bar_num, dtype='int') # 20个分钟 K线 收盘价
def handle_tick(context, tick):
"""主函数"""
context.tick_series.append(tick)
context.tick_num_count += 1 # 每遍历一个tick数据,计数+1
if context.tick_num_count % context.tick_num == 0: # 每过一分钟 就加一根bar
OCHL = calc_OCHL(context.tick_series) # 分钟K线
context.bar_series.append(OCHL) # 分钟K线的队列
context.bar_open_series.append(OCHL['open'])
context.bar_low_series.append(OCHL['low'])
context.bar_high_series.append(OCHL['high'])
context.bar_close_series.append(OCHL['close'])
# 不足20根k线时,直接返回,不用往下运行
if len(context.bar_series) < context.bar_num:
return
elif len(context.bar_series) == context.bar_num:
mean_close = context.bar_close_series.data().mean() # 分钟K线均值
position_long = context.get_position(tick.symbol, Direction.LONG) # 多头持仓
position_short = context.get_position(tick.symbol, Direction.SHORT) # 空头持仓
price = tick.last_price # 最新价
cur_hm = tick.datetime.strftime('%H:%M') # 当前时间
# 部分品种夜盘收盘时间不一样,此时间表示指定的尾盘平仓时间往后偏移30分钟,这段时间内不能开新仓,只能平仓。给30分钟是为了足够的冗余
closetime_nightshift = (datetime.strptime(context.closetime_night,'%H:%M') + timedelta(minutes = 30)).strftime('%H:%M')
# 尾盘平仓
if((cur_hm>=context.closetime_day and cur_hm<="15:00") or (cur_hm>=context.closetime_night and cur_hm<=closetime_nightshift)):
if (position_long.current_qty != 0):
context.sell_close(tick.symbol, position_long.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 尾盘平多 for {} 最新价={}".format(cur_hm,tick.symbol,str(price))
context.write_log(msg, stdout=0) #输出关键日志,stdout设置为0,不展示到页面,stdout设置为1,展示到页面
if (position_short.current_qty != 0):
context.buy_close(tick.symbol, position_short.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 尾盘平空 for {} 最新价={}".format(cur_hm,tick.symbol,str(price))
context.write_log(msg, stdout=0) #输出关键日志
# 建仓逻辑
if price > mean_close and position_long.current_qty == 0 and context.trade_count <=2 and cur_hm<="14:30":
context.buy_open(tick.symbol, context.order_num, price, order_type=OrderType.MARKET)
msg = "{} 开多 for {} 最新价={}".format(cur_hm,tick.symbol,str(price))
context.trade_count += 1 # 开仓次数判断需要
context.write_log(msg, stdout=0) #输出关键日志
elif price < mean_close and position_short.current_qty == 0 and context.trade_count <=2 and cur_hm<="14:30":
context.sell_open(tick.symbol, context.order_num, price, order_type=OrderType.MARKET)
msg = "{} 开空 for {} 最新价={}".format(cur_hm,tick.symbol,str(price))
context.trade_count += 1 # 开仓次数判断需要
context.write_log(msg, stdout=0) #输出关键日志
def handle_order(context, order):
"""委托回报推送"""
msg = "handle_order data:{}".format(order.log_str())
context.write_log(msg, stdout=1)
def handle_trade(context, trade):
"""成交回报推送"""
msg = "handle_trade data:{}".format(trade.log_str())
context.write_log(msg, stdout=1)
# 分别获取最新的多头持仓和空头持仓
position_long = context.get_position(trade.symbol, Direction.LONG)
position_short = context.get_position(trade.symbol, Direction.SHORT)
msg = "当前多头持仓:{} 当前空头持仓:{}".format(str(position_long),str(position_short))
context.write_log(msg, stdout=1)
instruments = ['AG2006.SHF'] # 白银2106合约
strategy_setting = [
{
"instruments": instruments,
"order_num": 1,
"closetime_day": "14:58",
"closetime_night": "22:58"
}]
start_date = "2020-01-01"
end_date = "2020-04-01"
md = M.hfbacktest.v1(
start_date=start_date, # 开始时间
end_date=end_date, # 结束时间
instruments=instruments, # 回测标的
capital_base=100000, # 初始资金
product_type=Product.FUTURE, # 回测标的所属市场
frequency = Frequency.TICK, # 回测数据级别
initialize=initialize, # 初始化函数
before_trading_start=before_trading, # 盘前处理函数
handle_tick=handle_tick, # 主函数
handle_order=handle_order,
handle_trade=handle_trade,
plot_charts=True, # 是否绘制回测图
volume_limit=1.0, # 成交比率限制
disable_cache=0,
show_debug_info=1, # 是否显示完善日志
strategy_setting=strategy_setting, # 策略外部参数配置字典
slippage_type=SlippageType.FIXED,# 滑点固定模式
slippage_value=1.0, # 买卖双向各1个滑点
before_start_days=10,
m_deps=np.random.rand()) # 模块依赖,值相同的话会命中缓存
例2 菲阿里四价策略(分钟级别)
策略思想: 菲阿里四价指的是:昨日高点、昨日低点、昨天收盘、今天开盘四个价格。 菲阿里四价上下轨的计算非常简单。昨日高点为上轨,昨日低点为下轨。当价格突破上轨时,买入开仓;当价格突破下轨时,卖出开仓。
策略代码如下:
from datetime import datetime,timedelta
STRATEGY_NAME = "STRATEGY_1"
def initialize(context):
"""初始化"""
context.ins = context.get_conf_param("instruments") # 从传入参数中获取需要交易的合约
context.order_num = context.get_conf_param("order_num") # 下单手数
context.set_universe(context.ins) # 设置需要处理的合约
context.closetime_day = context.get_conf_param("closetime_day") # 日内策略白盘平仓时间,一般14:58
context.closetime_night = context.get_conf_param("closetime_night") # 日内策略夜盘平仓时间,一般22:58,注意有些商品夜盘收盘时间不一样
def before_trading(context, data):
"""盘前处理"""
context.subscribe(context.ins) # 注册合约
context.flag = 1 # 用于获取今开
context.count = 0 # 交易次数记录
bar1d_df = data.history(context.ins, ["high","low",'close'], 1, "1d")
context.high = bar1d_df.iloc[0]['high'] # 昨高
context.low = bar1d_df.iloc[0]['low'] # 昨低
def handle_data(context, data):
"""Bar行情推送"""
# 获取当天开盘价
if context.flag == 1:
open_price = data.history(context.ins, ["open"], 1, "1m")
context.flag = context.flag + 1
context.today_open = open_price.iloc[-1]['open']
cur_date = data.current_dt
cur_hm = cur_date.strftime('%H:%M')
# 部分品种夜盘收盘时间不一样,此时间表示指定的尾盘平仓时间往后偏移30分钟,这段时间内不能开新仓,只能平仓。给30分钟是为了足够的冗余
closetime_nightshift = (datetime.strptime(context.closetime_night,'%H:%M') + timedelta(minutes = 30)).strftime('%H:%M')
# 分别获取多头持和空头持仓
position_long = context.get_position(context.ins, Direction.LONG)
position_short = context.get_position(context.ins, Direction.SHORT)
# 获取当前价格
price = data.current(context.ins, "close")
# 尾盘平仓
if((cur_hm>=context.closetime_day and cur_hm<="15:00") or (cur_hm>=context.closetime_night and cur_hm<=closetime_nightshift)):
if(position_long.current_qty != 0):
rv = context.sell_close(context.ins, position_long.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 尾盘平多 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
if(position_short.current_qty != 0):
rv = context.buy_close(context.ins, position_short.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 尾盘平空 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
#尾盘不开新仓,直接返回
return
# 建仓逻辑
# 多头持仓,最新价小于开盘价 平多
if position_long.current_qty != 0 and price < context.today_open:
rv = context.sell_close(context.ins, position_long.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 平多 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1)
# 空头持仓,最新价大于开盘价 平空
elif position_short.current_qty != 0 and price > context.today_open:
rv = context.buy_close(context.ins, position_short.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 平空 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1)
# 当前的最新价大于前一天的最高价且没有持仓 开多
if (position_long.current_qty == 0) and (price > context.high) and (context.count<=1):
rv = context.buy_open(context.ins, context.order_num, price, order_type=OrderType.MARKET)
msg = "{} 开多 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.count += 1 #只开仓一次的判断
context.write_log(msg, stdout=1)
# 当前最新价小于前一天的最低价且没有持仓 开空
elif (position_short.current_qty == 0) and (price < context.low) and (context.count<=1):
rv = context.sell_open(context.ins, context.order_num, price, order_type=OrderType.MARKET)
msg = "{} 开空 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.count += 1
context.write_log(msg, stdout=1)
def handle_order(context, order):
"""委托回报推送"""
msg = "handle_order data:{}".format(order.log_str())
context.write_log(msg, stdout=1)
def handle_trade(context, trade):
"""成交回报推送"""
msg = "handle_trade data:{}".format(trade.log_str())
context.write_log(msg, stdout=1)
# 分别获取最新的多头持仓和空头持仓
position_long = context.get_position(trade.symbol, Direction.LONG)
position_short = context.get_position(trade.symbol, Direction.SHORT)
msg = "当前多头持仓:{} 当前空头持仓:{}".format(str(position_long),str(position_short))
context.write_log(msg, stdout=1)
instruments = "RB2110.SHF"
#需要交易者传入的参数
strategy_setting = [{
"instruments": instruments,
"order_num": 2,
"closetime_day": "14:58",
"closetime_night": "22:58"
}]
start_date = "2021-04-06"
end_date = "2021-05-12"
md = M.hfbacktest.v1(start_date=start_date,
end_date=end_date,
instruments=[instruments],
capital_base=100000,
product_type=Product.FUTURE,
frequency=Frequency.MINUTE,
initialize=initialize,
before_trading_start=before_trading,
handle_data=handle_data,
handle_order=handle_order,
handle_trade=handle_trade,
plot_charts=True,
volume_limit=1.0,
disable_cache=0,
show_debug_info=1,
strategy_setting=strategy_setting,
slippage_type=SlippageType.FIXED,#滑点固定模式
slippage_value=1.0,#买卖双向各1个滑点
before_start_days=10,
m_deps=np.random.rand())
经典策略示例
例1 均线金叉开仓策略(股票)
策略思想: 1分钟频率回测,如果5均线上穿20均线平空开多,5均线下穿20均线平多。尾盘需要清仓。
策略代码如下:
#给每个策略取一个名字
STRATEGY_NAME = "double_ma"
def initialize(context):
"""策略初始化函数,只触发一次。可以在该函数中初始化一些变量,如读取配置和全局使用数据"""
#输出关键日志
msg = "initialize:"
context.write_log(msg, stdout=1)
context.ins = context.get_conf_param("instruments") #从传入参数中获取需要交易的合约
context.short_ma = 5 #均线短周期参数
context.long_ma = 20 #均线长周期参数
context.order_pct =0.1 #每只股票占用总资金的百分比
context.closetime = "14:55"
def before_trading(context, data):
"""盘前处理,策略盘前交易函数,每日盘前触发一次。可以在该函数中一些启动前的准备,如订阅行情等"""
# 输出关键日志
msg = "before_trading"
context.write_log(msg, stdout=1)
# 订阅要交易的合约的行情
context.subscribe(context.ins)
def handle_data(context, data):
"""行情通知函数"""
#获取当前时间
cur_date = data.current_dt
cur_hm = cur_date.strftime('%H:%M')
# 获取账户资金
trading_account = context.get_trading_account()
total_portfolio = trading_account.portfolio_value
for instr in context.ins:
#获取持仓情况
position = context.get_position(instr)
#最新价格
price = data.current(instr, "close")
#尾盘平仓
if(cur_hm>=context.closetime and cur_hm<="15:00"):
#有持仓则卖出
if (position.current_qty != 0):
rv = context.order(instr, -position.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 尾盘卖出{} 最新价={:.2f} 下单函数返回={}".format(cur_date,context.ins,price,rv)
context.write_log(msg, stdout=1)
#尾盘不开新仓,直接返回
continue
#获取1m历史数据数据
hist = context.history_data(instr, ["open","high","low","close"], context.long_ma + 5, "1m")
#计算短周期和长周期均线
hist['short_ma'] = hist['close'].rolling(context.short_ma).mean()
hist['long_ma'] = hist['close'].rolling(context.long_ma).mean()
#短周期均线上穿越长周期均线买入
if(hist.iloc[-2]['short_ma']<=hist.iloc[-2]['long_ma'] and hist.iloc[-1]['short_ma']>hist.iloc[-1]['long_ma']):
#当前没有持仓则市价买入
if (position.current_qty == 0):
#计算买入此股票的数量,不要超过总资金的某个比例
order_num = int(total_portfolio*context.order_pct/price/100)*100
rv = context.order(instr, order_num, price, order_type=OrderType.MARKET)
msg = "{} 买入{} 最新价={:.2f} 下单函数返回={}".format(cur_date,context.ins,price,rv)
context.write_log(msg, stdout=1)
#短周期均线下穿越长周期均线卖出
elif(hist.iloc[-2]['short_ma']>=hist.iloc[-2]['long_ma'] and hist.iloc[-1]['short_ma']<hist.iloc[-1]['long_ma']):
#有持仓则卖出
if (position.current_qty != 0):
rv = context.order(instr, -position.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 卖出{} 最新价={:.2f} 下单函数返回={}".format(cur_date,context.ins,price,rv)
context.write_log(msg, stdout=1)
def handle_order(context, order):
"""委托回报通知函数,每个订单状态有变化时会触发"""
#输出关键日志
msg = "handle_order data:{}".format(order.log_str())
context.write_log(msg, stdout=1)
def handle_trade(context, trade):
"""成交回报通知函数,有成交时会触发"""
#输出关键日志
msg = "handle_trade data:{}".format(trade.log_str())
context.write_log(msg, stdout=1)
instruments = ["000001.SZA","000002.SZA"]
# 需要交易者传入的参数
strategy_setting = [
{
"instruments": instruments,
}
]
start_date = "2021-06-01"
end_date = "2021-06-05"
md = M.hfbacktest.v1(start_date=start_date, #回测开始时间
end_date=end_date, #回测结束时间
instruments=instruments, #股票列表
capital_base=1000000, #初始资金
product_type=Product.EQUITY, #交易标的为股票
frequency=Frequency.MINUTE, #回测频率
initialize=initialize,
before_trading_start=before_trading,
handle_data=handle_data,
handle_order=handle_order,
handle_trade=handle_trade,
plot_charts=True, #画出回测结果分析图
disable_cache=0, #启用缓存加速
show_debug_info=1, #打印debug日志
strategy_setting=strategy_setting, #配置参数
slippage_type=SlippageType.FIXED,#滑点固定模式
slippage_value=0.02,#买卖双向各0.02个滑点
stock_t1=0, #0表示回测支持当天买和卖。1表示不支持t0
before_start_days=10
)
例2 价格穿越均线开仓策略(期货)
策略思想: 1分钟k线如果上穿20均线则平空开多,如果跌破20均线则平多开空, 为正反手策略。
策略代码如下:
#给每个策略取一个名字
STRATEGY_NAME = "STRATEGY_1"
def initialize(context):
"""策略初始化函数,只触发一次。可以在该函数中初始化一些变量,如读取配置和全局使用数据"""
#输出关键日志
msg = "initialize:"
context.write_log(msg, stdout=1)
#从传入参数中获取需要交易的合约
context.ins = context.get_conf_param("instrument")
#设置需要处理的股票池或者合约集合
context.set_universe(context.ins)
def before_trading(context, data):
"""盘前处理,策略盘前交易函数,每日盘前触发一次。可以在该函数中一些启动前的准备,如订阅行情等"""
# 输出关键日志
msg = "before_trading"
context.write_log(msg, stdout=1)
# 订阅要交易的合约的行情
context.subscribe(context.ins)
# 如果需要处理tick数据,需要添加tick处理函数:handle_tick(context, tick):
def handle_bar(context, bar):
"""Bars行情通知函数,每个bar时间周期会触发,包括日线和分钟"""
# 分别获取多头持仓和空头持仓,主要使用 current_qty 和 avail_qty 两个属性
position_long = context.get_position(context.ins, Direction.LONG)
position_short = context.get_position(context.ins, Direction.SHORT)
# 获取账户资金,主要使用 balance 和 available 两个属性,本策略没有使用到账户信息,在此仅作展示获取方法
trading_account = context.get_trading_account()
# 获取当前k线最新收盘价格
price = bar.close
#获取30根1m历史数据数据
hist = context.history_data(context.ins, ["open","high","low","close"], 30, "1m")
#计算20均线
hist['ma'] = hist['close'].rolling(20).mean()
#价格向上穿越20均线
if(hist.iloc[-2]['close']<hist.iloc[-2]['ma'] and price>=hist.iloc[-1]['ma']):
#有空单的先平掉
if (position_short.avail_qty != 0):
rv = context.buy_close(context.ins, position_short.avail_qty, price, order_type=OrderType.MARKET)
context.write_log("buy_close error={}".format(context.get_error_msg(rv)), stdout=1)
msg = "{} 平空 for {} 最新价={} 下单函数返回={}".format(bar.datetime,context.ins,str(price),str(rv))
context.write_log(msg, stdout=1)
#没有多单则开多1手
if (position_long.current_qty == 0):
rv = context.buy_open(context.ins, 1, price, order_type=OrderType.MARKET)
msg = "{} 开多 for {} 最新价={} 下单函数返回={}".format(bar.datetime,context.ins,str(price),str(rv))
context.write_log(msg, stdout=1)
#价格向下穿越20均线
if (hist.iloc[-2]['close']>hist.iloc[-2]['ma'] and price<hist.iloc[-1]['ma']):
#有多单的先平掉
if (position_long.avail_qty != 0):
rv = context.sell_close(context.ins, position_long.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 平多 for {} 最新价={} 下单函数返回={}".format(bar.datetime,context.ins,str(price),str(rv))
context.write_log(msg, stdout=1)
#没有空单则开空1手
if (position_short.current_qty == 0):
rv = context.sell_open(context.ins, 1, price, order_type=OrderType.MARKET)
msg = "{} 开空 for {} 最新价={} 下单函数返回={}".format(bar.datetime,context.ins,str(price),str(rv))
context.write_log(msg, stdout=1)
def handle_order(context, order):
"""委托回报通知函数,每个订单状态有变化时会触发"""
#输出关键日志
msg = "handle_order data:{}".format(order.log_str())
context.write_log(msg, stdout=1)
def handle_trade(context, trade):
"""成交回报通知函数,有成交时会触发"""
#输出关键日志
msg = "handle_trade data:{}".format(trade.log_str())
context.write_log(msg, stdout=1)
instrument = "RB2110.SHF"
# 需要交易者传入的参数
strategy_setting = [
{
"instrument": instrument,
}
]
start_date = "2021-05-14"
end_date = "2021-05-15"
md = M.hfbacktest.v1(start_date=start_date, #回测开始时间
end_date=end_date, #回测结束时间
instruments=[instrument], #合约列表
capital_base=100000, #初始资金
product_type=Product.FUTURE,
frequency=Frequency.MINUTE, #回测频率
initialize=initialize,
before_trading_start=before_trading,
handle_bar=handle_bar,
handle_order=handle_order,
handle_trade=handle_trade,
plot_charts=True, #画出回测结果分析图
disable_cache=0, #启用缓存加速
show_debug_info=1, #打印debug日志
strategy_setting=strategy_setting, #配置参数
slippage_type=SlippageType.FIXED,#滑点固定模式
slippage_value=1.0,#买卖双向各1个滑点
before_start_days=10
)
例3 Dual Thrust日内策略
策略思想: 计算前N天的最高价-收盘价的最小值和收盘价的最大值-最低价。然后取其中的较大值,乘以k值。把结果称为触发值。 当价格超过上轨(开盘+触发值)时买入,或者价格低于下轨(开盘-触发值)时卖空。此策略没有止损,为正反手策略。
策略代码如下:
from datetime import datetime,timedelta
STRATEGY_NAME = "STRATEGY_1m"
def initialize(context):
"""初始化"""
print("initialize")
context.ins = context.get_conf_param("instruments")#从传入参数中获取需要交易的合约
context.order_num = context.get_conf_param("order_num")#下单手数
context.set_universe(context.ins)#设置需要处理的合约
context.N = 5 #基于N天的历史数据
context.k1 = 0.2 #上轨参数
context.k2 = 0.2 #下轨参数
context.today_open = 0 #当天开盘价
context.closetime_day = context.get_conf_param("closetime_day")#日内策略白盘平仓时间,一般14:58
context.closetime_night = context.get_conf_param("closetime_night")#日内策略夜盘平仓时间,一般22:58,注意有些商品夜盘收盘时间不一样
def before_trading(context, data):
"""盘前处理"""
context.subscribe(context.ins) #注册合约
context.index = 0
context.flag = 1 #用于获取今开
# 取历史数据
hist = data.history(context.ins, ["open","high","low","close"], context.N, "1d")
# 计算Dual Thrust 的上下轨
HH = hist['high'].max()
HC = hist['close'].max()
LC = hist['close'].min()
LL = hist['low'].min()
context.range = max(HH - LC, HC - LL)
def handle_data(context, data):
"""Bar行情推送"""
#获取当天的开盘价
if context.flag == 1 :
history_data = data.history(context.ins, ["open"], 1, "1m")
context.today_open = history_data.iloc[-1]['open']
context.flag += 1
#获取当前时间
cur_date = data.current_dt
cur_hm = cur_date.strftime('%H:%M')
#计算上下轨
buy_line = context.today_open + context.range * context.k1
sell_line = context.today_open - context.range * context.k2
# 分别获取多头持仓和空头持仓
position_long = context.get_position(context.ins, Direction.LONG)
position_short = context.get_position(context.ins, Direction.SHORT)
# 获取当前价格
price = data.current(context.ins, "close")
#尾盘平仓
#部分品种夜盘收盘时间不一样,此时间表示指定的尾盘平仓时间往后偏移30分钟,这段时间内不能开新仓,只能平仓。给30分钟是为了足够的冗余
closetime_nightshift = (datetime.strptime(context.closetime_night,'%H:%M') + timedelta(minutes = 30)).strftime('%H:%M')
if((cur_hm>=context.closetime_day and cur_hm<="15:00") or (cur_hm>=context.closetime_night and cur_hm<=closetime_nightshift)):
if(position_long.current_qty != 0):
rv = context.sell_close(context.ins, position_long.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 尾盘平多 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1)
if(position_short.current_qty != 0):
rv = context.buy_close(context.ins, position_short.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 尾盘平空 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1)
#尾盘不开新仓,直接返回
return
#交易逻辑
if price > buy_line:
if position_long.current_qty != 0:
return
else:
if position_short.current_qty != 0:
rv = context.buy_close(context.ins, position_short.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 平空 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1)
rv = context.buy_open(context.ins, context.order_num, price, order_type=OrderType.MARKET)
msg = "{} 开多 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1)
elif price < sell_line:
if position_short.current_qty != 0:
return
else:
if position_long.current_qty != 0:
rv = context.sell_close(context.ins, position_long.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 平多 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1)
rv = context.sell_open(context.ins, context.order_num, price, order_type=OrderType.MARKET)
msg = "{} 开空 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1)
def handle_order(context, order):
"""委托回报推送"""
msg = "handle_order data:{}".format(order.log_str())
context.write_log(msg, stdout=1)
def handle_trade(context, trade):
"""成交回报推送"""
msg = "handle_trade data:{}".format(trade.log_str())
context.write_log(msg, stdout=1)
# 分别获取最新的多头持仓和空头持仓
position_long = context.get_position(trade.symbol, Direction.LONG)
position_short = context.get_position(trade.symbol, Direction.SHORT)
msg = "当前多头持仓:{} 当前空头持仓:{}".format(str(position_long),str(position_short))
context.write_log(msg, stdout=1)
instruments = "RB2110.SHF"
#需要交易者传入的参数
strategy_setting = [
{
"instruments": instruments,
"order_num": 2,
"closetime_day": "14:58",
"closetime_night": "22:58"
}
]
start_date = "2021-04-21"
end_date = "2021-04-27"
md = M.hfbacktest.v1(start_date=start_date,
end_date=end_date,
instruments=[instruments],
capital_base=100000,
product_type=Product.FUTURE,
frequency=Frequency.MINUTE,
initialize=initialize,
before_trading_start=before_trading,
handle_data=handle_data,
handle_order=handle_order,
handle_trade=handle_trade,
plot_charts=True,
volume_limit=1.0,
disable_cache=0,
show_debug_info=1,
strategy_setting=strategy_setting,
slippage_type=SlippageType.FIXED,#滑点固定模式
slippage_value=1.0,#买卖双向各1个滑点
before_start_days=10,
m_deps=np.random.rand())
例4 R-Breaker日内策略
策略思想: R-Breaker是一种短线日内交易策略。根据前一个交易日的收盘价(C)、最高价(H)和最低价(L)数据通过一定方式计算出六个价位,从大到小依次为: 突破买入价、观察卖出价、反转卖出价、反转买入、观察买入价、突破卖出价。 中心价位P = (H + C + L)/3 突破买入价 = H + 2P -2L 观察卖出价 = P + H - L 反转卖出价 = 2P - L 反转买入价 = 2P - H 观察买入价 = P - (H - L) 突破卖出价 = L - 2(H - P)
空仓时:突破策略 空仓时,当盘中价格>突破买入价,则认为上涨的趋势还会继续,开仓做多; 空仓时,当盘中价格<突破卖出价,则认为下跌的趋势还会继续,开仓做空。
持仓时:反转策略 持多单时:当日内最高价>观察卖出价后,盘中价格回落,跌破反转卖出价构成的支撑线时,采取反转策略,即做空; 持空单时:当日内最低价<观察买入价后,盘中价格反弹,超过反转买入价构成的阻力线时,采取反转策略,即做多。
策略代码如下:
from datetime import datetime,timedelta
STRATEGY_NAME = "STRATEGY_1m"
def initialize(context):
"""初始化"""
print("initialize")
context.symbol = instruments
context.ins = context.get_conf_param("instruments")#从传入参数中获取需要交易的合约
context.order_num = context.get_conf_param("order_num")#下单手数
context.set_universe(context.ins)#设置需要处理的合约
context.stopLossPrice = 50 # 设置止损点数
context.closetime_day = context.get_conf_param("closetime_day")#日内策略白盘平仓时间,一般14:58
context.closetime_night = context.get_conf_param("closetime_night")#日内策略夜盘平仓时间,一般22:58,注意有些商品夜盘收盘时间不一样
def before_trading(context, data):
"""盘前处理"""
#print("before_trading")
context.subscribe(context.ins) #注册合约
context.max_high = 0 #当日最高价
context.max_low = 0 #当日最低价
hist = data.history(context.ins, ["high","low","open","close"], 1, "1d")
high = hist['high'].iloc[0] # 前一日的最高价
low = hist['low'].iloc[0] # 前一日的最低价
close = hist['close'].iloc[0] # 前一日的收盘价
pivot = (high + low + close) / 3 # 枢轴点
context.bBreak = high + 2 * (pivot - low) # 突破买入价
context.sSetup = pivot + (high - low) # 观察卖出价
context.sEnter = 2 * pivot - low # 反转卖出价
context.bEnter = 2 * pivot - high # 反转买入价
context.bSetup = pivot - (high - low) # 观察买入价
context.sBreak = low - 2 * (high - pivot) # 突破卖出价
def handle_data(context, data):
"""Bar行情推送"""
cur_date = data.current_dt
cur_hm = cur_date.strftime('%H:%M')
#获取当日最高价和最低价
today_high = data.current(context.ins,'high')
today_low = data.current(context.ins,'low')
context.max_high = max(context.max_high,today_high)
context.max_low = max(context.max_high,today_low)
# 分别获取多头持仓,和空头持仓
position_long = context.get_position(context.ins, Direction.LONG)
position_short = context.get_position(context.ins, Direction.SHORT)
# 获取当前价格
price = data.current(context.ins, "close")
#部分品种夜盘收盘时间不一样,此时间表示指定的尾盘平仓时间往后偏移30分钟,这段时间内不能开新仓,只能平仓。给30分钟是为了足够的冗余
closetime_nightshift = (datetime.strptime(context.closetime_night,'%H:%M') + timedelta(minutes = 30)).strftime('%H:%M')
#尾盘平仓
if((cur_hm>=context.closetime_day and cur_hm<="15:00") or (cur_hm>=context.closetime_night and cur_hm<=closetime_nightshift)):
if(position_long.current_qty != 0):
rv = context.sell_close(context.ins, position_long.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 尾盘平多 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
if(position_short.current_qty != 0):
rv = context.buy_close(context.ins, position_short.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 尾盘平空 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
#尾盘不开新仓,直接返回
return
# 突破策略:
if position_long.current_qty == 0 and position_short.current_qty == 0: # 空仓条件下
if price > context.bBreak:
# 在空仓的情况下,如果盘中价格超过突破买入价,则采取趋势策略,即在该点位开仓做多
rv = context.buy_open(context.ins, context.order_num, price, order_type=OrderType.MARKET)
msg = "{} 开多 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
context.open_position_price = price
elif price < context.sBreak:
# 在空仓的情况下,如果盘中价格跌破突破卖出价,则采取趋势策略,即在该点位开仓做空
rv = context.sell_open(context.ins, context.order_num, price, order_type=OrderType.MARKET)
msg = "{} 开空 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
context.open_position_price = price
# 设置止损条件
else: # 有持仓时
# 开仓价与当前行情价之差大于止损点则止损
if (position_long.current_qty != 0 and context.open_position_price - price >= context.stopLossPrice) or \
(position_short.current_qty != 0 and price - context.open_position_price >= context.stopLossPrice):
context.write_log('达到止损点,全部平仓', stdout=1) #输出关键日志
if(position_long.current_qty != 0):
rv = context.sell_close(context.ins, position_long.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 止损平多 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
if(position_short.current_qty != 0):
rv = context.buy_close(context.ins, position_short.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 止损平空 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
# 反转策略:
if position_long.current_qty != 0: # 多仓条件下
if context.max_high > context.sSetup and price < context.sEnter:
# 多头持仓,当日内最高价超过观察卖出价后
# 盘中价格出现回落,且进一步跌破反转卖出价构成的支撑线时
# 采取反转策略,即在该点位反手做空
context.write_log('多头持仓,当日内最高价超过观察卖出价后跌破反转卖出价: 反手做空', stdout=1) #输出关键日志
rv = context.sell_close(context.ins, position_long.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 平多 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
rv = context.sell_open(context.ins, context.order_num, price, order_type=OrderType.MARKET)
context.open_position_price = price
msg = "{} 开空 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
elif position_short.current_qty != 0: # 空头持仓
if context.max_low < context.bSetup and price > context.bEnter:
# 空头持仓,当日内最低价低于观察买入价后,
# 盘中价格出现反弹,且进一步超过反转买入价构成的阻力线时,
# 采取反转策略,即在该点位反手做多
context.write_log('空头持仓,当日最低价低于观察买入价后超过反转买入价: 反手做多', stdout=1) #输出关键日志
rv = context.buy_close(context.ins, position_short.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 平空 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
rv = context.buy_open(context.ins, context.order_num, price, order_type=OrderType.MARKET)
context.open_position_price = price
msg = "{} 开多 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
def handle_order(context, order):
"""委托回报推送"""
msg = "handle_order data:{}".format(order.log_str())
context.write_log(msg, stdout=1)
def handle_trade(context, trade):
"""成交回报推送"""
msg = "handle_trade data:{}".format(trade.log_str())
context.write_log(msg, stdout=1)
# 分别获取最新的多头持仓和空头持仓
position_long = context.get_position(trade.symbol, Direction.LONG)
position_short = context.get_position(trade.symbol, Direction.SHORT)
msg = "当前多头持仓:{} 当前空头持仓:{}".format(str(position_long),str(position_short))
context.write_log(msg, stdout=1)
instruments = "RB2005.SHF"
#需要交易者传入的参数
strategy_setting = [
{
"instruments": instruments,
"order_num": 2,
"closetime_day": "14:58",
"closetime_night": "22:58"
}
]
start_date = "2020-01-21"
end_date = "2020-04-01"
md = M.hfbacktest.v1(start_date=start_date,
end_date=end_date,
instruments=[instruments], #只传入一个合约便于策略逻辑展示
capital_base=100000,
product_type=Product.FUTURE,
frequency=Frequency.MINUTE,
initialize=initialize,
before_trading_start=before_trading,
handle_data=handle_data,
handle_order=handle_order,
handle_trade=handle_trade,
plot_charts=True,
volume_limit=1.0,
disable_cache=0,
show_debug_info=1,
strategy_setting=strategy_setting,
slippage_type=SlippageType.FIXED,#滑点固定模式
slippage_value=1.0,#买卖双向各1个滑点
before_start_days=10,
m_deps=np.random.rand())
例5 网格交易策略
策略思想: 第一步:确定价格中枢、压力位和阻力位 第二步:确定网格的数量和间隔 第三步:当价格触碰到网格线时,若高于买入价,则每上升一格卖出m手;若低于买入价,则每下跌一格买入m手。
策略说明: 1、采用区域判断方式。根据网格线划分网格区域为1、2、3、4、5、6。利用pandas库提供的cut函数,将当前价格所处的网格区域表示出来。当网格区域发生变化,说明价格突破了一个网格线。 2、为了避免出现4区-5区开仓一次,5区-4区又平仓一次这种假突破,我们认定4-5开仓一次和5-4平仓一次实际上突破的是一根线,此时的形态是价格沿着这根线上下波动。只有第一次穿过这条线时才是真正的交易信号,其他的并没有形成突破。因此我们需要一个变量储存每一次交易时网格区域的变化形态(按照从大到小的顺序),比如5-4可以记为[4,5],4-5记为[4,5]。当新的记录=旧的记录时,信号失效
策略代码如下:
from datetime import datetime,timedelta
STRATEGY_NAME = "STRATEGY_1m"
def initialize(context):
"""初始化"""
print("initialize")
context.ins = context.get_conf_param("instruments")#从传入参数中获取需要交易的合约
context.order_num = context.get_conf_param("order_num")#下单手数
context.set_universe(context.ins)#设置需要处理的合约
context.last_grid = 0 # 储存前一个网格所处区间,用来和最新网格所处区间作比较
context.closetime_day = context.get_conf_param("closetime_day")#日内策略白盘平仓时间,一般14:58
context.closetime_night = context.get_conf_param("closetime_night")#日内策略夜盘平仓时间,一般22:58,注意有些商品夜盘收盘时间不一样
def before_trading(context, data):
"""盘前处理"""
context.subscribe(context.ins) #注册合约
# 记录上一次交易时网格范围的变化情况(例如从4区到5区,记为4,5)
context.grid_change_last = [0,0]
# 以前一日的收盘价为中枢价格
context.center = data.history(context.ins,["close"],1,"1d").iat[0,0]
def handle_data(context, data):
"""Bar行情推送"""
cur_date = data.current_dt
cur_hm = cur_date.strftime('%H:%M') #time
# 分别获取多头持仓和空头持仓
position_long = context.get_position(context.ins, Direction.LONG)
position_short = context.get_position(context.ins, Direction.SHORT)
# 获取当前价格
price = data.current(context.ins, "close")
#部分品种夜盘收盘时间不一样,此时间表示指定的尾盘平仓时间往后偏移30分钟,这段时间内不能开新仓,只能平仓。给30分钟是为了足够的冗余
closetime_nightshift = (datetime.strptime(context.closetime_night,'%H:%M') + timedelta(minutes = 30)).strftime('%H:%M')
#尾盘平仓
if((cur_hm>=context.closetime_day and cur_hm<="15:00") or (cur_hm>=context.closetime_night and cur_hm<=closetime_nightshift)):
if(position_long.current_qty != 0):
rv = context.sell_close(context.ins, position_long.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 尾盘平多 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
if(position_short.current_qty != 0):
rv = context.buy_close(context.ins, position_short.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 尾盘平空 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
#尾盘不开新仓,直接返回
return
# 设置网格和当前价格所处的网格区域
band = np.array([0.91, 0.92, 0.93, 0.94, 0.95, 0.96, 0.97, 0.98, 0.99, 1, 1.01, 1.02, 1.03, 1.04, 1.05, 1.06, 1.07, 1.08, 1.09]) * context.center
grid = pd.cut([price], band, labels=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18])[0]
# 如果价格超出网格设置范围,则提示调节网格宽度和数量
if np.isnan(grid):
context.write_log("价格波动超过网格范围,可适当调节网格宽度和数量", stdout=1) #输出关键日志
# 如果新的价格所处网格区间和前一个价格所处的网格区间不同,说明触碰到了网格线,需要进行交易
# 如果新网格大于前一天的网格,做空或平多
if context.last_grid < grid:
# 记录新旧格子范围(按照大小排序)
grid_change_new = [context.last_grid,grid]
# 几种例外:
# 当last_grid = 0 时是初始阶段,不构成信号
# 如果此时grid = 3,说明当前价格仅在开盘价之下的3区域中,没有突破网格线
# 如果此时grid = 4,说明当前价格仅在开盘价之上的4区域中,没有突破网格线
if context.last_grid == 0:
context.last_grid = grid
return
if context.last_grid != 0:
# 如果前一次开仓是4-5,这一次是5-4,算是没有突破,不成交
if grid_change_new != context.grid_change_last:
# 更新前一次的数据
context.last_grid = grid
context.grid_change_last = grid_change_new
# 如果有多仓,平多
if position_long.current_qty != 0:
rv = context.sell_close(context.ins,context.order_num, price, order_type=OrderType.MARKET)
msg = "{} 平多 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
# 否则,做空
if not position_long.current_qty != 0:
rv = context.sell_open(context.ins, context.order_num, price, order_type=OrderType.MARKET)
msg = "{} 开空 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
# 如果新网格小于前一天的网格,做多或平空
if context.last_grid > grid:
# 记录新旧格子范围(按照大小排序)
grid_change_new = [grid,context.last_grid]
# 几种例外:
# 当last_grid = 0 时是初始阶段,不构成信号
# 如果此时grid = 3,说明当前价格仅在开盘价之下的3区域中,没有突破网格线
# 如果此时grid = 4,说明当前价格仅在开盘价之上的4区域中,没有突破网格线
if context.last_grid == 0:
context.last_grid = grid
return
if context.last_grid != 0:
# 如果前一次开仓是4-5,这一次是5-4,算是没有突破,不成交
if grid_change_new != context.grid_change_last:
# 更新前一次的数据
context.last_grid = grid
context.grid_change_last = grid_change_new
# 如果有空仓,平空
if position_short.current_qty != 0:
rv = context.buy_close(context.ins, context.order_num, price, order_type=OrderType.MARKET)
msg = "{} 平空 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
# 否则,做多
if not position_short:
rv = context.buy_open(context.ins, context.order_num, price, order_type=OrderType.MARKET)
msg = "{} 开多 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
# 设计一个止损条件:当持仓量达到10手,全部平仓
if position_long.current_qty == 10 or position_long.current_qty == 10:
context.write_log('触发止损,全部平仓', stdout=1) #输出关键日志
if(position_long.current_qty != 0):
rv = context.sell_close(context.ins, position_long.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 止损平多 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
if(position_short.current_qty != 0):
rv = context.buy_close(context.ins, position_short.avail_qty, price, order_type=OrderType.MARKET)
msg = "{} 止损平空 for {} 最新价={} 下单函数返回={}".format(str(data.current_dt),context.ins,str(price),str(rv))
context.write_log(msg, stdout=1) #输出关键日志
def handle_order(context, order):
"""委托回报推送"""
msg = "handle_order data:{}".format(order.log_str())
context.write_log(msg, stdout=1)
def handle_trade(context, trade):
"""成交回报推送"""
msg = "handle_trade data:{}".format(trade.log_str())
context.write_log(msg, stdout=1)
# 分别获取最新的多头持仓和空头持仓
position_long = context.get_position(trade.symbol, Direction.LONG)
position_short = context.get_position(trade.symbol, Direction.SHORT)
msg = "当前多头持仓:{} 当前空头持仓:{}".format(str(position_long),str(position_short))
context.write_log(msg, stdout=1)
instruments = "RB2110.SHF"
#需要交易者传入的参数
strategy_setting = [
{
"instruments": instruments,
"order_num": 2,
"closetime_day": "14:58",
"closetime_night": "22:58"
}
]
start_date = "2021-01-01"
end_date = "2021-05-12"
md = M.hfbacktest.v1(start_date=start_date,
end_date=end_date,
instruments=[instruments],
capital_base=100000,
product_type=Product.FUTURE,
frequency=Frequency.MINUTE,
initialize=initialize,
before_trading_start=before_trading,
handle_data=handle_data,
handle_order=handle_order,
handle_trade=handle_trade,
plot_charts=True,
volume_limit=1.0,
disable_cache=0,
show_debug_info=1,
strategy_setting=strategy_setting,
slippage_type=SlippageType.FIXED,#滑点固定模式
slippage_value=1.0,#买卖双向各1个滑点
before_start_days=10,
m_deps=np.random.rand())
常见问题
回测如何设置手续费和保证金率
可以在Initial函数中通过context的set_commission设置
def initialize(context):
"""初始化"""
print("initialize")
# 股票设置费率的示例
context.set_commission(equities_commission=PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5.0))
# 期货设置费率的示例
comm_dict = {
# 开仓,平仓,平今手续费
'I':(0.00012, 0.00006, 0.00012),
'RB':(0.000045, 0.000045, 0.000045)
}
context.set_commission(futures_commission=PerContract(comm_dict))
# 期货设置保证金率
context.set_margin('RB', 0.05)
可视化模块介绍
HFTrade也支持可视化的方式,模块在回测与交易目录下面,主要有如下参数:
-
开始和结束时间:表示回测的时间段,一般不用填写使用输入的代码列表模块的时间。
-
初始化函数:只触发一次。可以在该函数中初始化一些变量,如读取配置等。
-
盘前处理函数:每日盘前触发一次。可以在该函数中一些启动前的准备,如订阅行情等。
-
Tick处理函数:Tick快照行情通知函数,每个标的的行情有变化时则会触发。
-
K线处理函数:行情通知函数,每个时间周期会触发,包括日线和分钟,同上面介绍的handle_data函数。如果需要handle_bar或自定义周期需要通过subscribe_bar(symbols, period, my_handle_bar)订阅。
-
成交回报处理函数:成交回报通知函数,有成交时会触发。
-
委托回报处理函数:委托回报通知函数,每个订单状态有变化时会触发。
-
盘后处理函数:每日盘后处理的函数。
-
初始资金:回测初始资金。
-
数据频率:支持日线,分钟,tick,tick2(限股票)。
-
价格类型:支持真实价格或后复权价格回测。
-
产品类型:支持股票、期货、期权。
-
历史数据向前取天数:如果在回测时间范围内需要向前取数据,可以填此参数。
-
基准代码:回测对比基准代码。
\