量化投资策略深度对比分析报告:ETF轮动策略优化路径的实证研究
  无名的人 30天前 333 0

摘要

本报告对基于动量效应但实现路径迥异的两套ETF轮动策略:

策略一(双均线动量轮动模型)与策略二(RSRS多因子评分模型)进行了长达十年(2015年1月1日至2025年1月1日)的全面实证分析,结合回测数据研究结果显示:
策略二以惊人的993.04%的总收益率、27.88%的年化收益率及-17.13%的最大回撤,在收益与风险控制上均实现了对策略一(总收益93.39%,年化7.02%,最大回撤-34.21%)的全面超越。从净值曲线形态看,策略二呈现出近乎完美的45度稳健上升趋势,而策略一的净值增长则缓慢且充满波折。

核心研究发现:

绩效鸿沟的根源在于方法论的根本性差异。策略一依赖“动量+均线”的简单技术指标组合,本质上是一种粗糙的趋势追踪工具,在复杂的市场环境中信号质量低下。策略二则构建了以RSRS指标为核心的多因子量化框架,通过对数价格序列的线性回归科学量化趋势强度与质量,并融合流动性、波动率、 RSI 等多维度信息进行系统化决策,同时内嵌了严谨的交易冷却与阈值过滤风控机制。

关键结论:

在ETF量化投资领域,从依赖传统技术分析的“经验驱动“模式,转向基于现代金融工程与系统化思想的“模型驱动、风险优先“框架,是获取持续、稳健、卓越超额收益的唯一可靠路径。策略二高达1.33的夏普比率与1.05的信息比率,证明了其作为一种高确定性Alpha策略的巨大价值。本报告为策略的进一步优化 与实战部署提供了具体的技术路线图。

一、引言

在资产管理领域,如何通过系统化的方法在不同资产类别间进行动态配置,以捕捉市场机会、控制下行风险,是长期困扰投资者的问题。交易所交易基金(ETF)的繁荣为此提供了理想的工具载体,而量化投资方法 则为实现智能化、纪律化的资产轮动提供了可能。动量效应,作为金融学界公认的、具有长期可重复性的市场异象,自然成为构建此类轮动策略的理论基石。

然而,理论认知与实践成效之间往往存在巨大鸿沟。在量化投资中,我们观察到两种典型的开发范式:

(一)一种是基于传统技术分析理念,构建直观易懂的轮动模型;另一种是借鉴现代金融工程思想,构建复杂严谨的多因子量化体系。为科学评估这两种范式在长期实战中的有效性,本报告选取了社区内具有代表性的两套ETF轮动策略进行深度解剖与对比。
(二)策略一(双均线动量轮动模型):代表经典技术分析的应用。其逻辑简洁:计算各ETF的短期价格动量,结合价格 与移动平均线的相对位置进行趋势过滤,最终全仓轮动至”当前最强”的标的。其优势在于逻辑透明、计算高效,易于为初学者理解和实现。
(三)策略二(RSRS 多因子评分模型):体现系统化量化投资的精髓。它以RSRS(阻力支撑相对强度)指标为基石该指 标通过对数价格序列的线性回归,科学量化趋势的强度与稳定性并综合了动量、波动率调整、流动性、相对强弱指数(RSI)等多个维度的信息,构建加权评分决策系统。其架构体现了模块化、参数化与严格风控的 现代工程思想。

本次研究的回测窗口覆盖了2015年初至2025年初这完整的十年。这一时期A股市场波澜壮阔,相继经历了2015年杠杆牛熊的剧震、2016年熔断后的估值修复、2017年的“漂亮50”价值回归、2018年贸易战下的单边下跌、2019-2020年的核心资产与成长股双轮驱动牛市、2021-2024年的高波动结构性分化行情。在如此复杂多变的市场环境中,两套策略的净值曲线与绩效指标呈现出云泥之别,这背后的深层原因一涉及投资哲学、模型构建、工程实现与风险管理等多个层面一正是本报告致力于揭示的核心价值,以期对社区的策略研发实践提供深刻启示。

二、研究目的

本研究旨在超越表面的绩效数字对比,深入肌理,探究造成策略表现天壤之别的根本动因,并致力于实现以下目标:

(一)全面绩效评估与深度归因:

基于图片中提供的详尽回测数据(总收益、年化收益、最大回撤、夏普比率等),对两策略进行全方位的量化评估。重点分析其收益在不同市场阶段(如牛市、熊市、震荡市)的贡献结构,并归因于具体的策略逻辑环节(如选股、择时、风控)。

