You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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一致。具体步骤如下:

  1. 先计算需要补充的空值数量:原始序列长度减去处理后序列的长度;
  2. 用Enumerable.Repeat生成对应数量的null(模拟Pandas的NaN);
  3. 把空值序列和处理后的序列拼接,再按需填充缺失值。

修改后的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 07:17:44