[原创] 每根 K 线都更新模型,究竟是在适应市场,还是在追逐噪声?一次基于 FMZ Rust 的在线学习对照实践

一次基于 FMZ Rust 的在线学习对照实验

在线学习在量化研究里很容易被包装成一项天然优势:市场是非平稳的,所以模型应该持续吸收新数据。

这个推理只走了一半。

模型更新得更快,确实可能更早摆脱已经失效的历史关系;但同一套更新机制,也会更快地把短期随机波动写进参数。对交易策略来说,后者不仅表现为预测不稳定,还会转化为更高的换手、更强的成本敏感性,以及更大的路径依赖。

因此,本文不试图证明“在线模型优于固定模型”。实验要回答的是一个更具体的问题:

当模型结构、特征、交易规则和成本假设完全相同时,改变模型吸收新样本的速度,会怎样影响样本外损失、参数稳定性、换手和虚拟净值?

为避免把模型差异和回测环境差异混在一起,四套模型在同一个 Rust 策略进程内并行运行,读取同一组 K 线,使用相同的特征和虚拟成交规则。策略不向交易所发单,只维护四个影子账户。

本文保留一组实际回测,用来检查时间对齐、标签分布、预测损失、换手和交易成本是否符合预期。但单次回测不会被当作普遍结论。策略的主要价值,仍然是建立一套可复现的比较框架。


1. 研究问题:更新速度本身就是模型风险

在线学习通常采用“先预测、后观察标签、再更新”的顺序。第  个样本到来时,模型先用当前参数做预测;真实结果出现后,计算损失并更新参数。

这类方法的优势是无需反复保存和重训全部历史数据,也能逐步吸收新信息。问题在于,每条新样本都会立即影响参数。

可以把更新机制理解为一条连续的速度轴:

永不更新
  ── 定期滚动重训
  ── 误差恶化后更新
  ── 每根 K 线更新
  ── 每个 Tick 更新

越靠左,参数越稳定,但面对结构变化时反应更慢;越靠右,模型越灵活,也越容易被短期噪声推动。

因此,本次实验预先提出三个待检验的判断,而不是预设答案:

  1. 逐根在线更新可能缩短模型在行情切换后的恢复时间;
  2. 同一机制也可能放大参数波动、信号翻转和交易成本;
  3. 模型表现未必随更新频率单调改善,中间速度可能更稳。

2. 实验设计:只改变模型怎样吸收新样本

四组模型共享同一套初始化参数。

模型 更新方式 研究含义
固定模型 初始化后不再更新 观察历史关系自然衰减的速度
逐根在线 每获得一个新标签就做一次梯度更新 最快的样本吸收速度
滚动重训 每隔固定根数,用最近窗口从头训练 降低单个样本的即时影响
误差门控 近期 Log Loss 明显恶化后,用小批近期样本低速更新 在稳定和适应之间加一道门槛

第四组只能称为“误差门控更新”,不能称为严格的概念漂移检测器。

预测损失上升可能来自条件分布变化,也可能只是波动率上升、异常值、标签随机性或模型本身校准不佳。本文只利用损失恶化作为工程触发条件,不根据它断言市场已经发生概念漂移。

为了保证比较尽量干净,四组模型使用完全相同的:

  • 初始训练样本;
  • 特征及标准化参数;
  • 逻辑回归结构;
  • 多空概率阈值;
  • 收益计算方式;
  • 单边换手成本假设。

四个虚拟账户也在同一根 K 线上同步结算。这样,结果差异主要来自更新机制,而不是数据切片、接口时点或回测撮合差异。


3. 时间对齐:先把未来函数和成交假设处理清楚

在线模型最容易出错的地方不是梯度公式,而是样本时间。

这里的 0 类准确地说是“非上涨”,因为开盘价等于收盘价时也会被编码为 0。

标签函数如下:

fn make_label(
    bars: &[Bar],
    feature_index: usize,
) -> f64 {
    let next_bar =
        bars[feature_index + 1];

    if next_bar.close > next_bar.open {
        1.0
    } else {
        0.0
    }
}

流程如下:

第 t 根 K 线结束
        ↓
计算 x_t 并预测第 t+1 根方向
        ↓
假定在第 t+1 根开盘附近建立仓位
        ↓
第 t+1 根收盘,得到标签和开盘到收盘收益
        ↓
先评价原预测,再允许模型更新

这种定义没有假定策略能够在已经知道收盘价后,仍成交在第  根 K 线的收盘价上。