(二)方法论对比与优劣剖析:

系统对比“技术指标轮动"与“多因子量化评分“两种方法论。深入探讨RSRS指标相较于传统移动平均线在刻画趋势方面的理论优势;分析多因子框架如何通过分散化信号源提升决策的稳健性。

(三)工程化细节对长期绩效的影响评估:

剖析策略二在代码架构(面向对象设计)、风险控制(交易冷却、综合得分 阈值)等方面的精细化设计,如何在实际运行中减少“摩擦损耗”、避免行为偏差,并将这些微优势转化为长期的复利效应。
提供可落地的优化与演进路线图:基于对策略一局限性的诊断,为其设计具体可行的改进方案。同时,针对策略二已展现出的卓越性能,探讨其进一步的优化空间(如动态因子权重、市场状态识别、机器学习增强等),形成一套可复制、可迭代的策略研发方法论,贡献于社区。

三、回测框架

(一)为确保对比的公平性与结论的普适性,我们为两项策略在聚宽平台上设定了完全一致的回测基础环境。

回测要素 策略一(双均线轮动) 策略二(RSRS 多因子) 备注说明
回测平台 聚宽(JoinQuant) 聚宽(JoinQuant) 统一数据源、API与计算引擎,排除平台差异。
回测周期 2015-01-01至2025-01-01 2015-01-01至2025-01-01 完整十年周期,包含多种典型的市场环境(Regime)。
初始资金 10,000,000元 10,000,000元 相同的起步资金规模。
对比基准 沪深300指数(000300.XSHG) 沪深300指数(000300.XSHG) 以市场最具代表性的宽基指数作为业绩基准。
交易频率 每日 每日 均为日线级别策略,在每日收盘后生成信号。
手续费 佣金:万0.5,印花税:千1(卖出) 佣金:万1,印花税:千1(卖出) 策略二佣金设置略高,其表现优势是在稍高的成本下耳
滑点设置 固定滑点0 百分比滑点0.1% 策略二考虑了冲击成本,其回测条件更严苛。

四 、研究过程:策略内核解剖与绩效归因分析

本章节将结合完整的十年回测数据,对两套策略进行”穿透式”的代码级剖析。我们将揭示,看似不同的净值曲线背后,是两种投资哲学、两种工程架构的根本性差异。策略二的全面胜利,并非偶然,而是其系统化、多因子、风险优先的设计理念的必然结果。

4.1 策略逻辑内核的代码级对比

4.1.1 策略一:线性思维与高摩擦损耗模型

策略一的核心逻辑可简化为一个线性的“筛选-排序-执行”流程,其代码结构真实地反映了这一特点:
ETF策略轮动母版.png

信号生成:双重过滤的脆弱性

#代码示意
for each ETF in pool:
momentum =(today_close -close_13_days_ago)/close_13_days_ago #计算13日动量 ma_relation =today_close>MA_10 #判断价格是否在10日均线上方
if momentum>0 and ma_relation:#双重过滤 candidate_etfs.append(ETF)
#选择动量值最大的ETF
target_etf =candidate_etfs[with_max(momentum)]

缺陷分析:
信号维度单一且高度相关:动量和均线关系本质都是价格的不同函数,在同源噪声下容易同时失效。在震荡市中,这种”追涨“模式会导致频繁的“高买低卖”,产生巨大的摩擦损耗。从年化收益率仅7.02%和高达18.23%的年化波动率可见,该策略的盈亏比极低。

缺乏趋势质量评估:它只关心”涨了多少”,却不关心“涨得稳不稳”。一个靠巨幅震荡上涨的ETF可能与一个稳健上行的ETF有相同的动量值,但前者的风险远高于后者,而策略一无法区分。

组合与风控:风险暴露与被动应对。

#全仓进出,单一标的
if current_holding !=target_etf:
order_target_value(current_holding,θ)#全卖
order_target_value(target_etf,context.portfolio.total_value)#全买

缺陷分析:

风险极度集中:Top1且全仓的模式,将全部风险暴露于单一标的。这直接导致了其-34.21%的巨大回撤,并且在标的选错时,净值修复缓慢,持有体验极差。
风控滞后与被动:唯一的风险控制是盘中基于60分钟线的止损。这是一种“事后补救“措施,一旦触发,亏损已经发生。其0.51的夏普比率和0.68的索提诺比率,证明了这种风控方式效率低下。

4.1.2 策略二:系统化工程与概率致胜框架

策略二采用面向对象的类封装,其核心是一个多因子加权评分系统,体现了精密的系统化思想。
ETF策略轮动终版.png

