vnpy_tushare目前没有提供期权支持,tushare好像只有期权日线行情,可以自己用pro_bar函数试试看
可以检查一下未开仓时的策略持仓self.pos,然后在engine的process_trade_event函数下打印排查策略持仓的变化
发布于vn.py社区公众号【vnpy-community】
原文作者: 丛子龙 | 发布时间:2023-09-19
2023年VeighNa小班特训营【套利价差交易】即将在10月中旬开班!对比趋势跟踪类的CTA策略,均值回归类的价差策略由于其高胜率的特征,能够实现相对更加平稳的盈利绩效,适合用于量化投资组合在市场横盘震荡期的配置优化。目前半数名额已经被报名锁定,感兴趣的同学请抓紧!内容大纲戳这里。
本文将会在上一篇文章中提到的策略基础之上,扩大可投资的标的数量,在更多种类的大类资产中寻找超额收益。
我们从上市时间在2016年以前的ETF中选择了如下标的:
医药50ETF(512120.SSE)
金融ETF(510230.SSE)
TMTETF(512220.SSE)
信息科技ETF(512330.SSE)
证券保险ETF(512070.SSE)
可选消费ETF(159936.SZSE)
必选消费ETF(512600.SSE)
能源ETF(159930.SZSE)
材料ETF(159944.SZSE)
大宗商品ETF(510170.SSE)
黄金ETF(518880.SSE)
相信大家对A股市场中的行业轮动现象都不陌生。行业轮动是利用市场趋势获利的一种主动交易策略,其本质是利用不同投资品种强势时间的错位对行业持仓进行切换,以达到投资收益优化的目的。
通俗点讲,就是根据不同行业的区间表现差异性进行轮动配置,力求能够抓住区间内表现较好的行业、剔除表现不佳的行业,在判断市场不佳的时候,降低权益类仓位,提升另类资产的比例。
在本策略中,专门选择了能代表具体行业的ETF作为资产配置标的,上述列表中除了行业ETF之外还包括了大宗商品和黄金ETF。大宗商品具有较好的抗通胀属性,而黄金则是具有避险资产的属性。同时它们自身也与权益类资产一样,拥有较强的趋势性。
选定了资产池以后,下一步就是将数据加载到回测引擎中进行回测,策略层面将会沿用上篇文章中的EtfRotationStrategy。
这次在回测任务层面会与常用的单次回测稍有不同,原因是我们并不知道怎样配置资产组合才能获得更好的效果,所以在每次回测中需要尝试调整:
为了尽可能寻找到优质的资产组合,本次投研将使用“嵌套循环+批量回测”的方法,具体实现包括以下三步:
第一步:确定组合搜索空间
第二步:遍历组合列表回测
第三步:对回测结果排序分析
首先要计算的是:给定一定数量的合约,一共有多少种组合方法。在这里会用到Python的内置库【itertools】:
# 大类资产ETF范围
etf_list = [
"510170.SSE",
"159944.SZSE",
"512220.SSE",
"512120.SSE",
"159936.SZSE",
"159930.SZSE",
"512330.SSE",
"510230.SSE",
"512600.SSE",
"512070.SSE",
"518880.SSE",
]
# 筛选投资池,下限4只
all_combinations = []
for r in range(4, len(etf_list)):
all_combinations.extend(combinations(etf_list, r))
# 显示总投资池数量
len(all_combinations)
运行上述代码,即可得到一个缓存着所有可能组合的列表。
这么多组合,用手来一个一个敲入系统进行回测肯定是不现实的。随着标的池扩大,组合总数会以指数级增长。此时需要编写一段脚本让程序实现批量回测并缓存目标数据:
def run_backtesting(vt_symbols: list[str], holding_size: int) -> float:
"""运行回测并返回夏普比率"""
engine = BacktestingEngine()
engine.output = lambda a: a
engine.set_parameters(
vt_symbols=vt_symbols,
interval=Interval.DAILY,
start=datetime(2016, 1, 1),
end=datetime.now(),
rates={key: 0.0001 for key in vt_symbols},
slippages={key: 0.001 for key in vt_symbols},
sizes={key: 1 for key in vt_symbols},
priceticks={key: 0.001 for key in vt_symbols},
capital=1_000_000,
)
setting = {"holding_size": holding_size}
engine.add_strategy(EtfRotationStrategy, setting)
engine.load_data()
engine.run_backtesting()
engine.calculate_result()
statistics = engine.calculate_statistics(output=False)
return statistics["sharpe_ratio"]
# 遍历执行回测
results = {}
for combo in all_combinations:
vt_symbols = list(combo)
for i in range(1, math.floor(len(vt_symbols) / 2) + 1):
sharpe_ratio = run_backtesting(vt_symbols, i)
results[combo, i] = sharpe_ratio
运行完成后就会得到一个名为【results】的字典,字典当中包含每一个组合的夏普比率,下一步对results进行排序:
# 基于Sharpe Ratio排序
sorted_results = sorted(results, key=results.get, reverse=True)
# 查看排名前100的组合
print(sorted_results[:100])
首先需要查看在排名靠前的组合中哪些ETF出现的次数较多:
# 计算参数出现频次
etf_counts = defaultdict(int)
size_counts = defaultdict(int)
for tp in sorted_results[:100]:
vt_symbols, holding_size = tp
for vt_symbol in vt_symbols:
etf_counts[vt_symbol] +=1
size_counts[holding_size] += 1
# 绘制ETF代码的出现频率
plt.figure(figsize=(12, 6))
plt.bar(etf_counts.keys(), etf_counts.values()) ))
# 绘制持仓数量出现频率
plt.bar(size_counts.keys(), size_counts.values())
运行过后得到下图(为了阅读清晰,这里将ETF代码转换成了名称):
排名靠前的投资组合中,出现频率较高的有必选消费ETF,信息科技ETF,金融ETF和黄金ETF。这说明在回测时段中,能为策略提供较好收益的资产是它们。
同时不同资产出现频率的差别并没有非常显著,说明每一个资产都可以在不同的时间提供一定的超额收益。
另一方面,排名靠前的ETF持仓数量为2,其次是3和1:
这显示并非同时持仓合约的数量越大效果就越好,反而将合约数量控制在一定水平以内可能获得更好的整体绩效。
接下来使用排名靠前的ETF组合池作为回测合约,并用优化引擎对【regression_window】参数进行穷举优化,得到的结果如下:
从资金曲线上看,虽然经过了前文的种种优化,但本次选择的ETF组合池整体回测绩效不如之前文章中的结果。
既然是轮动策略,那么理所当然会希望分析在历史回测过程中策略持仓成分的变化。这时可以遍历回测引擎的逐笔成交记录字典(engine.trades)来一笔笔检视每日持仓的变化,但无疑很麻烦,而且不够直观。
更方便的方法,是通过回测引擎提供的【get_all_daily_results】获取策略的逐日盯市统计数据来实现可视化分析:
import pandas as pd
import plotly.graph_objects as go
# 这里的实现需要运行完回测程序,并得到了一个engine对象
# 获取每日持仓数据
results = engine.get_all_daily_results()
# 创建DataFrame
df = pd.DataFrame(
[pos.end_poses for pos in results], index=[pos.date for pos in results]
)
# 这里对数据进行简单处理,我们假设所有持仓都是同等大小
df = df.clip(0, 100)
# 还记得持仓种类数额吗?这里将N设置为策略里的security_size
df[df.T.sum() > 100] *= 1 / 2
# 绘制图表
fig = go.Figure()
for col in df.columns:
fig.add_trace(
go.Scatter(x=df.index, y=df[col], mode="none", fill="tozeroy", name=col)
)
fig.show()
输出的历史持仓分布图表如下:
需要注意的是,上述绘图方法只适用于策略在每只ETF上持仓市值相同的情况。有兴趣的同学也可以自行调整大类资产ETF的范围,结合之前两篇文章介绍的优化方法进一步的研究。
完整策略代码和回测数据文件,可以通过【VeighNa进阶用户交流群】获取:
免责声明
文章中的信息或观点仅供参考,作者不对其准确性或完整性做出任何保证。读者应以其独立判断做出投资决策,作者不对因使用本报告的内容而引致的损失承担任何责任。
vnpy_ctptest包的版本号前三位是API版本号
一般的股票接口都没有提供历史数据下载接口,直接配置数据服务获取历史数据即可
有足够的历史数据就能成功回测
没有配置数据服务通过数据服务查询K线失败是正常的,数据服务查不到就会去配置的数据库读取,图上后续也输出了初始化完成的日志。如果数据库数据足够初始化的话,图形界面上的指标应该是有数值的
不想要界面直接用no_ui脚本跑就好
https://gitee.com/vnpy/vnpy/blob/master/examples/no_ui/run.py
收不到tick就不会合成新的小时线,如果非交易时段收到了tick可以过滤掉
日级别的组合策略合约交易时间不同影响不大,再高频率一点的截面策略应该考虑一下品种交易时间的一致性。组合策略的合约选择、频率选择需要自己根据经验进行调整了
建议先把数据处理成单一合约按时序排列的格式再导入试试看
周末的simnow不稳定
策略初始化读取的是上次停止策略时缓存的变量值
发布于veighna社区公众号【vnpy-community】
原文作者:用Python的交易员 | 发布时间:2023-09-15
2023年VeighNa小班特训营【套利价差交易】即将在10月中旬开班!对比趋势跟踪类的CTA策略,均值回归类的价差策略由于其高胜率的特征,能够实现相对更加平稳的盈利绩效,适合用于量化投资组合在市场横盘震荡期的配置优化。目前半数名额已经被报名锁定,感兴趣的同学请抓紧!内容大纲戳这里。
上周发布了VeighNa的3.8.0版本,本次更新的主要内容是IB接口的整体功能强化,升级到10.19.1新版本API的同时,也进一步完善了期权相关的交易功能,包括期权链合约数据的查询获取,以及订阅获取IB实时推送的隐含波动率和希腊值风险数据。
对于已经安装了VeighNa Studio的用户,可以使用快速更新功能完成自动升级。对于没有安装的用户,请下载VeighNa Studio-3.8.0,体验一键安装的量化交易Python发行版,下载链接:
https://download.vnpy.com/veighna_studio-3.8.0.exe
使用Ubuntu或者Mac系统的用户,推荐使用VeighNa Docker量化交易容器解决方案:
https://hub.docker.com/repository/docker/veighna/veighna
VeighNa平台的IB接口模块vnpy_ib,底层基于IB官方推出的ibapi接口库开发。目前ibapi的10.19.1新版本已经不再通过PyPI发布,也就是无法直接通过pip install来安装了(会安装到老版本的9.81.1.post1),同样也意味着无法在VeighNa Studio中打包提供。
用户首先需要前往IB官网的API下载页面,下载自己操作系统对应的API安装程序:
推荐选择Stable版本,图中这里对应的是2022年11月16日发布的10.19版本(左上红色方框),下载完成后运行安装,API开发库会被安装到指定目录(默认为C:\TWS API)。
打开该目录下的source\pythonclient文件夹,看到如下图所示的内容:
该文件夹中包含的就是ibapi接口库源代码,在空白处按住Shift点击鼠标右键,菜单中选择【在此处打开Powershell窗口】,在弹出的命令行中运行下述命令即可将ibapi安装到Python环境中:
python setup.py install
由于IB接入的金融市场数量众多,导致其上可交易的合约数量非常庞大,不同交易所之间经常会出现合约代码冲突的情况。而国内金融市场大部分情况下都可以通过一个简短的代码来确定具体要交易的合约,比如IF2309.CFFEX、600036.SSE等。
VeighNa核心框架设计上遵循了这一规则,在早期vnpy_ib版本中合约代码(symbol)使用的是由IB(而非交易所)为每个合约分配的ConId,即【数字代码】(如下图中的51529211):
在TWS软件中【右键点击任意合约】->【金融产品信息】->【详情】,即可弹出上图中的页面。
尽管使用ConId在程序内部逻辑上非常方便,但对于交易员来说却不便记忆。基于社区用户的反馈建议,vnpy_ib后续版本中替换为了IB合约描述信息的一个字符串组合,即【描述代码】(如SPY-USD-STK、ES-202002-USD-FUT)。
使用【描述代码】2年多后,陆续收到期权交易相关的问题反馈,核心原因有两点:
综合来看,这两种代码类型都有各自好用的场景和难用的问题,那解决方案自然就是两者一起支持。所以在3.8.0版本中,vnpy_ib接口重新引入了对于【数字代码】的支持,并且两种代码类型可以混合使用,遵循以下规则:
建议之前已经习惯了【描述代码】的用户可以正常继续使用,对于有期权交易需求的用户则更推荐使用【数字代码】。
Interactive Brokers(IB)创始人Thomas Peterffy,早年以期权交易所场内做市交易员身份进入金融行业,后来创建了Timber Hill这家在全球期权市场发展历史上贡献颇多的自营做市商公司,而今的IB只是其当时的副业(用于提供全球交易所的交易链路接入)。
因此IB的TWS平台期权交易方面的功能可谓十分强大,本文封面图片就是TWS平台内置的Volatility Lab(波动率实验室)截图,对内部功能细节感兴趣的话推荐可以看这里的IB官方文档。
3.8.0版本的IB接口,在连接登录时提供了【查询期权】选项:
选择为【是】后,每次订阅标的合约行情时,均会自动发起查询其上期权链合约,从而满足期权策略交易中所需的期权产品组合信息。
同时在IB期权Tick行情数据推送中,增加了IB所提供的隐含波动率和希腊值风险数据支持,可以通过TickData.extra字典来访问获取,其中具体包括:
前缀
后缀
前缀和后缀两者组合为具体的数据字段,举例来说:
以上TickData数据对象,可以通过VeighNa平台中策略模块(CtaStrategy、PortfolioStrategy等)下策略模板所提供的on_tick函数来接收获取实时推送。
之前版本中的vnpy_tora基于奇点官方提供的Python 3.7 API开发,只能在Python 3.7环境中使用,VeighNa Station因为内置环境是Python 3.10一直用不了。
本次3.8.0版本更新中使用了奇点的C++ API重构封装,对于API层和Gateway层都做了兼容性调整,目前已经可以在VeighNa Station中直接加载使用。
K线合成器(BarGenerator)增加对日K线的合成支持
基于华鑫奇点柜台的C++ API重构vnpy_tora,实现VeighNa Station加载支持
新增vnpy_ib对于期权合约查询、波动率和希腊值等扩展行情数据的支持
vnpy_rest/vnpy_websocket限制在Windows上改为必须使用Selector事件循环
vnpy_rest/vnpy_websocket客户端关闭时确保所有会话结束,并等待有异步任务完成后安全退出
vnpy_ctp升级6.6.9版本API
vnpy_ctp支持大商所的1毫秒级别行情时间戳
vnpy_tqsdk过滤不支持的K线频率查询并输出日志
vnpy_datamanager增加数据频率下按交易所显示支持,优化数据加载显示速度
vnpy_ctabacktester如果加载的历史数据为空,则不执行后续回测
vnpy_spreadtrading采用轻量级数据结构,优化图形界面更新机制
vnpy_spreadtrading价差子引擎之间的事件推送,不再经过事件引擎,降低延迟水平
vnpy_rpcservice增加对下单返回委托号的gateway_name替换处理
vnpy_portfoliostrategy策略模板增加引擎类型查询函数get_engine_type
vnpy_sec更新行情API至1.6.45.0版本,更新交易API版本至1.6.88.18版本
vnpy_ib更新10.19.1版本的API,恢复对于数字格式代码(ConId)的支持
没有配置数据服务或者加载模块失败的情况下,使用BaseDatafeed作为数据服务
遗传优化算法运行时,子进程指定使用spawn方式启动,避免数据库连接对象异常
合约管理控件,增加对于期权合约的特有数据字段显示
修复vnpy_datarecorder对于新版本vnpy_spreadtrading价差数据的录制支持
修复vnpy_algotrading条件委托算法StopAlgo全部成交后状态更新可能缺失的问题
修复vnpy_ctastrategy策略初始化时,历史数据重复推送调用on_bar的问题
修复vnpy_wind查询日线历史数据时,数值存在NaN的问题
我这边用3.8.0是可以的
可以自己去vnpy.trader.engine的OmsEngine的process_contract_event函数下的if contract.gateway_name not in self.offset_converters:语句下进行打印排查
如果有历史数据权限,策略初始化load_bars(n)的时候会自动去IB查询n天的历史数据
有其他需求的话需要自己对框架进行个性化修改了
直接用conid订阅即可
参考一下2楼吧
ctp接口的持仓均价是通过onRspQryInvestorPosition推送的数据计算的,可以自己去onRspQryInvestorPosition函数下进行打印排查
全是提交中应该是底层报错了,可以自己看下底层输出信息
你的vnpy_ctp版本是?