因子扩展及策略应用案例(Tradingview平台 Dema Adjusted ATR及Rainbow Adaptive RSI )
-
近期,我在 TradingView 平台上探索了“彩虹波动交易法”策略,基于现有移植的c#平台,尝试扩展新的因子并设置toml策略进行应用,针对创业板 ETF(159915)进行了优化与初步回测,分享以下经验与成果。
新因子:DEMA Adjusted ATR 和 Rainbow Adaptive RSI
因子背景与设计
• DEMA Adjusted ATR:通过双指数移动平均调整的平均真实波幅,衡量价格波动强度。绿色信号(上穿基线)用于买入,红色信号(下穿基线)用于卖出。
• Rainbow Adaptive RSI:结合多周期自适应相对强弱指数,绿色线条(中间周期)确认上升趋势,红色线条(长周期)确认下降趋势。
策略优化过程
初始配置复刻了“做多三铁律”(DEMA ATR 绿色 + 绿线交叉 + 阳线)和“做空三要素”(DEMA ATR 红色 + 红线交叉 + 阴线),通过多次回测调整参数:
• 买入条件从 dema_atr_14 > 1.1ma_dema_atr_20 优化至 dema_atr_14 > 1.03ma_dema_atr_20,增加信号频率。
• RSI 阈值从 rainbow_rsi_14 > 45 放宽至 > 40,rainbow_rsi_28 < 55 调整至 < 49,提升趋势捕捉能力。
• 卖出条件提前至 dema_atr_14 < 0.98*ma_dema_atr_20,控制回撤。
回测成果
• 数据范围:2019-10-25 至 2025-04-16日。
• 结果:累计收益率:92.97% 年化收益率:16.96% 夏普比率:0.56% 最大回撤率:20.96% 风险比率:0.00% 开仓次数:97 胜率:48.45%
心得与建议
因子扩展的关键在于百分百复刻原有因子算法,并适配当前量化框架,平衡信号数量与质量。DEMA Adjusted ATR 提供波动过滤,Rainbow Adaptive RSI 增强趋势确认,结合 K 线形态(阳线/阴线)提高胜率。建议量化爱好者:
• 调整 ATR 和 RSI 参数,适配不同市场特性,进一步提高回测结果指标。
• 加入止损或动态调整,优化回撤控制。
• 分享回测日志,验证信号有效性。
欢迎社区交流进一步提升和优化的思路,共同提升策略表现!以下是c#相关的因子算法:
public static double CalculateTvDemaAdjustedAtr(double[] high, double[] low, double[] close, int timeperiod) { // 验证输入序列有效性及长度 if (high == null || low == null || close == null || high.Length < timeperiod * 2 + 1 || low.Length < timeperiod * 2 + 1 || close.Length < timeperiod * 2 + 1) { return double.NaN; // 无效输入返回 NaN } // 初始化真波幅(TR)数组 double[] tr = new double[high.Length]; // 遍历序列计算每日 TR for (int i = 0; i < high.Length; i++) { // TR1:当日高低价差 double tr1 = high[i] - low[i]; // TR2:高价与前日收盘价差的绝对值,首日忽略 double tr2 = i > 0 ? Math.Abs(high[i] - close[i - 1]) : 0; // TR3:低价与前日收盘价差的绝对值,首日忽略 double tr3 = i > 0 ? Math.Abs(low[i] - close[i - 1]) : 0; // TR:取三者最大值 tr[i] = Math.Max(tr1, Math.Max(tr2, tr3)); } // 对 TR 应用现有 DEMA 计算 double demaAtr = CalculateTaDema(tr, timeperiod); // 返回最新 DEMA ATR 值 return demaAtr; } public static double CalculateTvRainbowAdaptiveRsi(double[] close, double[] high, double[] low, int baseTimeperiod, int minTimeperiod, int maxTimeperiod) { // 验证输入序列有效性及长度 if (close == null || high == null || low == null || close.Length < baseTimeperiod + 1 || high.Length < baseTimeperiod + 1 || low.Length < baseTimeperiod + 1) { return double.NaN; // 无效输入返回 NaN } // 计算基础周期的 ATR 作为波动率衡量 double atr = CalculateTaAtr(high, low, close, baseTimeperiod); // 标准化波动率:ATR 相对收盘价的比例(百分比) double volatility = atr / close[close.Length - 1] * 100; // 归一化波动率到 [0,1],假设波动范围 1%~4% double normalizedVol = Math.Min(1, Math.Max(0, (volatility - 1) / 3)); // 计算自适应周期 int adaptiveLength = (int)(minTimeperiod + (maxTimeperiod - minTimeperiod) * normalizedVol); // 计算中间层 RSI(基于自适应周期) double rsi2 = CalculateTaRsi(close, adaptiveLength); // 返回最新中间层 RSI 值 return rsi2; } public static double CalculateTaRsi(double[] input, int period) { if (input == null || input.Length < period + 1) { return double.NaN; } // 计算价格变化 double[] diff = new double[input.Length - 1]; for (int i = 1; i < input.Length; i++) { diff[i - 1] = input[i] - input[i - 1]; } // 初始化平均增益和减益 double gainSum = 0.0; double lossSum = 0.0; for (int i = 0; i < period; i++) { if (diff[i] > 0) { gainSum += diff[i]; } else { lossSum += Math.Abs(diff[i]); } } double avgGain = gainSum / period; double avgLoss = lossSum / period; // 平滑 RSI 计算 for (int i = period; i < diff.Length; i++) { double gain = diff[i] > 0 ? diff[i] : 0.0; double loss = diff[i] < 0 ? Math.Abs(diff[i]) : 0.0; avgGain = ((avgGain * (period - 1)) + gain) / period; avgLoss = ((avgLoss * (period - 1)) + loss) / period; } // 计算 RSI if (avgLoss == 0) { return 100.0; } double rs = avgGain / avgLoss; double rsi = 100.0 - (100.0 / (1.0 + rs)); return rsi; } public static double CalculateTaDema(double[] series, int periods) { if (series.Length < periods * 2) return double.NaN; double[] ema1 = new double[series.Length]; double multiplier = 2.0 / (periods + 1); ema1[0] = series[0]; for (int i = 1; i < series.Length; i++) { ema1[i] = (series[i] - ema1[i - 1]) * multiplier + ema1[i - 1]; } double[] ema2 = new double[series.Length]; ema2[0] = ema1[0]; for (int i = 1; i < series.Length; i++) { ema2[i] = (ema1[i] - ema2[i - 1]) * multiplier + ema2[i - 1]; } return 2 * ema1[series.Length - 1] - ema2[series.Length - 1]; } public static double CalculateTaAtr(double[] high, double[] low, double[] close, int window) { if (high == null || low == null || close == null || high.Length < window + 1 || low.Length < window + 1 || close.Length < window + 1) { return double.NaN; } // 计算真实波幅(TR) double[] tr = new double[high.Length]; for (int i = 0; i < high.Length; i++) { double tr1 = high[i] - low[i]; double tr2 = i > 0 ? Math.Abs(high[i] - close[i - 1]) : 0; double tr3 = i > 0 ? Math.Abs(low[i] - close[i - 1]) : 0; tr[i] = Math.Max(tr1, Math.Max(tr2, tr3)); } // 计算 ATR(简单移动平均) double sum = 0.0; for (int i = high.Length - window; i < high.Length; i++) { sum += tr[i]; } double atr = sum / window; return atr; }
TOML文件内容
name = "彩虹波动交易法" symbols = [ "159915.SZ",] benchmark = "510300.SH" [date] start_date = "20191025" end_date = "20250416" [factors] exprs = [ "tv_dema_atr(high,low,close,14)","tv_rainbow_rsi(close,high,low,14,7,28)","tv_rainbow_rsi(close,high,low,28,7,28)","tv_dema_atr(high,low,close,20)","ma(tv_dema_atr(high,low,close,20),20)","close - open","open - close"] names = [ "dema_atr_14","rainbow_rsi_14","rainbow_rsi_28","dema_atr_20","ma_dema_atr_20","kma_positive","kma_negative"] [period] algo = "RunDaily" [selection] algo = "SelectAll" buy_rules = ['dema_atr_14 > 1.08*ma_dema_atr_20 and rainbow_rsi_14 > 42 and kma_positive > 0'] buy_at_least_count = 1 sell_rules = ['dema_atr_14 < 0.92*ma_dema_atr_20 or rainbow_rsi_28 < 52 or kma_negative > 0'] sell_at_least_count = 1 [order] factor = "" topK = 1 dropN = 0 is_desc = true [weight] algo = "WeighEqually" [weight.fixed_weights]