虚拟账户使用第 t+1 根 K 线的开盘到收盘收益:

这仍然是简化模型。真实订单未必能成交在下一根 K 线的开盘价,盘口冲击、延迟和滑点也不会固定。代码中的成本只是统一的压力测试假设,不代表任何交易所的真实费率。

3.1 回测与实盘分别判断完整 K 线

第一版策略曾统一使用当前时间判断 K 线是否结束。在实际回测中,这种写法把正在形成的 Bar 提前当成了完整样本,导致大量 K 线出现 Open == Close,模型准确率异常接近 100%。

修正版通过 IsVirtual() 区分运行环境:

  • 回测环境中,保守使用行情数组的倒数第二根 Bar;
  • 实盘环境中,检查最后一根 Bar 的起始时间加周期长度是否已经不晚于当前时间;
  • 如果实盘接口中的最后一根仍未结束,则退回倒数第二根。
fn latest_closed_index(
    bars: &[Bar],
    is_backtest: bool,
    bar_seconds: i64,
) -> Option<usize> {
    if bars.len() < 2 {
        return None;
    }

    if is_backtest {
        return Some(bars.len() - 2);
    }

    let last_index = bars.len() - 1;
    let period_ms = bar_seconds * 1000;
    let current_time_ms =
        (UnixNano() / 1_000_000) as i64;

    if bars[last_index].time + period_ms
        <= current_time_ms
    {
        Some(last_index)
    } else {
        Some(last_index - 1)
    }
}

少处理一根 K 线的代价,远小于把未完成数据误作标签。

3.2 Test-then-train

每个新标签出现时,处理顺序严格保持为:

  1. 使用之前保存的预测计算准确率和 Log Loss;
  2. 使用之前确定的仓位计算当前 K 线收益;
  3. 将当前样本加入训练数据;
  4. 按各模型的规则更新参数;
  5. 使用当前完整 K 线生成下一根预测。

也就是先测试,再训练。当前样本不会先进入模型,再拿同一条样本评价模型。


4. 模型:简单的逻辑回归足够完成这次实验

本文没有使用神经网络或树模型,而是使用带 L2 正则的逻辑回归:

其中  Pt 是下一根 K 线上涨的估计概率。

逐根更新采用二元交叉熵对应的随机梯度:

上式只写出了权重项。代码中的偏置 b 也会同步更新,但不施加 L2 正则。

fn predict(
    &self,
    features: &[f64; N_FEATURES],
) -> f64 {
    let mut linear_value = self.bias;

    for j in 0..N_FEATURES {
        linear_value +=
            self.weights[j] * features[j];
    }

    Self::sigmoid(linear_value)
}

fn update(
    &mut self,
    features: &[f64; N_FEATURES],
    label: f64,
    learning_rate_scale: f64,
) {
    let probability = self.predict(features);
    let error = probability - label;
    let step =
        self.learning_rate
            * learning_rate_scale;

    for j in 0..N_FEATURES {
        let gradient = clip(
            error * features[j]
                + self.l2_penalty
                    * self.weights[j],
            -5.0,
            5.0,
        );

        self.weights[j] -= step * gradient;
    }

    self.bias -=
        step * clip(error, -1.0, 1.0);

    self.updates += 1;
}

选择简单模型并不是认为逻辑回归足以描述市场,而是为了减少实验中的混杂因素。

如果同时改变网络结构、训练轮数、特征数量和更新频率,最后很难判断收益差异来自哪里。线性模型的参数变化可以直接测量,也便于观察“模型适应”是否伴随着过大的权重漂移。

代码还对单次梯度和标准化特征做了截断,目的不是提高收益,而是避免极端 K 线让参数在一次更新中跳得过远。


5. 特征:只保留价格结构和成交量的基础信息

模型使用八个特征:

  1. 单根对数收益;
  2. K 线实体的对数收益;
  3. 最高价与最低价的对数区间;
  4. 上影线相对长度;
  5. 下影线相对长度;
  6. 成交量对数变化;
  7. 三根 K 线动量;
  8. 最近五根 K 线收益的实现波动率。

没有加入 RSI、MACD、均线交叉等派生指标,也没有使用几十个滞后项。

下面是特征计算中的主要部分:

