发布于veighna社区公众号【vnpy-community】
 
原文作者:用Python的交易员 | 发布时间:2023-3-1
 

失踪系列回归

 

VeighNa平台面向专业量化交易用户的Elite(菁英版)从2022年10月开始内测以来,经过5个月时间的迭代目前已经到了0.9.0版本,预计将在3月底之前正式上线发布。

前期VeighNa Elite在开发上主要专注于交易系统性能方面的优化,如:

  • 低延时事件引擎:实现更高速率的系统事件处理;
  • 前后端分离架构:分离前端UI界面的运行时开销;
  • 多进程策略并发:突破GIL带来的CPU单核算力上限。

后续根据内测用户的反馈,开始增加交易和策略相关的功能扩展,交易方面的内容包括:市场深度委托、多账户组合交易、标准化执行算法等。策略方面推出的则是更加针对量化私募机构策略开发需求的EliteCtaStrategy和ElitePortfolioStrategy模块。

翻了下公众号的历史记录,【VeighNa量化策略实验室】系列的上一篇文章更新已经是2021年9月的事情。时隔一年半再次回归,升级为基于VeighNa Elite版的【Elite量化策略实验室】,同样希望能够在量化策略研究方面给大家带来更多的灵感。

本文分享的RUMI策略来自于VeighNa社区用户推荐的互联网资料,原作者已经很难确定,甚至我们连RUMI具体是什么的缩写也不清楚(如果有知道的欢迎评论区留言)。

为了吊起大家的胃口,先来看一下螺纹钢期货上经过各种细节优化的结果:

description

description

 

策略基本信息

 

description

 

策略核心原理

 

移动平均线(Moving Average)作为一种大家非常熟悉的技术指标,由于其非平稳性时间序列的特征(即均值并不围绕固定中枢波动),本身并不适合作为CTA策略的核心信号使用。

而简单的使用双均线金死叉作为交易信号,尽管能够在大趋势中捕捉相当一部分行情,但在均线缠绕出现的震荡类行情中则会遭遇来回打脸的问题。

因此RUMI策略采用了对均线偏离度平滑处理的方式,来更好的识别趋势机会:

  • 快速均线:使用SMA(简单移动平均)计算
  • 慢速均线:使用WMA(加权移动平均)计算
  • DIFF:计算快慢均线的偏离度(两者相减)
  • RUMI:对偏离度使用SMA进行平滑处理

有了RUMI指标后,交易信号则是十分简单:

  • RUMI上穿0轴,买入做多
  • RUMI下穿0轴,卖出做空

看到这里不难发现,本质上RUMI策略是对传统均线金死叉(即DIFF上下穿0轴)信号的一种优化,通过平滑处理的方式降低DIFF的敏感度来解决均线缠绕的问题。

 

策略代码实现

 

策略参数定义

本文中的RumiStrategy采用EliteCtaStrategy模块下的策略模板类EliteCtaTemplate开发,用户可以直接通过基础参数来配置策略运行的K线数据周期(无需再创建和维护BarGenerator进行合成):

class RumiStrategy(EliteCtaTemplate):
    """RUMI策略"""

    author = "VeighNa菁英版"

    # 基础参数(必填)
    bar_window: int = Parameter(1)              # K线窗口
    bar_interval: int = Parameter("1m")         # K线级别
    bar_buffer: int = Parameter(100)            # K线缓存

在定义策略的参数和变量时,可以分别使用Parameter和Variable辅助类来创建,无需再手动添加字段名称到parameters和variables列表中:

# 策略参数(可选)
    fast_window: int = Parameter(3)             # 快速均线窗口
    slow_window: int = Parameter(50)            # 慢速均线窗口
    rumi_window: int = Parameter(30)            # 均线偏差窗口
    max_holding: int = Parameter(100)           # 持仓周期上限
    stop_percent: float = Parameter(0.03)       # 保守止损比例
    risk_window: int = Parameter(10)            # 风险计算窗口
    risk_capital: int = Parameter(1_000_000)    # 交易风险投入
    price_add: int = Parameter(5)               # 委托下单超价

    # 策略变量
    trading_size: int = Variable(1)             # 当前委托数量
    rumi_0: float = Variable(0.0)               # RUMI当前数值
    rumi_1: float = Variable(0.0)               # RUMI上期数值

 

信号指标计算

EliteCtaTemplate提供了新的on_history回调函数,用于推送已经合成完毕且缓存为时间序列的K线数据容器HistoryManager:

def on_history(self, hm: HistoryManager) -> None:
    """K线推送"""
    # 计算均线数组
    fast_array: ndarray = sma(hm.close, self.fast_window)
    slow_array: ndarray = wma(hm.close, self.slow_window)

    # 计算均线差值
    diff_array: ndarray = fast_array - slow_array
    rumi_array: ndarray = sma(diff_array, self.rumi_window)
    self.rumi_0 = rumi_array[-1]
    self.rumi_1 = rumi_array[-2]

    # 判断上下穿
    long_signal: bool = cross_over(rumi_array, 0)
    short_signal: bool = cross_below(rumi_array, 0)