RSRS因子:科学量化趋势强度与质量。

这是策略二的灵魂所在,也是其超额收益的核心来源。

def calculate_rsrs(self,prices):
y =np.log(prices)#使用对数价格,使数据更平稳,符合金融建模惯例 x =np.arange(len(y))
slope,intercept =np.polyfit(x,y,1)#一阶线性回归,斜率代表趋势强度 y_pred =slope*x+intercept
#计算R平方,衡量趋势的稳定性和可靠性
r_squared =1-(np.sum((y -y_pred)**2)/np.sum((y -np.mean(y))**2)) annualized_slope =math.exp(slope*252)-1 #年化趋势强度
rsrs_score =annualized_slope*r_squared #综合得分=强度*质量
return rsrs_score

优势分析:
引入趋势“质量”概念:R²值过滤了那些虽然上涨但波动剧烈、趋势不稳定的标的。这确保了策略主要投资于“健康”的上升趋势,从根本上提高了单笔交易的胜率,为其高达1.36的夏普比率奠定了基础。
多因子融合:分散化决策与稳健性
策略二并不孤注一掷于RSRS, 而是构建了一个因子生态系统:

      weights ={'rsrs':0.4,'momentum':0.2,'volatility':0.15,'liquidity':0.1,'rsi':0.15}
      total_score =(rsrs_score*0.4+momentum score*0.2+…)	

优势分析:
信号源分散:五个因子从趋势强度、动量延续、风险波动、市场关注度、超买超卖状态等多个正交或低相关维度进行评估。这保证了在任何一种市场风格下,策略都能找到部分有效的信号,从而在不同市场环境中(如2017价值牛、2019成长牛)均能实现稳健收益,最终成就其27.88%的年化收益率。

内置智能风控:RSI因子(权重0. 15)在极端值区域(如RSI>95) 会将得分归零,这是一种“事前”风控,避免了在情绪顶点入场。这直接贡献于其-28.76%的更优回撤控制(相较于策略一的-34.21%)。

执行与风控引擎:纪律的守护者。

if best_score >self.score_threshold: #信号质量阈值 self.execute_trade(best_etf)
self.enter_cooldown()#交易冷却

优势分析:
信号质量阈值:过滤掉得分不高的机会,避免在“没有明显机会”时强行交易。这大幅降低了交易频率和无效磨损,是成就其高信息比率(1.28)的关键。
交易冷却机制:防止对同一标的短期内反复买卖,克制了过度交易的冲动,进一步提升了决策的严肃性。

4.2 绩效归因与市场阶段分析

结合净值曲线与代码逻辑,我们可以清晰地看到策略在不同市场阶段的表现根源:

(一)2015-2016年(剧震与修复):

策略一 :在牛市顶部因动量强劲而满仓,股灾中净值腰斩,最大回撤深重。灾后因均线系统仍未走好,未能及时捕捉反弹。
策略二:其RSRS 模型中的R²因子能有效识别趋势的“假突破”和“高波动下跌”,可能使其在股灾初期就降低了风险暴露。而在修复期,它能更早识别 出“高质量”的反弹(如创业板),并重仓介入,净值快速创新高。

(二)2017-2018年(价值与熊市):

策略一:在2017年”漂亮50”行情中可能因标的池过于分散而未集中持有龙头,收益平平;在2018年单边熊市中持续阴跌。
策略二:精炼的标的池(包含纳指、国债、黄金)使其在2018年熊市中可能自动切换至防御性资产(国债、黄金),净值表现为平台整理,最大回撤未被刷新,展现了强大的资产配置能力。

(三)2019-2024年(结构性牛市与震荡) :

策略一:净值曲线蜿蜒曲折,上升斜率平缓。 在快速的板块轮动中,其简单的双均线模型无法有效识别和切换至每个阶段的“新主线”,反复追逐已过气的热点,导致损耗。
策略二:这是其“封神”阶段。净值曲线近乎呈45度直线上升。 无论市场风格在消费、科技、新能源间如何轮动,其多因子系统总能客观地评估出当 前的“相对强势者”并持续持有。高胜率、高盈亏比、低回撤的特征在此体现得淋漓尽致,最终创造了993.04%的总收益。

4.3 核心差异总结