fn raw_features(
    bars: &[Bar],
    index: usize,
) -> [f64; N_FEATURES] {
    let current = bars[index];
    let previous = bars[index - 1];
    let epsilon = 1e-12;

    let log_return_1 =
        (current.close.max(epsilon)
            / previous.close.max(epsilon))
            .ln();

    let candle_body =
        (current.close.max(epsilon)
            / current.open.max(epsilon))
            .ln();

    let range =
        (current.high.max(epsilon)
            / current.low.max(epsilon))
            .ln();

    let upper_wick =
        (current.high
            - current.open.max(current.close))
            .max(0.0)
            / current.open.max(epsilon);

    let lower_wick =
        (current.open.min(current.close)
            - current.low)
            .max(0.0)
            / current.open.max(epsilon);

    let volume_change =
        ((current.volume + epsilon)
            / (previous.volume + epsilon))
            .ln();

    let momentum_3 =
        (current.close.max(epsilon)
            / bars[index - 3]
                .close
                .max(epsilon))
            .ln();

    // 最近五根收益的实现波动率
    // 在完整代码中按同一窗口继续计算。

    [
        log_return_1,
        candle_body,
        range,
        upper_wick,
        lower_wick,
        volume_change,
        momentum_3,
        realized_volatility,
    ]
}

初始样本被按时间分为前 80% 和后 20%。标准化器只用前 80% 的特征拟合均值和标准差,之后保持固定。四套模型始终使用同一个标准化器。

这里特意不让标准化器在线更新。否则,逐根模型获得的不仅是参数更新,还多了一层特征分布自适应,比较对象就不再单一。

固定标准化器有利于保持四组模型可比,但当特征分布长期变化时,也可能产生尺度失配。因此,本次结果同时包含模型关系失效和固定标准化尺度失配的影响,二者并没有被单独识别。


6. 初始化:四个模型必须站在同一条起跑线上

代码默认准备 320 条初始监督样本。

初始化分两步:

  1. 使用前 80% 样本做六轮批量训练;
  2. 对后 20% 样本执行逐条的“预测—记录损失—更新”。

第二段得到的平均预测前 Log Loss,被用作误差门控模型的参考损失。处理完这段样本后得到的模型,才是四组模型共同的初始参数。

fn fit_initial_model(
    samples: &[Sample],
    split_index: usize,
    config: &Config,
) -> (LogisticModel, f64) {
    let mut model =
        train_model(
            &samples[..split_index],
            config.initial_epochs,
            config,
        );

    let mut loss_sum = 0.0;
    let mut count = 0usize;

    for sample in &samples[split_index..] {
        let probability =
            model.predict(&sample.x);

        loss_sum +=
            log_loss(
                probability,
                sample.y,
            );

        count += 1;

        model.update(
            &sample.x,
            sample.y,
            1.0,
        );
    }

    let baseline_loss =
        if count == 0 {
            0.69314718056
        } else {
            loss_sum / count as f64
        };

    (model, baseline_loss)
}

这种处理解决了两个问题。

第一,参考损失不是模型在已经训练过的数据上的回看损失,而是按时间顺序得到的预测前损失。

第二,四组模型都从同一个、已经吸收全部初始化样本的参数状态开始。不会出现固定模型和在线模型的初始训练数据不同,却把后续差异归因于更新方式的情况。


7. 四种更新机制

7.1 固定模型

固定模型在初始化后不再修改权重。

它提供的是基准:如果后续表现持续恶化,说明初始化阶段学到的关系无法稳定外推;如果它反而最稳,则说明额外更新没有带来足够的信息增量。

需要注意,固定模型只表示权重和偏置不再更新,不代表预测概率或仓位保持不变。输入特征每根 K 线都在变化,固定参数同样可能产生频繁换手。

7.2 逐根在线模型

每个标签出现后执行一次梯度更新:

online_model.update(
    &previous_features,
    label,
    1.0,
);

它对新环境反应最快,也最容易把连续几条偶然样本解释成结构变化。

学习率是这组模型最重要的风险参数。学习率越大,新样本对参数的影响越强。本文使用界面默认值 0.02,不做样本内寻优。

7.3 滚动重训模型

在默认参数下,滚动模型保存最近 240 条样本,每经过 32 条新样本,将模型参数清零,再用窗口内样本重新训练。

if processed_bars
    % config.retrain_every
    == 0
{
    periodic_model =
        retrain_from_window(
            &rolling_samples,
            &config,
        );

    periodic_retrains += 1;
}

这种方式有两个特征:

  • 旧样本会被明确移出训练集;
  • 单根新样本不会立即改变模型,而是等到下一次重训时与一批近期样本共同起作用。

