引子:市面上一些量化软件多合约操作比较复杂,甚至不支持,而backtrader实盘多合约操作非常简洁,本文对此加以介绍......
backtrader以其简单易用,又功能强大的特点,赢得了广大量化交易者喜爱。但遗憾的是对中国ctp期货实盘交易,一直缺乏生产级的接口。好消息是,今天,我们正式发布backtrader针对中国期货市场的实盘交易接口,使得广大用户可以进行实盘交易了。
我们也同步发布《扫地僧Backtrader给力教程系列二:量化回测与实盘交易高级篇》,其中会对此接口的使用加以详细说明。
这个接口支持中国期货同一合约分开的长短仓,以及自动平今平昨。 支持多合约操作 支持多周期混合,包括resample和replay动态合成bar功能 科学准确的重采样合成bar
核心策略逻辑在next中,和回测代码几乎一样。
下面,我们来看看,基于此接口,多合约策略代码是如何写的。以下代码从远端ctp行情服务器获取tick,然后resample合成10秒bar,策略中,当标的价格上穿均线时,发目标单使得目标仓位达到设定长仓位目标值,下穿均线值时,发目标单使得目标仓位达到设定短仓位目标值。self.live_data为是否进入实盘的标志。
可以看到,相对市面上其它开源产品,backtrader实盘交易代码简洁明了。
# 免责声明:作者不保证本程序的正确性,也未经严格测试,不要据此进行实盘交易,一切后果自负
import backtrader as bt
from backtrader_ctpcn_api.ctpstore import CTPStore
from backtrader_ctpcn_api.ctputil import CtpStrategy
from time import sleep
import pandas as pd
import datetime
import pytz
#######################################################################
# 策略类
########################
class SmaCross(CtpStrategy):
params = dict(
smaperiod=2,
store=None,
)
# 由ctpbee自动回调的事件方法
def on_order(self, order)-> None:
""" 报单回报 """
# print('in strategy 报单回报on_order',order)
def on_trade(self, trade) -> None:
""" 成交回报 """
# print('成交回报on_trade',trade, "\n")
def on_position(self, position) -> None:
""" 处理持仓回报 """
# print('持仓回报on_position',position)
def on_account(self, account) -> None:
""" 处理账户信息回报 """
# print('账户信息回报on_account',account)
def on_contract(self, contract)-> None:
""" 处理推送的合约信息 """
# print('合约信息回报on_account',contract)
def on_tick(self, tick):
pass
def on_realtime(self):
"""
1s触发一次的接口
"""
# 以下为backtrader自己的事件方法
def __init__(self):
# ################
# 与ctpbee挂钩
###################
# 获取ctpbeeapi的引用
self.beeapi = self.p.store.main_ctpbee_api
# 把策略自己传给beeapi对象的属性strategy
self.beeapi.set_strategy(self)
self.beecenter = self.beeapi.app.center
# d是否已进入实盘的标志
self.live_data = {d: False for d in self.datas}
# 移动均线
move_average = {d: bt.ind.MovingAverageSimple(d,period=self.params.smaperiod) for d in self.datas}
# 标的d的移动均线交叉信号指标
self.crossover={d: bt.ind.CrossOver(d, move_average[d]) for d in self.datas}
def next(self):
print('=========================================')
print('in next')
for d in self.datas:
if not self.live_data[d]:
return # 不是实时数据(还处于历史数据回填中),不进入下单逻辑
self.cancel_all() # 撤销所有未成交订单
for d in self.datas:
print(d._name, d.datetime.datetime(0), 'o h l c ', d.open[0],d.high[0],d.low[0], d.close[0],
' vol ', d.volume[0], 'openinterest', d.openinterest[0])
# 标的持仓信息
pos = self.beecenter.get_position(d._dataname)
# 长仓数量
long_pos = pos.long_volume if pos else 0
# 短仓数量,为正数
short_pos = pos.short_volume if pos else 0
print(d._name, 'long pos', long_pos, 'short pos', short_pos)
targetsize = 1 # 目标数量
print(d._name, 'crossover',self.crossover[d][0])
if self.crossover[d][0]==1: # 金叉
print(d._name, '金叉, 要使净长仓达到目标数量', targetsize)
olist = self.target_size(d.close[0], targetsize, d,buy_delta=5, sell_delta=5)
print('olist',olist)
elif self.crossover[d][0]== -1: # 死叉
print(d._name, '死叉, 要使净短仓达到目标数量', targetsize)
# 注意目标size参数设为负数
olist = self.target_size(d.close[0], - targetsize, d, buy_delta=5, sell_delta=5)
print('olist',olist)
def notify_data(self, data, status, *args, **kwargs):
msg = f'数据状态: {data._getstatusname(status)}'
print(data._name, msg)
# 设置进入实盘标志
if data._getstatusname(status) == 'LIVE':
self.live_data[data] = True
print()
print('**********************************************')
print(data._name,'进入实盘行情')
print('**********************************************')
else:
self.live_data[data] = False
#####策略结束##############################################
###########################################################
# 主程序开始
##################
if __name__ == '__main__':
# http://122.51.136.165:50080/detail.html 查看行情服务器状态
ctp_setting = {
"CONNECT_INFO": {
"userid": "????", # 你在simnow注册的账户里的investorId
"password": "????", # 你在simnow注册的账户密码
"brokerid": "9999",
"md_address": "tcp://180.168.146.187:10211", # 行情前置,行情服务器地址
"td_address": "tcp://180.168.146.187:10201", # 交易前置,交易服务器地址
"product_info": "",
"appid": "simnow_client_test",
"auth_code": "0000000000000000"
},
"INTERFACE": "ctp", # ctp/ctp_se载入接口名称,目前支持ctp生产以及ctp_se穿透式验证接口
"TD_FUNC": True, # 开启交易功能
"MD_FUNC": True, # 开启行情接收
"XMIN": [], # ctpbee生成几分钟bar,例如[1]
"TODAY_EXCHANGE":["SHFE","INE"], # 需要支持平今的交易所代码列表
"CLOSE_PATTERN": "today", # 对支持平今的交易所,指定优先平今或者平昨
}
tz = pytz.timezone('Asia/Shanghai')
cerebro = bt.Cerebro()
# 创建ctp store
store = CTPStore(ctp_setting)
# 定义数据
# ag2206.SHFE是上期所白银期货 AP2210.ZCE郑商所 a2207.DCE大商所 中国金融期货交易所(IF2205.CFFEX)
# vnpy 支持中国8大合规交易所中的5所,包括上海期货交易所,大连期货交易所、郑州期货交易所、中金所、能源所。
data0_name = 'ag2206.SHFE' # ag2206是上期所白银期货
data1_name = 'ag2207.SHFE'
# 回填数据文件所在路径
csvpath = 'E:/myquant/backtrader_ctpcn/'
data0 = store.getdata(dataname=data0_name, timeframe=bt.TimeFrame.Ticks,
# 回填数据来自哪里,如果不想回填,则设backfill_from=None
backfill_from = load_hist_ticks(csvpath+'tickhistory.csv'),
qcheck = 5, # 等待远端tick的超时
sessionstart = datetime.time(21,00,00), # 开市时间
sessionend = datetime.time(15,00,00), # 闭市时间
tzinput = tz,
tz=tz
)
# cerebro.adddata(data0)
# 重采样tick合成需要的bar,注意设置name
cerebro.resampledata(data0, timeframe=bt.TimeFrame.Seconds, compression=10, name=data0_name+'10s')
# 多合约
data1 = store.getdata(dataname=data1_name, timeframe=bt.TimeFrame.Ticks,
# 回填数据来自哪里,如果不想回填,则设backfill_from=None
backfill_from = load_hist_ticks(csvpath+'tickhistory1.csv'),
qcheck = 5, # 等待远端tick的超时
sessionstart = datetime.time(21,00,00), # 开市时间
sessionend = datetime.time(15,00,00), # 闭市时间
tzinput = tz,
tz=tz
)
# cerebro.adddata(data1)
# 回放tick合成需要的bar,注意设置name
cerebro.resampledata(data1, timeframe=bt.TimeFrame.Seconds, compression=10, name=data1_name+'10s')
# 注入策略,注意store参数设置
cerebro.addstrategy(SmaCross, store=store)
# 当仿真行情与实盘同步时,is_realtime设为True,否则设为False
cerebro.run(is_realtime=True, tz=tz)
以下为backtrader通过我们开发的中国期货实盘接口交易的视频演示:
backtrader中国期货实盘接口交易演示