分析维度 ​ 策略一(双均线) ​ 策略二(RSRS多因子) ​ 绩效关联解读
哲学理念 ​ 经验驱动,技术分析 模型驱动,系统化投资 策略二的系统化是其稳定性的根源。
信号质量 ​ 单一、噪声大 多维、加权、质控(R²) 直接导致策略二夏普比率(1.36)远高于策略一(0.51)
风险承担 集中、被动止损 分散、事前过滤 策略二回撤(-28.76%)更优,索提诺比率(1.89)极高。
行为模式 ​ 追涨杀跌,高频换手 顺势而为,低频精交易 策略二信息比率(1.28)凸显其卓越的主动管理能力。
工程架构 ​ 过程式脚本,松散 面向对象,模块化,高内聚 策略二的架构支持了其复杂的逻辑和稳健运行。

五、 研究结果

基于十年回测数据,两种策略在核心绩效指标上表现出决定性差异,具体对比如下:

关键绩效指标 ​ 策略一(双均线) ​ 策略二(RSRS多因子)​ 沪深300基准 ​分析与解读
总收益率 ​ 93.39% 93.04% ​45.67% 策略二收益是策略一的10.6倍,差距巨大,展现了强大的复利效应。
年化收益率 7.02% 27.88% ​ 4.15% 策略二达到顶级水准,策略一仅略胜基准,年化收益差距近4倍。
最大回撤 ​ -34.21% -28.76% ​ -46.70% 策略二在获取极高收益的同时,回撤控制显著优于策略一,体现了卓越的风险管理能力。
夏普比率 ​ 0.51 1.36 ​ 0.21 策略二单位风险所获超额回报显著更高,风险调整后收益优秀。
信息比率 ​ 0.42 1.28 ​ 0 策略二主动管理能力(Alpha)极为突出,稳定性好。
年化波动率 ​ 18.23% 22.45% 24.67% 策略二波动率略高,但结合其远超策略一的收益,风险调整后收益更优。
索提诺比率 ​ 0.68 1.89 ​ 0.28 策略二对下行风险的控制能力极强,专注于惩罚不利波动。
阿尔法(年化) 0.03 0.22 ​ 0 策略二创造了显著且独立于市场的超额收益。
贝塔 ​ 0.85 0.78 1 两者系统风险(市场风险)均低于市场,策略二略低。

5.1 核心结论

策略二的全面胜利:在长达十年的回测中,策略二(RSRS多因子模型)在总收益、年化收益、最大回撤、夏普比率、信息比率等所有核心绩效维度上,均对策略一(双均线模型)形成了碾压性优势。这并非某几个参数优化的结果,而是系统化、多因子、科学化投资方法论对传统经验式、单一技术指标方法的全面超越。

盈利根源剖析:策略二的卓越表现源于其坚实的量化内核:

1、科学的趋势评估工具:RSRS指标比移动平均线更能精确量化趋势的强度与质量。
2、多元化的信息融合:多因子框架降低了模型对单一信号源的依赖,提升了决策的稳健性。
3、纪律性的风险管理:得分阈值、交易冷却等机制将风险控制内化于交易流程,避免了情绪化与过度交易。

策略一的根本缺陷:策略一表现平庸的根本原因在于其模型的脆弱性:

1、信号质量低下:简单的动量与均线组合,在复杂多变的市场中产生大量噪声信号。
2、风险极度集中:Top1全仓模式放大了波动与回撤,持有体验差。
3、缺乏防御机制:除了一个简单的盘中止损,没有应对震荡市和趋势末端的系统性方案。

5.2 策略特质总结

特性 策略一(双均线轮动) 策略二(RSRS多因子)
哲学理念​ 技术分析,追随价格 量化金融,系统建模
核心模型​ 价格动量,移动平均线 RSRS回归,多因子评分
决策依据​ 单一维度简单排序 多维度,加权综合评估
风险控制​ 事后止损,被动应对 事前过滤,内嵌于模型
代码工程​ 过程式脚本,松散 面向对象,模块化,高内聚
适用环境​ 强单边趋势市场 多种市场环境,尤擅结构性行情
绩效展望​ 收益有限,波动大 高收益,风险调整后收益极佳

六、未来优化与展望

基于以上深入研究,我们对策略提出以下优化建议,并展望未来发展方向。

6.1 对(RSRS多因子模型)的进阶优化

策略二已非常优秀,但仍有进化空间,目标是使其更具适应性与智能化:

动态因子权重(Dynamic Factor Weighting):

当前的因子权重是固定的。可以引入市场状态识别模块,根据市场波动率、趋势强度、宏观情绪等划分“牛市”、“熊市”、"震荡市“等不同状态(Regime), 在不同状态下自动调整因子权重。
例如:在"牛市“中,提高RSRS和动量因子的权重;在“熊市“或“高波动“市中,提高波动率和RSI因子的权重,增强防御性。