它的适应有延迟,但参数变化通常不会像逐根更新那样完全由最后一条样本推动。滚动窗口和重训间隔都可以在策略界面中调整。

7.4 误差门控模型

门控模型维护自身预测 Log Loss 的指数移动平均:

当近期损失高于初始化参考损失的一定比例,且不处于冷却期时,模型才使用最近一小批样本进行低学习率更新。

let current_gated_loss =
    log_loss(
        gated_old_probability,
        label,
    );

gated_loss_ema =
    (1.0 - config.loss_ema_alpha)
        * gated_loss_ema
        + config.loss_ema_alpha
            * current_gated_loss;

let gate_threshold =
    baseline_loss
        * config.gate_loss_multiplier;

if gate_cooldown_left == 0
    && gated_loss_ema > gate_threshold
{
    let batch_start =
        rolling_samples
            .len()
            .saturating_sub(
                config.gate_batch
            );

    for (sample_index, sample)
        in rolling_samples
            .iter()
            .enumerate()
    {
        if sample_index >= batch_start {
            gated_model.update(
                &sample.x,
                sample.y,
                config.gated_lr_scale,
            );
        }
    }

    gate_events += 1;
    gate_cooldown_left =
        config.gate_cooldown;
}

界面默认参数为:

  • 损失阈值:参考损失的 1.10 倍;
  • 更新批量:最近 32 条样本;
  • 学习率缩放:基础学习率的 0.25;
  • 更新后冷却:32 根 K 线。

这些值只是未经优化的实验默认值。它们的作用是定义一套可重复的门控规则,不代表统计意义上的最优阈值。


8. 交易映射与成本

模型概率被映射为三种仓位:

fn position_from_probability(
    probability: f64,
    config: &Config,
) -> i32 {
    if probability >= config.long_threshold {
        1
    } else if probability
        <= config.short_threshold
    {
        -1
    } else {
        0
    }
}

默认规则是:

p >= 0.55:多仓
p <= 0.45:空仓
其他:不持仓

0.45 到 0.55 的中性区间用于减少模型在 0.5 附近的小幅波动直接转化为交易。

仓位变化量定义为:

因此:

  • 空仓到多仓,换手为 1;
  • 多仓到空仓,换手为 1;
  • 多仓直接反为空仓,换手为 2。

成本已经设置为界面参数,而不是写死的代码常量:

let cost_bps = float_parameter(
    "CostBps",
    CostBps,
    0.0,
    100.0,
    true,
)?;

cost_per_turnover:
    cost_bps / 10_000.0,

默认将 CostBps 设置为 5,即每单位单边换手扣除 5 个基点。该值是实验参数,可以在策略界面中调整,不代表具体交易所的实际费率。


9. 评价指标:不要只看准确率

四组模型同时记录以下指标:

指标 观察目的
平均 Log Loss 概率预测是否变得更可靠,而不只是方向碰巧正确
方向准确率 0.5 阈值下的基本分类表现
虚拟净收益 预测能否在统一交易规则下转化为收益
最大回撤 更新机制是否增加净值路径风险
持仓率 某模型是否只是因为更少交易而显得稳定
累计换手 交易成本敏感性
仓位变化次数 信号是否频繁改口
多空直接翻转次数 模型是否在两个方向间剧烈摆动
参数位移 当前权重与共同初始模型之间的距离

其中,参数位移定义为权重和偏置差值的欧氏距离。由于特征使用同一个固定标准化器,四组模型之间可以做相对比较。

参数位移大本身不是错误。行情结构真的改变时,模型需要移动。需要警惕的是下面这种组合:

参数位移快速上升
+ 换手明显增加
+ Log Loss 没有改善
+ 扣成本净值恶化

这更像是模型在追逐近期样本,而不是获得了稳定的适应能力。


10. 默认参数下的一组实际回测

修正完整 K 线判断后,使用以下条件完成了一次基准回测:

品种:ETH_USDT.swap
周期:15 分钟
回测区间:2025-07-09 至 2026-06-21
初始化样本:320
样本外 K 线:33333
基础学习率:0.02
滚动窗口:240
单边换手成本:5 bps

样本外统计中,上涨标签为 16644 条,非上涨标签为 16689 条,其中有 36 根 K 线开盘价等于收盘价。状态栏当前显示为“下跌”,但代码中的实际口径是 Close <= Open,因此下文统一称为“非上涨”。

