Python Pandas交易策略转C#实现:平移与滚动窗口后缺失空元素填充异常排查
解决C#中Shift+Window后序列长度不匹配的问题
我一看你这个问题就明白是怎么回事了——Python Pandas和你用的C#序列库在处理shift和rolling(对应C#的Shift和Window)时的行为差异导致的。Pandas会自动保留原数据的长度,用NaN填充前面的缺失部分;但你的C#库直接把长度不足的部分截断了,再加上FillMissing()只能补中间的空值,没法补开头的缺口,自然就出现了长度不匹配的问题。
问题根源拆解
你的原始序列长度是10180:
Shift(20)会把数据向后挪20位,Pandas里前20个位置是NaN,但你的C#库直接去掉了前20个元素;- 接着
Window(48)(也就是12*60/15)需要至少48个元素才能计算第一个窗口的min/max,所以前47个窗口会被截断,又少了47个元素; - 总共少了20+47=67个元素,所以处理后的序列长度变成10180-67=10113,和原序列长度对不上,后续交叉判断自然索引越界。
解决方案:手动补全开头的缺失值
我们需要手动给处理后的min和max序列开头补上对应数量的空值,让它们的长度和原始typicalPrice一致。具体步骤如下:
- 先计算需要补充的空值数量:原始序列长度减去处理后序列的长度;
- 用
Enumerable.Repeat生成对应数量的null(模拟Pandas的NaN); - 把空值序列和处理后的序列拼接,再按需填充缺失值。
修改后的C#代码片段
public override List<TradeAdvice> Prepare(List<OHLCV> candles) { var result = new List<TradeAdvice>(); var typicalPrice = candles.TypPrice().Select(e => e ?? 0).ToList(); var typicalSma = typicalPrice.Sma(10); // 先定义移位和窗口大小,方便维护 var shiftSize = 20; var windowSize = 12 * 60 / 15; // 48 var series = typicalPrice.ToOrdinalSeries(); // 先获取窗口计算后的min和max(此时长度是10113) var minWindowed = series.Shift(shiftSize).Window(windowSize).Select(kvp => kvp.Value.Min()); var maxWindowed = series.Shift(shiftSize).Window(windowSize).Select(kvp => kvp.Value.Max()); // 计算需要补充的空值数量:原始长度 - 处理后的长度 var padCount = typicalPrice.Count - minWindowed.Count(); // 开头补充padCount个null,再拼接处理后的序列,最后填充缺失值 var min = Enumerable.Repeat<decimal?>(null, padCount).Concat(minWindowed).FillMissing(); var max = Enumerable.Repeat<decimal?>(null, padCount).Concat(maxWindowed).FillMissing(); var dailyMean = (max + min) / 2; var asd = dailyMean.SelectValues(e => Convert.ToDecimal(e)).Values.ToList(); // 现在asd的长度和typicalPrice一致了,不会再索引越界 var crossedBelow = asd.CrossedBelow(typicalPrice); var crossedAbove = asd.CrossedAbove(typicalPrice); for (int i = 0; i < candles.Count; i++) { if (i < StartupCandleCount - 1) result.Add(TradeAdvice.WarmupData); else if (crossedBelow[i]) result.Add(TradeAdvice.Buy); else if (crossedAbove[i]) result.Add(TradeAdvice.Sell); else result.Add(TradeAdvice.NoAction); } return result; }
额外注意事项
- 如果你后续用到
typicalSma,也要检查它的长度:Pandas的SMA(10)前9个值是NaN,如果你的C#Sma(10)直接截断了前9个元素,那也要用同样的方式补9个null,保证长度一致; FillMissing()的填充逻辑要和原Python代码对齐:原Python代码里daily_mean的前67个值是NaN,如果你的策略不需要填充这些值,可以去掉FillMissing(),直接保留null,避免改变原策略逻辑;- 测试时要重点对比前67个元素的处理结果,确保和Python输出一致,这是最容易出问题的地方。
内容的提问来源于stack exchange,提问作者nop
相关产品推荐
相关产品推荐

