load_bar的时候策略inited状态还是False,不会发单。要初始化完成之后,策略inited状态为True了,才能发出委托
都是限价单
没有在on_tick下缓存self.tick吧
看你图上打印的时间点不像两小时K线像一小时K线
先只跑一个合约确定一下单合约K线合成有没有问题吧
发布于veighna社区公众号【vnpy-community】
原文作者:用Python的交易员 | 发布时间:2023-10-17
2023年第5场VeighNa社区活动开始报名,本场活动将在北京举办,分享主题是【基于Scikit-Learn的机器学习CTA策略信号挖掘实践】。
机器学习(Machine Learning)各种算法在量化交易领域中的应用越发广泛,但由于目前互联网上的资料质量参差不齐,许多VeighNa社区的同学想要学习尝试但却不知道从何入手。
本次活动中,我们将会由浅入深介绍机器学习技术在CTA量化策略开发中的应用场景,并基于Scikit-Learn这款广受好评的机器学习算法库,给出一套具体的CTA策略信号挖掘实践案例:
KBins聚类特征分析
特征相关性热力图
向量化回测绩效图
本场活动仅提供线下参会名额,同时因为北京场地有限(目前预估30人位置),还是优先对金融机构量化从业人员开放,请在填写报名表单时提供公司和职位信息,后续报名成功的同学小助手会添加微信联系确认。
内容:
机器学习在量化中的应用场景
基于Scikit-Learn的实践案例
其他近期社区感兴趣的主题
QA问答和交流环节
时间:10月28日 14:00-17:00
地点:北京(具体地址微信确认后通知)
报名费:99元(Elite会员免费参加)
报名方式:扫描下方二维码填写表单报名(报名成功小助手会添加微信联系确认)
no_ui和图形界面跑起来的结果应该要一致的,不一致的话可以自己打印策略实例收到的bar和策略指标进行排查
你同一个账号同时建立两个连接更容易出问题
发布于vn.py社区公众号【vnpy-community】
原文作者: 丛子龙 | 发布时间:2023-10-13
RSI相对强度指数是技术分析中常用的指标之一,由J. Welles Wilder Jr.在其1978年的著作《技术交易系统的新概念》中开发并引入技术分析中使用。RSI衡量价格变动的速度和幅度,该指标绘制在0到100的范围内。
许多技术分析类的书籍中常常见到将RSI用于均值回归交易,为人熟知的用法之一是在RSI超过70时卖出资产,当RSI跌破30时买入资产。然而,RSI也可以用作动量趋势指标,比如在上升趋势中,RSI通常在40到80之间波动,在下降趋势中在20到60之间波动。
本篇文章将会介绍三套围绕RSI构建的多头短线择时策略(策略思路来源于【QuantifiedStrategies】网站),分别是:
1. RSI经典策略
2. RSI区域动量策略
3. RSI-IBS策略
在文章的结尾,我们将会组合上述三个策略中的核心信号,构建一个新的RSI多信号集成策略,并在SPY(标普500ETF基金)和IF(沪深300股指期货)上进行回测。
由于【QuantifiedStrategies】网站文章中主要使用了SPY的日线数据进行回测,以下回测结果也基于同样数据:
策略的核心思想是在市场情绪低迷时,买入以寻求市场反弹,然后在价格触及昨日高点时卖出:
class RsiStrategy(CtaTemplate):
"""经典RSI策略"""
# 计算RSI指标的窗口
rsi_window: int = 2
# RSI低阈值
rsi_lower: int = 15
# 持仓周期限制
max_holding: int = 9
# 风险资金
risk_capital: int = 1_000_000
parameters = [
"rsi_window",
"rsi_lower",
"max_holding",
"risk_capital",
]
variables = []
def on_init(self):
"""
Callback when strategy is inited.
"""
self.write_log("策略初始化")
self.bg = BarGenerator(self.on_bar)
self.am = ArrayManager()
# 加载足够的历史数据来计算指标
self.load_bar(60)
def on_bar(self, bar: BarData):
"""
K线数据推送
"""
# 撤销之前发出的委托
self.cancel_all()
am = self.am
am.update_bar(bar)
if not am.inited:
return
# 计算RSI指标
rsi_value = am.rsi(self.rsi_window)
# 保存昨日高点
prev_high = am.high[-1]
# 判断是否要进行交易
long_signal = rsi_value <= self.rsi_lower
# 计算每次交易的头寸
self.trading_size = int(self.risk_capital / am.close[-1] / 100) * 100
# 如果无持仓,且满足条件,则直接开仓
if self.pos == 0 and long_signal:
self.buy(bar.close_price * 1.05, self.trading_size)
# 如果持仓,且满足条件,则直接平仓
if self.pos > 0:
self.sell(prev_high, self.pos)
# 推送UI更新
self.put_event()
本策略的独特之处在于其较低的回撤率(-7.99%),如此低的回撤率也造就了漂亮的收益回撤比:17.62。
本策略一旦开仓就通常保持在上升轨道上,这意味着它在大部分时间内不会暴露于市场风险之下。这种特性使得这个策略在不稳定市场中具有较强的抗跌能力,有助于保护利润。
由于在大部分时间内没有仓位,该策略可以与其他策略相互配合提供额外收益。低回撤和稳定的增长趋势为其提供了与其他更高风险策略相互协同的机会,以实现更好的综合投资表现。
该策略包括两个指标:
RSI多头范围:RSI过去N天内在40到100之间波动。
RSI多头动量:RSI的极值高点在N天内大于70。
本次回测将使用100天的回望窗口和14天的RSI,这意味着为了触发RSI多头范围的信号,RSI必须在过去的100天内在40到100之间波动。
有了这个理念,交易逻辑非常简单:
当RSI多头范围和多头动量条件都成立时,开仓。
当RSI多头范围和多头动量条件都不再成立时,平仓。
class RsiRangeMomStrategy(CtaTemplate):
"""RSI区域动量策略"""
# 计算RSI指标的窗口
rsi_window: int = 14
# RSI低阈值
rsi_lower: int = 40
# RSI高阈值
rsi_upper: int = 100
# RSI极值阈值
rsi_highest: int = 70
# 风险资金
risk_capital: int = 1_000_000
parameters = [
"rsi_window",
"rsi_lower",
"rsi_upper",
"rsi_highest",
"risk_capital",
]
variables = []
def on_init(self):
"""
Callback when strategy is inited.
"""
self.write_log("策略初始化")
self.bg = BarGenerator(self.on_bar)
self.am = ArrayManager()
# 加载足够的历史数据来计算指标
self.load_bar(150)
def on_bar(self, bar: BarData):
# 撤销所有挂单
self.cancel_all()
am = self.am
am.update_bar(bar)
if not am.inited:
return
# 计算rsi的值,并返回一个【rsi_window】长度的数组
rsi_array: np.ndarray = am.rsi(self.rsi_window, array=True)
# 计算该rsi数组的平均值,使用【np.nanmean】的原因是因为返回的数组中包含NaN值
mean_value: float = np.nanmean(rsi_array)
# 将NaN值使用【mean_value】填充
rsi_array: np.ndarray = np.nan_to_num(rsi_array, nan=mean_value)
# 使用历史rsi值计算趋势是否处于牛市区间
# 判断标准为本段历史中所有rsi的值是否都在【rsi_lower】与【rsi_upper】之间
bull_range_signal: bool = (np.all(rsi_array > self.rsi_lower) and
np.all(rsi_array < self.rsi_upper))
# 判断本段历史中是否存在较强的rsi值,只要有一个超过设定的【rsi_highest】即成立
bull_mom_signal: bool = np.any(rsi_array > self.rsi_highest)
# 计算开仓数量
trading_size: int = (int(self.risk_capital / bar.close_price / 100)
* 100)
# 判断开仓信号
if self.pos == 0:
# 如果牛市区间以及极值信号都出现,满仓入场
if bull_range_signal and bull_mom_signal:
self.buy(bar.close_price*1.05, trading_size)
# 判断平仓信号
if self.pos > 0:
# 如果信号不再成立,清仓
if not (bull_range_signal or bull_mom_signal):
self.sell(bar.close_price*0.95, self.pos)
# 推送UI更新
self.put_event()
策略中用到的IBS(内部K线强度),指标计算公式如下:
(Close - Low) / (High - Low)
IBS指标的波动范围从0到1,测量收盘价相对于日内高低点的位置,较低的值被认为是看涨的,而较高的值则是短期看跌的,IBS的基本假设是市场具有均值回归的特性。
策略交易逻辑为:
class RsiIbsStrategyOG(CtaTemplate):
"""RSI-IBS策略"""
# rsi指标窗口
rsi_window: int = 21
# 入场rsi指标阈值
rsi_entry: int = 45
# 入场ibs指标阈值
ibs_entry: float = 0.25
# 风险资金
risk_capital: int = 1_000_000
parameters = [
"rsi_window",
"rsi_entry",
"ibs_entry",
]
variables = []
def on_init(self):
"""
Callback when strategy is inited.
"""
self.write_log("策略初始化")
self.bg = BarGenerator(self.on_bar)
self.am = ArrayManager()
self.prev_close = 0
self.load_bar(60)
def on_bar(self, bar: BarData):
"""
Callback of new bar data update.
"""
# 撤销之前发出的委托
self.cancel_all()
# 对am更新bar数据
am = self.am
am.update_bar(bar)
if not am.inited:
return
# 计算开仓手数
trading_size = int(self.risk_capital / bar.close_price / 100) * 100
# 计算rsi指标数值
rsi_value = am.rsi(self.rsi_window)
# 计算ibs指标数值
ibs_value = ((bar.close_price - bar.low_price) /
(bar.high_price - bar.low_price))
# 分别计算开仓信号
cond_1 = rsi_value < self.rsi_entry
cond_2 = ibs_value < self.ibs_entry
# 汇总合成信号
long_signal = cond_1 and cond_2
# 执行开仓操作
if self.pos == 0 and long_signal:
self.buy(bar.close_price * 1.05, trading_size)
# 计算平仓信号,并执行平仓操作
if self.pos > 0:
if bar.close_price > self.prev_close:
self.sell(bar.close_price * 0.95, self.pos)
# 记录当今bar的收盘价
self.prev_close = bar.close_price
# 推送UI更新
self.put_event()
前文中的三套策略虽然都围绕RSI指标开发,但由于核心思路的区别,其回测绩效曲线还是体现出了较低的相关性。那么下一步的研究方向,就是把三套策略中的交易信号提取出来后,集成组合成为一个新的策略,看看能否达到更优秀的整体绩效。
为了实现信号的集成组合,需要对之前的策略代码进行调整,拆分成为策略信号和交易执行两块部分,具体逻辑流程看了下图应该会有一个更加清晰直观的理解:
当任一策略信号给出True值即入场做多,当所有策略信号都返回False值或达到止损目标时平仓离场。
信号生成部分被封装在独立的信号类中,分别是:
1. RsiSignal
2. RsiRangeMomSignal
3. RsiIbsSignal
实现这些信号的方式并没有什么特别之处,单单是将前述三个策略的信号生成部分截取出来封装成一个可以返回布尔值(信号)的函数即可。
RsiSignal
class RsiSignal:
def __init__(
self,
rsi_window: int = 2,
rsi_lower: int = 15
):
# 计算RSI指标的窗口
self.rsi_window: int = rsi_window
# RSI低阈值
self.rsi_lower: int = rsi_lower
def calculate_signal(self, am: ArrayManager) -> bool:
# 计算RSI指标
rsi_value = am.rsi(self.rsi_window)
# 判断是否要进行交易
return rsi_value <= self.rsi_lower
RsiRangeMomSignal
class RsiRangeMomSignal:
def __init__(
self,
rsi_window: int = 14,
rsi_lower: int = 40,
rsi_upper: int = 100,
rsi_highest: int = 70
) -> None:
# 计算RSI指标的窗口
self.rsi_window: int = rsi_window
# RSI低阈值
self.rsi_lower: int = rsi_lower
# RSI高阈值
self.rsi_upper: int = rsi_upper
# RSI极值阈值
self.rsi_highest: int = rsi_highest
def calculate_signal(self, am: ArrayManager) -> bool:
# 计算rsi的值,并返回一个【rsi_window】长度的数组
rsi_array: np.ndarray = am.rsi(self.rsi_window, array=True)
# 计算该rsi数组的平均值,使用【np.nanmean】的原因是因为返回的数组中包含NaN值
mean_value: float = np.nanmean(rsi_array)
# 将NaN值使用【mean_value】填充
rsi_array: np.ndarray = np.nan_to_num(rsi_array, nan=mean_value)
# 使用历史rsi值计算趋势是否处于牛市区间
# 判断标准为本段历史中所有rsi的值是否都在【rsi_lower】与【rsi_upper】之间
bull_range_signal: bool = (np.all(rsi_array > self.rsi_lower) and
np.all(rsi_array < self.rsi_upper))
# 判断本段历史中是否存在较强的rsi值,只要有一个超过设定的【rsi_highest】即成立
bull_mom_signal: bool = np.any(rsi_array > self.rsi_highest)
return bull_range_signal and bull_mom_signal
RsiIbsSignal
class RsiIbsSignal:
def __init__(
self,
rsi_window: int = 21,
rsi_entry: int = 45,
ibs_entry: float = 0.25
):
# rsi指标窗口
self.rsi_window: int = rsi_window
# 入场rsi指标阈值
self.rsi_entry: int = rsi_entry
# 入场ibs指标阈值
self.ibs_entry: float = ibs_entry
def calculate_signal(self, am: ArrayManager) -> bool:
# 计算rsi指标数值
rsi_value = am.rsi(self.rsi_window)
# 计算ibs指标数值
ibs_value = ((am.close[-1] - am.low[-1]) /
(am.high[-1] - am.low[-1]))
# 计算开仓信号
cond_1 = rsi_value < self.rsi_entry
cond_2 = ibs_value < self.ibs_entry
return cond_1 and cond_2
前文已经详细讲解过各个信号的生成逻辑,这里便不再赘述。
在主策略的【on_init】函数下,将上述三个信号类实例化为成员对象,并分别传入量价数据缓存容器(通过ArrayManager类的封装):
class RsiEnsembleStrategy(CtaTemplate):
""""""
author = "Tony"
rrms_rsi_window: int = 14
rrms_rsi_lower: int = 40
rrms_rsi_upper: int = 100
rrms_rsi_highest: int = 70
ris_rsi_window: int = 21
ris_rsi_entry: int = 45
ris_ibs_entry: float = 0.25
rs_rsi_window: int = 2
rs_rsi_lower: int = 15
# 风险资金
risk_capital: int = 1_000_000
parameters = [
"rrms_rsi_window",
"rrms_rsi_lower",
"rrms_rsi_upper",
"rrms_rsi_highest",
"ris_rsi_window",
"ris_rsi_entry",
"ris_ibs_entry",
"rs_rsi_window",
"rs_rsi_lower",
]
variables = []
def on_init(self):
"""
Callback when strategy is inited.
"""
self.write_log("策略初始化")
self.bg = BarGenerator(self.on_bar)
self.am = ArrayManager()
# 初始化信号生成器实例
self.rrms = RsiRangeMomSignal(
self.rrms_rsi_window,
self.rrms_rsi_lower,
self.rrms_rsi_upper,
self.rrms_rsi_highest
)
self.ris = RsiIbsSignal(
self.ris_rsi_window,
self.ris_rsi_entry,
self.ris_ibs_entry
)
self.rs = RsiSignal(
self.rs_rsi_window,
self.rs_rsi_lower,
)
self.load_bar(150)
def on_start(self):
"""
Callback when strategy is started.
"""
self.write_log("策略启动")
def on_stop(self):
"""
Callback when strategy is stopped.
"""
self.write_log("策略停止")
def on_bar(self, bar: BarData):
"""
Callback of new bar data update.
"""
# 撤销之前发出的委托
self.cancel_all()
# 对am更新bar数据
am = self.am
am.update_bar(bar)
if not am.inited:
return
# 计算开仓手数
trading_size = int(self.risk_capital / bar.close_price / 100) * 100
# 传入am,计算三个信号的值
rrms_signal = self.rrms.calculate_signal(am)
ris_signal = self.ris.calculate_signal(am)
rs_signal = self.rs.calculate_signal(am)
# 如果任一信号成立则进行开仓
if self.pos == 0:
if rrms_signal or ris_signal or rs_signal:
self.buy(bar.close_price*1.05, trading_size)
# 如果三个信号都不成立则进行平仓
if self.pos != 0:
if not rrms_signal and not ris_signal and not rs_signal:
self.sell(bar.close_price*0.95, abs(self.pos))
# 移动止损逻辑
elif self.pos > 0:
self.sell(bar.close_price*0.95, abs(self.pos), stop=True)
# 推送UI更新
self.put_event()
该策略历史回测需要用到的SPY历史数据,可以下载zip文件后解压,找到其中load_bar_data.py脚本文件,然后用Python运行即可自动导入数据库。
具体的回测参数配置如下:
本文选择的回测数据时间段中SPY整体处于长周期大牛市,因此多头逻辑的交易策略可能天然具有明显优势(毕竟简单买入做多就能赚钱),所以这里选择使用IF股指期货来作为策略有效性的交叉验证:
可以看出,RSI多信号集成策略在IF上的绩效也是相当可观的,虽然自2021年起策略的有效性变差了许多,但是仍然将回撤保持在了一个可控的范围,这也体现了CTA策略中多信号组合的优势。
本文代码中多次运用了numpy库中提供的向量化计算函数,例如【np.nan】、【np.mean】、【np.any】、【np.all】等。向量化计算是一种使用数组或矢量操作来处理数据的方法,它具有性能提升、代码简洁、可读性高、跨平台性,适用于大规模数据等优势。
完整策略代码和回测数据文件,可以通过【VeighNa进阶用户交流群】获取:
免责声明
文章中的信息或观点仅供参考,作者不对其准确性或完整性做出任何保证。读者应以其独立判断做出投资决策,作者不对因使用本报告的内容而引致的损失承担任何责任。
可以打印一下你收到的df
no_ui和图形界面不要一起启动
可以去确认一下,现在应该没有用这么老的版本的了。现在vnpy_ctptest对接的API已经到6.6.9了
如果要对接6.3.1.9的API,可以自己去github仓库下载vnpy_ctptest之前版本的源码自行编译了
elite版本不提供源代码,elite版本仿真模拟是免费的,社区论坛账号密码即可登录,elite版本实盘需要开通elite会员服务才可以用
elite版本功能介绍可以参考https://www.vnpy.com/forum/topic/31762-veighna-elite-mian-xiang-zhuan-ye-jiao-yi-yuan-de-liang-hua-zhong-duan
请问Exchange.INE和Exchange.GFEX对应的交易所字符串是?
方便的话可以去github发个PR,下个版本就能修复了
要看你合成最后一根K线的目的是什么了,如果只是想更新指标数值应该是可以的
可以检查一下注册的时候是否开了代理
发布于veighna社区公众号【vnpy-community】
原文作者:用Python的交易员 | 发布时间:2023-10-11
VeighNa全实战进阶期权系列的第三阶段《精研期权价差策略》正式上线!
这套课程差不多筹划了两年时间,核心原因在于期权策略回测真的很复杂(对比CTA策略来说要复杂得多),列举几个关键点:
为了解决这些问题我们开发了OptionStrategy期权策略模块,但底层需要依赖于【VeighNa 机构版】的服务端架构,对于个人交易员或者小型团队来说运维太过复杂。
截止今年三季度终于基本完成了OptionStrategy在Elite版上的移植工作,所以《精研期权价差策略》课程将会使用【VeighNa Elite版 仿真模拟】来讲解,带着大家由浅入深研究期权价差策略的开发、回测、优化的全流程,同时基于策略历史回测绩效来精研期权价差交易中的各种细节:
目前【VeighNa Elite版 仿真模拟】已经可以直接在官网下载,安装完成后使用社区论坛的账号密码登录即可(和VeighNa Station一样),仿真交易目前支持上期技术的SimNow环境,后续也计划接入更多其他仿真环境。
课程目前一共计划40节,内容大纲如下(黑体加粗课时为代码实践内容):
这门课程适合的人群:
课程当前已经上线,价格499元,前100名购买享受9折优惠(449元)。直接在【VeighNa开源量化】公众号(vnpy-community)里就能购买和观看(点击底部菜单栏的【进阶资料】进入)。推荐使用PC微信打开,视频分辨率更加清晰。
本线上课程包含在【Elite会员】免费学习权益内。
看报错是numpy版本的问题吧,.vntrader是要你启动成功才会自动创建的
安装包是统一的打包好的环境。你在自己的环境手动安装vnpy这一个包,会有和其他包版本冲突是正常的
可以自己过滤一下收到的非交易时段tick