模型 准确率 Log Loss 持仓率 累计换手 多空翻转 参数位移 净收益
固定模型 52.07% 0.7094 65.2% 24755 4985 0.0000 -100.00%
逐根在线 51.44% 0.7203 56.7% 21073 3619 0.4409 -100.00%
滚动重训 51.74% 0.7301 64.6% 21271 4111 0.5712 -100.00%
误差门控 49.40% 0.6986 18.3% 6190 142 0.2989 -98.00%
  • 10.1 时间对齐问题已经修正

    上涨与非上涨标签基本均衡,模型准确率回到 49%~52%,初始化参考 Log Loss 为 0.6988。此前接近 100% 的准确率,确实来自未完成 K 线被提前处理,而不是模型突然获得了极强的预测能力。

  • 10.2 更新更勤,没有改善样本外概率预测

    对于始终输出 0.5 的朴素概率模型,每条样本的 Log Loss 都约为 0.6931。

    本次回测中,四组模型的平均 Log Loss 均高于这一基准。误差门控最接近 0.6931,但仍不能认为模型已经获得有效预测能力。

    逐根在线和滚动重训都产生了明显的参数位移,但 Log Loss 分别上升到 0.7203 和 0.7301,弱于固定模型。至少在当前品种、周期、特征和默认参数下,更频繁地更新模型没有改善样本外概率预测。

  • 10.3 门控机制降低了噪声反应,但没有创造交易优势

    误差门控模型只触发了 8 次更新。它的累计换手降到 6190,多空直接翻转只有 142 次,明显低于另外三组。

    这说明门控机制至少完成了它的工程目标:减少模型因为短期误差而频繁改口。

    但其成本后净值仍下降约 98%。准确的结论不是“误差门控已经有效”,而是:

    受控更新比无条件更新更稳定,但稳定本身还没有转化为交易优势。

    状态栏最终显示“未达到更新阈值”,只表示回测结束时损失 EMA 位于阈值下方,并不与历史上曾触发 8 次更新矛盾。

  • 10.4 固定模型不更新参数,仍然可能频繁换手

    固定模型的参数位移为 0,但累计换手反而是四组中最高的。

    这并不矛盾。固定模型只表示权重和偏置不再变化,输入特征  每根 K 线仍然不同。参数固定、输入变化,预测概率和仓位依然会变化。

  • 10.5 净值接近零后,收益指标已经饱和

    四组模型在 5 bps 单边成本下都出现了严重亏损。主要问题不是某一次大额损失,而是在三万多根 K 线上累计了大量换手。

    当净值接近零以后,净收益和最大回撤都接近极限值,已经很难继续区分模型之间的经济差异。

    后续版本应至少增加:

    • 扣成本前的毛收益;
    • 累计成本;
    • 净对数收益;
    • 每千根 K 线换手;
    • 0、2、5、10 bps 多种成本情景。

    这组结果不能证明在线学习在其他市场中无效。它只能说明:在本次实验条件下,模型更新得更勤,并没有表现出更强的市场适应能力;受控更新减少了噪声反应,但稳定性还没有转化为交易优势。


11. 完整 Rust 实现

完整策略只读取 K 线,在内存中维护四个模型和四个虚拟账户,不包含真实下单逻辑。

策略代码


12. 怎样判断模型是在适应,还是在追噪声

一次回测中在线模型净值最高,不能直接证明在线更新有效。更有信息量的是指标之间的关系,以及结论能否跨区间重复。

  • 情形一:Log Loss 改善,换手没有明显上升

    如果逐根模型在行情切换后较快降低 Log Loss,同时参数位移趋于稳定,累计换手与固定模型差距不大,这才是较强的适应证据。

    这里的重点不是“参数动了”,而是参数移动后预测质量得到持续改善。

  • 情形二:准确率提高,但 Log Loss 和净值变差

    准确率只关心概率是否跨过 0.5,不关心模型有多自信。

    模型可能把原来的 0.51 提高到 0.90,方向仍然正确;一旦判断错误,Log Loss 会显著恶化。交易上,这类过度自信也更容易让信号跨过多空阈值。

    因此,在线模型准确率略高,但 Log Loss、回撤和成本后收益更差,并不矛盾。

  • 情形三:零成本有优势,加入成本后消失

    这通常意味着在线更新产生了更多边际信号,但单次优势不足以覆盖交易摩擦。

    此时模型也许确实捕捉到了一点短期可预测性,但这种可预测性没有达到可交易强度。不能把毛收益提升等同于策略有效。

  • 情形四:滚动重训或门控更新更稳

    如果这两组在多个品种和周期上都表现出较低 Log Loss、较小回撤和较低换手,说明有用的适应速度可能位于“永不更新”和“逐根更新”之间。

    仍需注意,滚动窗口、重训间隔和门控阈值本身也是超参数。不能只保留表现最好的一组设置。

  • 情形五:固定模型最好

    这并不说明市场是平稳的,只说明在当前特征、模型和样本区间中,新样本没有提供足够稳定的信息,或者在线更新规则没有正确提取这些信息。

    固定模型参数稳定,并不代表信号更少。本次回测中,固定模型的累计换手反而是四组中最高的。