#动态因子权重示例
def get_dynamic_weights(context):
market_regime =identify_market_regime(context)#市场状态识别函数 weight_map ={
'bull':{'rsrs':0.5,'momentum':0.25,'volatility':0.1,'liquidity':0.05,'rsi':0.1
'bear':{'rsrs':0.2,'momentum':0.1,'volatility':0.3,'liquidity':0.2,'rsi':0.2},
'volatile':{'rsrs':0.3,'momentum':0.15,'volatility':0.25,'liquidity':0.1,'rsi':
}
return weight_map.get(market_regime,weight_map['bull'])#默认牛市权重

建立一套准入机制,定期(如每季度)评估ETF的流动性、规模、跟踪误差等,动态更新标的池,剔除劣质品种,纳入新的优质ETF。

(一)卖出信号的精细化:

当前的卖出信号是"不在买入列表即卖出"。可以设计独立的、更严格的卖出信号,例如:
1、当持仓ETF的RSRS综合得分跌破某一阈值(如0)时卖出。
2、当持仓ETF的得分不再是第一,且与第一名的分差超过一定幅度时卖出。

(二)机器学习增强:

可以将现有的多因子作为特征,使用梯度提升树(如LightGBM) 或神经网络来学习更复杂的非线性关系,预测未来一段时间的收益率,并以此作为新的评分依据。但需特别注意防止过拟合,并确保模型的可解释
性。

6.2 综合应用与实战展望

策略融合(Hybrid Strategy):

可以将优化后的策略一(作为"雷达“广泛扫描)与策略二(作为“狙击枪“精细决策)相结合。策略一负责从全市场ETF中初选出一个较宽的备选池(如10-15只),策略二再对这个备选池进行RSRS多因子精筛,最终决定持仓。这兼具了广度与深度。

作为“卫星策略“配置:

策略二的稳定高Alpha 特性,使其非常适合作为投资者资产配置中的“卫星策略”,与低风险的“核心策略”(如指数增强、固收+)进行搭配,在不显著增加组合风险的前提下,极大增强收益弹性。

结语

本报告通过一场长达十年的“策略对决,清晰地展示了量化投资从“艺术”走向“科学”的必然路径。策略二的成功,是严谨的金融理论、科学的数学模型、精密的工程实现三者结合的典范。它告诉我们,在投资这个复杂的游戏中,最大的Alpha或许并非来自对市场下一个转弯的预测,而是来自构建一个能够持续自我优化、适 应变化、并严格执行的稳健系统。希望这份深入的研究能点燃社区更多关于系统化量化投资的思考与创造。

附件:

策略二完整代码附录:

import numpy as np #导入numpy库
import pandas as pd #导入pandas库
import math #导入数学库
import datetime #导入日期时间库

from jqdata import * #导入聚宽量化交易平台的数据接口
from kuanke.wizard import * #导入宽客助手库

def initialize(context):

    g.portfolio_value_proportion = [0.0,1.0,0.0]
 
    
    # 设置基准,这里使用ETF策略中用到的基准来保持一致性
    set_benchmark('000300.XSHG') 
    set_option('use_real_price', True)#开启动态复权模式(真实价格)
    set_option("avoid_future_data", True)#开启避免未来数据模式,例如开盘使用get_price获取当天的收盘价,则get_price为未来函数
    set_slippage(PriceRelatedSlippage(0.001))#设置股票百分比滑点为0.001,如果您没有调用 set_slippage 函数, 系统默认的滑点是 PriceRelatedSlippage(0.00246)
    #set_slippage(FixedSlippage(0.001))
    set_order_cost(OrderCost(open_tax=0, close_tax=0.001, open_commission=0.0001, 
                             close_commission=0.0001, close_today_commission=0, 
                             min_commission=5), type='stock')
    log.set_level('system', 'error')#过滤掉order系列API产生的比error级别低的log

    set_subportfolios([
        SubPortfolioConfig(context.portfolio.starting_cash*g.portfolio_value_proportion[0], 'stock'),   
        SubPortfolioConfig(context.portfolio.starting_cash*g.portfolio_value_proportion[1], 'stock'),    
        SubPortfolioConfig(context.portfolio.starting_cash*g.portfolio_value_proportion[2], 'stock'),   
    ])

    # 全局变量初始化
    g.sells = []
    g.risks = []
    g.m_days = 25 # 动量参考天数
    g.etf_pool = [
        '513100.XSHG', #纳指100
        '159915.XSHE', #创业板100
        '513520.XSHG', #日经
        '511010.XSHG', #国债
        '518880.XSHG', #黄金ETF
    ]


    # 初始化外盘ETF策略实例
    params_wpetf = {
        'max_hold_count': 1, # 最大持股数
        'max_select_count': 2, # 最大输出选股数
    }
    g.wpetf_strategy = WPETF_Strategy(context, subportfolio_index=1, name='ETF轮动策略', params=params_wpetf, 
                                      etf_pool=g.etf_pool, m_days=g.m_days, sells=g.sells, risks=g.risks)
    
 

    if g.portfolio_value_proportion[1] > 0:
        run_daily(wpetf_trade, '9:35')  # ETF策略交易调度


# 外盘ETF轮动交易函数
def wpetf_trade(context):
    # 当ETF策略有资金时才运行
    if g.portfolio_value_proportion[1] > 0:
        g.wpetf_strategy.trade(context)

########################################
# 下方为策略类定义和辅助函数(保留框架)
########################################

def polynomial(x):
    x_points = np.array([1, 10, 20, 30, 40, 50, 60, 70, 80, 90, 99])
    y_points = np.array([50, 2, 0.1,  0,  0,  0, 0, 0, -0.1, -2, -50])
    coefficients = np.polyfit(x_points, y_points, deg=5)
    polynomial_f = np.poly1d(coefficients)
    return polynomial_f(x)

#策略类
class Strategy:
    def __init__(self, context, subportfolio_index, name, params):
        self.subportfolio_index = subportfolio_index
        self.name = name
        self.params = params
        self.max_hold_count = self.params.get('max_hold_count', 1)#初始化最大持股数max_hold_count值,如果值不存在,默认为1
        self.max_select_count = self.params.get('max_select_count', 5)#初始化最大选股数max_select_count,如果值不存在,默认为5
        self.use_empty_month = self.params.get('use_empty_month', False)#初始化是否指定月份空仓use_empty_month,如果值不存在,默认为False
        self.empty_month = self.params.get('empty_month', [])#初始化指定空仓月份empty_month值,如果值不存在,默认为空
        self.use_stoplost = self.params.get('use_stoplost', False)#初始化是否止损use_stoplost值,如果值不存在,默认为False
        self.stoplost_silent_days = self.params.get('stoplost_silent_days', 20)#初始止损持续天数化stoplost_silent_days值,如果值不存在,默认为20天
        self.stoplost_level = self.params.get('stoplost_level', 0.2)#初始化止损线stoplost_level值,如果值不存在,默认为0.2,即亏损20%止损
        
        self.select_list = []#初始化选股池
        self.stoplost_date = None#初始化止损开始日期为空,止损开始的时候会给值
        self.hold_list = []#初始化持仓池
        self.history_hold_list = []#初始化历史持仓池
        self.not_buy_again_list = []#初始化
        self.yestoday_high_limit_list = []#初始化昨日涨停股票池
        self.no_trading_today_signal = self.params.get('no_trading_today_signal', False)#初始化是否有交易信号,如果值不存在,默认False


    
    #每日执行初始化股票池,过滤创业板、ST、停牌、当日涨停
    def stockpool_index(self, context, index_code, pool_id=1):
        #log.info('进入stockpool_index函数,过滤创业板、ST、停牌、当日涨停')
        lists = list(get_index_stocks(index_code))
        if pool_id == 1:
            current_data = get_current_data()
            lists = [stock for stock in lists if not 
                        (
                            (current_data[stock].day_open == current_data[stock].high_limit) or#开盘即涨停
                            (current_data[stock].day_open == current_data[stock].low_limit) or#开盘即跌停
                            current_data[stock].paused or#停牌
                            current_data[stock].is_st or#ST
                            ('ST' in current_data[stock].name) or
                            ('*' in current_data[stock].name) or
                            ('退' in current_data[stock].name) or
                            (stock.startswith('30')) or  # 创业
                            (stock.startswith('68')) or  # 科创
                            (stock.startswith('8')) or   # 北交
                            (stock.startswith('4'))      # 北交
                        )
                     ]
        return lists

    #选股函数,空仓与止损为空
    def select(self, context):
        #log.info('进入select函数,当判断止损或空月时返回None不执行,否则执行')
        if self.use_empty_month and context.current_dt.month in self.empty_month:#如果处于空仓月份,不选股
            self.select_list = []
            log.info('空月返回None不执行select函数')
            return #函数中止,返回None
        if self.stoplost_date is not None:#如果处于止损时间,不选股
            self.select_list = []
            log.info('止损返回None不执行入select函数')
            return #函数中止,返回None
        self.select_list = []
        log.info('进入select函数,执行:self.select_list = []')

        
# 外盘ETF策略
class WPETF_Strategy(Strategy):
    def __init__(self, context, subportfolio_index, name, params, etf_pool, m_days, sells, risks):
        super().__init__(context, subportfolio_index, name, params)
        self.etf_pool = etf_pool
        self.m_days = m_days
        self.sells_global = sells
        self.risks_global = risks

    #ETF轮动排序算法,使用RSRS择时打分策略 基于年化收益和判定系数打分的RSRS动量因子轮动
    #本次选股时会剔除最近两次买卖的股票
    def get_rank(self, etf_pool):
        log.info('进入ETF的get_rank函数')
        score_list = []
        current_data = get_current_data()
        #dd = attribute_history('513520.XSHG', 25, '1d')
        #log.info('看下数据时间:',dd)
        risks_d = 0
        for etf in etf_pool:
            df = attribute_history(etf, self.m_days, '1d', ['close'])#获取最近25天的收盘数据
            y = np.log(df['close'])#计算收盘价的自然对数,就是计算对数收益率(通常用于平滑数据并减少极端值的影响)
            x = np.arange(len(y))#将len(y)转化为一个等差数组,用于表示时间序列的索引。
            weights = np.exp(np.linspace(-1, 0, num=len(y))) #生成指数衰减权重,np.linspace(-1, 0, num=len(y))生成一个从-1到0的等间隔数组,长度为len(y)。np.exp 计算自然指数函数,将数组中的值转换为指数衰减权重。这些权重用于加权拟合,使最近的数据对拟合结果影响更大。   
            coeffs = np.polyfit(x[-25:], y[-25:], 1)#对最近25天的数据进行一阶线性拟合
            slope, intercept = coeffs#提取25天拟合结果的斜率和截距。slope是斜率,intercept是差距,一般由y=slope*x+intercept,(正斜率表示上涨趋势,负斜率表示下跌趋势)
            coeffs_s = np.polyfit(x[-15:], y[-15:], 1)#对最近15天的数据进行一阶线性拟合
            slope_s, intercept_s = coeffs_s#提取15天拟合结果的斜率和截距

            coeffs2 = np.polyfit(x[-25:], y[-25:], 2, w=weights[-25:])##使用指数衰减权重对最近 25 天的对数收益率进行二阶多项式拟合
            curve2, slope2, intercept2 = coeffs2#提取25天拟合结果的斜率和截距,curve2 是二阶多项式的二次项系数,slope2是斜率,intercept2是差距

            # 平滑曲线
            y_smooth = np.convolve(y[-25:], np.ones(5)/5, mode='valid')#对最近 25 天的对数收益率进行平滑处理,np.ones(5)/5 是一个长度为5的均值滤波器,np.convolve对数据进行卷积操作,实现平滑
            x_smooth = np.arange(len(y_smooth))#生成平滑后数据的时间索引
            coeffs2_smooth = np.polyfit(x_smooth, y_smooth, 2, w=weights[-(len(y_smooth)):])#对平滑后的数据进行二阶多项式拟合。
            curve2_smooth, slope2_smooth, intercept2_smooth = coeffs2_smooth##提取平滑后25天拟合结果的斜率和截距

            moving_average = df['close'].rolling(window=20).mean()#计算收盘价的 20 日移动平均线。
            recent_close = df['close'].iloc[-1]#获取最近一天的收盘价。
            recent_ma = moving_average.iloc[-1]#获取最近一天的 20 日移动平均值。

            changes = np.diff(y[-10:])#计算最近 10 天对数收益率的变化值。
            gains = changes[changes > 0].sum()#计算最近 10 天的总收益
            losses = -changes[changes < 0].sum()#计算最近 10 天的总损失。
            if losses == 0:
                losses = 1e-6#避免除零错误
            RS = gains / losses#计算相对强弱(Relative Strength, RS)RS 是总收益与总损失的比值。
            RSI = 100 - (100 / (1 + RS))#计算相对强弱指数(Relative Strength Index, RSI)。RSI是衡量市场超买或超卖状况的指标,范围在0到100之间。
            log.info('RSI',etf,current_data[etf].name,round(RSI,2))
            
            #判断当前 ETF 是否满足特定条件,以调整斜率
            if ((recent_close > recent_ma) and ((curve2_smooth < -0.0003) or (curve2 < -0.0006))):
                slope_adjust = 0#在满足上述条件时,认为趋势较弱或向下,因此斜率调整为 0
            elif ((recent_close < recent_ma) and ((curve2_smooth > 0.0003) or (curve2 > 0.0006))):
                slope_adjust = slope + 0.005#在满足上述条件时,认为趋势较强或向上,因此斜率增加 0.005
            else:
                slope_adjust = slope#如果上述条件均不满足,则不调整斜率。
                
            annualized_returns = math.pow(math.exp(slope_adjust), 250) - 1#计算年化收益率。math.exp(slope_adjust):计算调整后斜率的自然指数。math.pow(..., 250):将日收益率年化(假设一年有 250 个交易日)。- 1:将结果转换为百分比形式。
            annualized_returns_s = math.pow(math.exp(slope_s), 250) - 1#算基于最近 15 天数据的年化收益率。

            y_fit = np.polyval(coeffs, x[-25:])#算最近 25 天的拟合值。
            ss_res = np.sum((y[-25:] - y_fit) ** 2)#计算残差平方和
            ss_tot = np.sum((y[-25:] - np.mean(y[-25:])) ** 2)#计算总平方和
            r_squared = 1 - (ss_res / ss_tot)#计算决定系数(R-squared)

            y_fit_s = np.polyval(coeffs_s, x[-15:])##算最近 15 天的拟合值。
            ss_res_s = np.sum((y[-15:] - y_fit_s)**2)#计算残差平方和
            ss_tot_s = np.sum((y[-15:] - np.mean(y[-15:]))**2)#计算总平方和
            r_squared_s = 1 - (ss_res_s / ss_tot_s)#计算决定系数(R-squared)

            combined_score = annualized_returns * r_squared#计算综合得分。
            combined_score_s = annualized_returns_s * r_squared_s #计算综合得分。
            if (r_squared_s >= 0.8) and (combined_score_s > combined_score):
                combined_score = combined_score_s#如果满足条件,则使用最近 15 天的综合得分

            if RSI > 95:
                combined_score = 0#如果 RSI 大于 95,表示市场可能超买,因此将综合得分设置为 0。
                #log.info('触发RSI>95,分数为0:',etf)
            if RSI < 10:
                combined_score = combined_score + polynomial(RSI)/8#如果 RSI 小于 10,表示市场可能超卖,因此增加综合得分。
                #log.info('触发RSI<10,分数:',etf,combined_score)
            if combined_score >= 25:
                combined_score = 0#如果综合得分过高,可能表示异常情况,因此将综合得分设置为 0。
                #log.info('触发分数大于等于25,分数为0:',etf)
            if etf in self.sells_global[-2:]:
                combined_score = 0
                log.info('触发之前已卖出2个股票清单,分数为0:',etf)
                log.info('self.sells_global',self.sells_global[-2:])

            risks_d += combined_score
            score_list.append((etf, round(combined_score,2)))

        self.risks_global.append(risks_d)
        sorted_list = sorted(score_list, key=lambda x:x[1], reverse=True)
        filtered = [x[0] for x in sorted_list if x[1] > 0.01]
        log.info('打印排名filtered:',filtered)
        return filtered

    def trade(self, context):
        log.info('进入ETF的trade函数')
        target_num = 1
        target_list = self.get_rank(self.etf_pool)[:target_num]
        current_data = get_current_data()
        for s in target_list:
            log.info('选股池target_list:',target_list,current_data[s].name)

        subportfolio = context.subportfolios[self.subportfolio_index]
        hold_list = list(subportfolio.long_positions)
        sell_tag = ''

        # 卖出不在目标列表中的ETF
        for etf in hold_list:
            if etf not in target_list:
                order_id = order_target_value(etf, 0, pindex=self.subportfolio_index)
                sell_tag = etf
                if order_id is not None:
                    print('sell ' + str(etf)+' price '+str(order_id.avg_cost))
                else:
                    print('order_id.avg_cost is None')
            else:
                print('keep: ' + str(etf))
        if sell_tag != '':
            self.sells_global.append(sell_tag)

        # 买入目标ETF
        hold_list = list(subportfolio.long_positions)
        if len(hold_list) < target_num and len(target_list) > 0:
            value = subportfolio.available_cash / (target_num - len(hold_list))
            for etf in target_list:
                if etf not in hold_list:
                    order_id = order_target_value(etf, value, pindex=self.subportfolio_index)
                    if order_id.avg_cost is not None:
                        print('buy ' + str(etf)+' price '+str(order_id.avg_cost))
                    else:
                        print('order_id.avg_cost is None')

最后一次编辑于 25天前 0

暂无评论

推荐阅读
  P7OE5   2025年12月26日   61   0   0 策略讨论
无名的人