由于采用了连续内存区域分配,HistoryManager可以提供比开源版ArrayManager更快的数据更新和计算效率,且支持快速转换为Pandas的DataFrame对象。当用户收到on_history推送时,HistoryManager对象中的K线缓存数据固定为bar_buffer长度,用户无需再额外进行条件判断检查数据满足长度要求。

除了封装TA-Lib中的指标计算函数外,EliteCtaStrategy模块中还额外提供了类似cross_over/cross_below的常用条件判断和逻辑运算函数。

 

动态仓位计算

根据市场波动对交易仓位进行动态管理的技术,能够有效平滑资金曲线波动和提高整体夏普比率,随着海龟策略体系的普及在CTA策略中被广泛应用(具体细节在《超越海龟策略精析》课程中有详细讲解)。

# 计算交易数量
self.trading_size = self.calculate_volume(self.risk_capital, self.risk_window, 1000, 1)

这里通过调用EliteCtaTemplate所提供的calculate_volume函数,传入交易风险资金risk_capital、风险评估窗口risk_window、委托数量上下限(1000、1)参数,快速计算当前市场情况下合适的交易委托数量。

 

目标交易执行

考虑到机构投资者普遍金额和数量较大的交易执行需求,由用户在策略中直接实现具体的买卖委托操作可能较为麻烦,因此EliteCtaTemplate提供了目标仓位交易执行功能:

# 获取当前目标
last_target: int = self.get_target()

# 初始化新一轮目标(默认不变)
new_target: int = last_target

# 执行开仓信号
if long_signal:
    new_target = self.trading_size
elif short_signal:
    new_target = -self.trading_size

# 持仓时间平仓
if self.bar_since_entry() >= self.max_holding:
    new_target = 0

# 保护止损平仓
close_price = hm.close[-1]

if last_target > 0:
    stop_price: float = self.long_average_price() * (1 - self.stop_percent)
    if close_price <= stop_price:
        new_target = 0
elif last_target < 0:
    stop_price: float = self.short_average_price() * (1 + self.stop_percent)
    if close_price >= stop_price:
        new_target = 0

# 设置新一轮目标
self.set_target(new_target)

# 执行目标交易
self.execute_trading(self.price_add)

# 推送UI更新
self.put_event()

在上文代码中,交易执行的步骤可以分解为:

  1. 通过get_target函数,获取策略当前T时刻的目标仓位数值last_target;
  2. 结合策略核心逻辑判断,计算生成新的目标仓位数值new_target;
  3. 调用set_target函数,设置策略T+1时刻的目标仓位数值;
  4. 调用execute_trading函数,计算目标持仓target和实际持仓pos的差值执行委托交易;

同时除了RUMI策略本身的多空交易逻辑外,我们出于实盘交易中的谨慎风险管理原则,额外添加了两块平仓出场逻辑:

  1. 总持仓时间限制的出场:

    a. 使用bar_since_entry函数,获取开仓以来经过的K线总数量;
    b. 当总数量超过一个上限值,认为趋势已经衰减结束,平仓离场;

  2. 保护性止损位平仓出场:

    a. 在多空持仓场景下,分别使用long_average_price和short_average_price函数获取开仓成本价;
    b. 以做多为例:当价格跌破多头开仓成本价一个固定百分比,则止损离场

在实盘交易中,目标交易执行模式的主要优势包括:

  • 策略逻辑围绕目标持仓展开,开盘初始化时可以直接通过历史数据回放来恢复正确的策略状态,无需再依赖昨日收盘时的策略变量缓存(如基于实际仓位跟踪的移动止损高低点),减少在实盘运维中的出错风险;
  • 用户不再需要为繁琐的委托下单代码头疼(尤其是某些比较复杂策略中的交易状态机管理),当执行数量较大时可以由策略引擎自动调用平台内置的执行算法(TWAP、Sniper、BestLimit等)来进行拆单交易,降低整体的交易成本。

 

回测结果

 

回测数据上,本文中选择使用米筐RQData提供的rb99连续指数合约数据(rb888连续主力合约数据由于长期的换月升贴水平滑,较早时期的价格已变为负数),在后续的篇幅中我们会尝试更多的品种,回测配置如下:

  • 本地代码:rb99.SHFE
  • K线周期:1分钟
  • 开始日期:2010-1-1
  • 结束日期:2023-2-3
  • 手续费率:0.0001
  • 交易滑点:1
  • 合约乘数:10
  • 价格跳动:1
  • 回测资金:1000W

作为原始版本策略的源码复现,初步回测结果勉强过得去:

description

策略回测的关键统计结果:

  • 总交易日:3226
  • 盈利交易日:1425
  • 总收益率:338.81%
  • 年化收益:25.21%
  • 百分比最大回撤:-32.80%

下一篇文章中,将会详细讲解如何使用EliteCtaStrategy实现基于稳健统计指标R-Cubed的参数优化,以及分享RumiStrategy在更多品种上的回测绩效。

 

当前VeighNa Elite版仍处于免费内测阶段,内测账号可以通过提供量化私募机构的投研人员名片申请,感兴趣的同学请扫码添加小助手:

description

 

免责声明

文章中的信息或观点仅供参考,作者不对其准确性或完整性做出任何保证。读者应以其独立判断做出投资决策,作者不对因使用本报告的内容而引致的损失承担任何责任。