13. 结果应怎样记录

至少应按品种、周期和时间区间分别保存下表,而不是只保留一条总净值:

品种 周期 区间 模型 Log Loss 准确率 毛收益 累计成本 净收益 最大回撤 持仓率 换手 参数位移

还应增加三类稳健性检查。

13.1 成本情景

至少比较:

0 bps
2 bps / 单边换手
5 bps / 单边换手
10 bps / 单边换手

如果在线模型只在零成本下胜出,结论应写成“产生了更多毛信号”,而不是“更适应市场”。

13.2 相邻参数

不要做大规模寻优,只检查结论是否对相邻参数稳定,例如:

在线学习率:0.01 / 0.02 / 0.04
重训间隔:16 / 32 / 64
滚动窗口:120 / 240 / 480
门控损失倍数:1.05 / 1.10 / 1.20

如果优势只出现在一个非常窄的参数点,更可能是样本适配。

13.3 分阶段观察

把结果按波动率、趋势强度或自然时间区间拆分。

总样本收益可能掩盖两种完全不同的行为:模型在切换期恢复更快,但在稳定期因频繁更新持续漏损。只有分阶段观察,才能看见适应收益和噪声成本分别发生在哪里。


14. 这套实验仍然有哪些限制

第一,虚拟成交不是交易所撮合。下一根开盘附近入场、固定基点成本和无盘口冲击,都是简化假设。

第二,模型只预测下一根 K 线的开盘到收盘方向。这个目标方便做时间对齐,但不代表它是最有经济意义的预测目标。

第三,固定标准化器保证了模型比较的一致性,却没有解决特征分布漂移。在线标准化可以作为另一组实验,但不能只加入逐根模型。

第四,误差门控只是一条启发式规则,不具有漂移检测算法的统计保证。若要研究正式的漂移识别,应单独实现 ADWIN、DDM 等方法,并控制误报和检测延迟。

第五,单一品种、单一周期或单一回测区间无法回答“在线学习是否更好”。这类问题只能通过跨市场、跨阶段和相邻参数的稳定性来回答。

第六,逻辑回归的结论不应直接外推到深度模型。复杂模型有更强的表达能力,也有更大的更新自由度;在线更新的噪声风险通常不会因此自动消失。

第七,当前虚拟净值在较高换手成本下快速接近零,导致净收益和最大回撤指标出现饱和。后续研究应同时输出毛收益、累计成本、净对数收益以及每千根 K 线换手,避免不同模型都显示为接近 -100% 后无法继续比较。


结论

市场会变,不等于模型应该在每根 K 线后立即改变。

固定模型承担的是“停留在旧关系里”的风险;逐根在线模型承担的是“过度相信最近样本”的风险。两者之间不存在一个脱离数据、成本和预测目标的普遍答案。

在线更新是否有价值,至少要同时满足三点:

  1. 新环境出现后,预测损失能够更快恢复;
  2. 参数变化没有长期演化为更高的信号翻转和换手;
  3. 改善在成本后仍然存在,并能跨品种、周期和相邻参数重复。

本次默认参数回测中,逐根在线和滚动重训都发生了明显参数移动,却没有降低样本外 Log Loss;误差门控减少了换手和多空翻转,但仍未形成可交易优势。

如果模型只是更新得更勤、参数走得更远、交易做得更多,却没有持续降低样本外损失,那么它并没有更适应市场。

它只是更快地学会了最近发生的事情。

策略代码

免责声明:信息仅供参考,不构成投资及交易建议。投资者据此操作,风险自担。
如果觉得文章对你有用,请随意赞赏收藏
相关推荐
相关下载
登录后评论
Copyright © 2019 宽客在线