近期,我在 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]