【交易策略】基于决策树的机器学习策略:从预测价格到预测市场结构

近期我尝试利用 Zorro 内置的决策树模型构建机器学习交易策略。在初步构建的模型中,策略未能实现稳定的盈利。经过复盘,我认为根本原因主要集中在两点:1. 选用的特征缺乏足够的非线性预测能力;2. 选择了错误的目标变量。

接下来的核心工作将围绕特征工程重构与目标变量调整展开,期望通过这一轮优化,能够逐步逼近"创建稳定盈利的机器学习策略"这一最终目标。

1. 目标变量重构

在初步模型中,我们简单地将下一笔交易的 PnL(盈亏)作为目标变量。

在传统的量化机器学习应用中,人们通常习惯于预测未来特定窗口期的价格绝对变化(回归问题),或者预测未来价格涨跌的符号(分类问题)。但大量的实盘经验证明,机器学习模型无法精准预测绝对的未来价格。基于此,我们必须进行建模思维的范式转换:从直接预测价格涨跌,转变为测度未来的"市场结构(Market Structure)"。

我们将市场结构划分为三种状态:

  • 上涨状态(标志为 +1)
  • 下跌状态(标志为 -1)
  • 震荡状态(标志为 0)

具体算法如下:

首先,将低通滤波器(Lowpass Filter)应用于收盘价,以提取极其平滑的长期趋势线。随后,基于真实波动幅度(ATR)与波动率乘数构建动态波动率通道。

  • 当趋势线上升,且收盘价高于通道上轨时,将市场状态标记为 +1(上涨状态)
  • 当趋势线下降,且收盘价低于通道下轨时,将市场状态标记为 -1(下跌状态)
  • 其余时间皆标记为 0(震荡状态)

最终,我们的目标变量被定义为:预测未来一天的市场结构。

核心机制解析

此时会产生一个技术疑问:我们的目标变量包含三个离散水平值(-100, 0, 100),在调用 adviseLong(DTREE) 时,Zorro 底层是在拟合回归模型还是分类模型?

直觉上,如果是纯粹的多分类模型,输出应该严丝合缝地落在 { -100, 0, 100 } 这三个选项上。然而,adviseLong 的实际输出却是一个受限的连续变量。

其底层逻辑在于:Zorro 巧妙地将多分类问题转化成了"概率期望值回归"。

当决策树根据输入的特征矩阵层层分裂,最终到达某个"叶子节点"时(例如该节点定义的规则是:MACD动能极强 且 均线偏离度适中)。假设在该同质化节点中,共计落入了 1000 个历史训练样本:

  • 其中 100 个样本随后进入大涨状态(目标值 +100);
  • 其中 50 个样本随后进入大跌状态(目标值 -100);
  • 剩余 850 个样本随后陷入震荡状态(目标值 0)。

Zorro 会计算该叶子节点的目标系统算数平均期望。即:
(100 * 100 + 50 * -100 + 850 * 0) / 1000 = +5

为了更直观地理解,我们可以将其梳理为以下图表:

text 复制代码
========================================================================
     ▶ 第一步:新数据进入漏斗 (Test Mode: 接收当前最新的一根 K 线)
========================================================================
  
        [ 当前特征集 Sigs ]
        Sig_MACD = 45.0
        Sig_Vola = 1.2
        Sig_Dist = -0.1
                 |
                 V
========================================================================
     ▶ 第二步:决策树模型层层筛选 (沿着规则树寻找归属)
========================================================================
                 
                       [ 节点 1 : Sig_MACD > 30? ]
                          /                   \
                      是(Yes)                否(No)
                        /                       \
           [ 节点 2 : Sig_Vola < 1.5? ]     [ 走向其他规则分支... ]
                   /              \
               是(Yes)           否(No)
                 /                  \
        【 叶子节点 A 】          【 叶子节点 B 】
     (当前K线匹配该规则,落入此桶)

========================================================================
     ▶ 第三步:调取"叶子节点 A"的先验概率 (从 Train Mode 的记忆中提取)
========================================================================

    +-------------------------------------------------------------+
    | 📦 叶子节点 A (Leaf Node) 档案室                              |
    |-------------------------------------------------------------|
    | 历史上所有满足 [MACD>30 且 Vola<1.5] 的 K 线共计 100 根。        |
    | 根据历史真实走势,这 100 根后续状态统计如下:                     |
    |                                                             |
    | 📈 状态分类      目标值(Target)   历史分布    经验概率P(x)      |
    | [1] 趋势大涨 -->     +100     -->   25次  -->    25%          |
    | [0] 陷入震荡 -->        0     -->   58次  -->    58%          |
    | [-1] 趋势大跌 -->    -100     -->   17次  -->    17%          |
    +-------------------------------------------------------------+
                 |
                 V
========================================================================
     ▶ 第四步:计算最终数学期望 (Zorro 的连续值转换公式)
========================================================================

    Output = ∑ [ 状态概率 * 状态目标值 ]
    Output = (25% * 100)  +  (58% * 0)  +  (17% * -100)
           =   +25        +      0      -     17
           =   +8
                 |
                 V
========================================================================
     ▶ 最终结果输出与执行
========================================================================

    [ Prediction = +8 ] 
    
    交给外层交易逻辑进行评判:
    "在当前的特征组合下,历史上做多具有净胜概率优势(+8分)。
     由于该期望值大于策略设定的入场阈值(如+5分),准许执行做多!"

2. 特征工程与标准化处理

在确认了目标变量后,我们选取了 5 个不同维度的特征。特征的选择需满足两大原则:1. 必须从多个正交(或低相关)的视角描述价格行为;2. 各特征间需相互配合,以便决策树模型发挥其捕捉非线性关系的优势。

指标 测度类型 计算逻辑
AtrRatio 波动率异动 ATR(20) / ATR(100)
Distance2Trend 趋势偏离度 Price / Lowpass(Price, 300) - 1
FractalDimension 市场微观分形 FractalDimension(Price, 50)
MACDHistogram 动能加速度 MACD(12, 26, 9) Histogram
AroonOscillator 市场内部动量 AroonOsc(300)

在将其喂入机器学习模型前,数据标准化(归一化)处理至关重要。考虑到不同指标的数学特性不同,我们采用了两种策略分类处理:静态阈值映射与动态滚动标准化。

  1. 均线偏离度 (Distance2Trend)
    • 特性: 取值主要在 -0.2 到 0.2 之间震荡,0 表示价格贴合均线。不受绝对标的价格影响,属于典型的平稳时间序列。
    • 处理公式: clamp(Dist2Trends[0] * 400, -100, 100)
  2. 分形维数 (FractalDimension)
    • 特性: 取值严格在 1.0 到 2.0 之间波动。我们使用静态数学映射,以理论中心点 1.5 作为 0 轴,1.0 映射为 -100,2.0 映射为 100。
    • 处理公式: clamp((Regimes[0] - 1.5) * 200, -100, 100)
  3. 动能加速度 (MACDHistogram)
    • 特性: 没有明确的物理边界,且其绝对波动率会随着行情级别动态变化,属于非平稳时间序列。静态映射会失效,因此采用计算滚动标准差的方法(Rolling Z-score)进行标准化。
  4. 波动性比率 (AtrRatio)
    • 特性: 数据大致分布在 0.5 到 2.0 之间,中心值为 1.0。高于 1.0 代表短期波动性爆发,低于则为极度萎缩状态。
    • 处理公式: clamp((VolaRatios[0] - 1.0) * 100, -100, 100)
  5. 阿隆振荡器 (AroonOscillator)
    • 特性: 本身的数学定义即被限制在 [-100, 100] 之间,无需额外处理。

经过缩放处理后,所有输入特征均被严格规范在 [-100, 100] 的闭区间内,有效避免了模型对绝对量级较大指标的过度拟合。

下图显示原始特征。

下图显示经过缩放的特征。

3. 模型训练

  • 标的与样本: BTCUSDT,4 小时 K 线。
  • 回测区间: 2018 年 - 2025 年。
  • 验证方法: 滚动前向优化(Walk-Forward Optimization, WFO=10),训练集比例为85%。整个时间序列被划分为 10 个周期,在每个周期的训练集上拟合 1 个决策树模型,并在未参与训练的样本外测试集上生成预测。

下图显示样本外的预测结果。

我们可以将决策树模型的输出值视为未来一天走势的"多空动能温度计"(其取值范围大多分布在 [-60, 30] ):

  • 0 轴附近: 预示未来一天多空力量陷入死水或极致势均力敌,属高杂音区,应规避交易。
  • 显著高于 0: 预示市场结构倾向于上涨的期望为正,多头占据统计学优势,适合做多。
  • 显著低于 0: 预示市场结构倾向于下跌的期望为负,空头占据统计学优势,适合做空。

4. 策略逻辑与源码实现

基于上述模型信号,我们设计了简单的交易策略:

  • 预测信号 > 做多阈值 时,多头开仓(或单边市保持多头);
  • 预测信号 < 0 时,多头绝对动能丧失,多头平仓;
  • 预测信号 < 做空阈值 时,空头开仓;
  • 预测信号 > 0 时,空头绝对动能丧失,空头平仓。

Zorro C-Lite 源码:

c 复制代码
#include <profile.c>
#include <utils.c>
#include <myindicators.c>

void tradeStrategy() {
    // ==================== 计算指标 ====================
	vars Prices = series(price());
    vars Trends = series(LowPass(Prices,300));
    vars Dist2Trends = series(Prices[0]/Trends[0]-1);
    vars Regimes = series(FractalDimension(Prices,50));
    MACD(Prices,12,26,9);
    vars Moms = series(rMACDHist);
    var ATR100 = ATR(100);
    vars VolaRatios = series(ATR(20)/ATR100);
    vars Aroons = series(AroonOsc(300));

    var ChannelUpper = Trends[0]+1.*ATR100;
    var ChannelLower = Trends[0]-1.*ATR100;
    var State = 0;
    if(rising(Trends) && Prices[0] > ChannelUpper) {
        State = 1;
    } else if (falling(Trends) && Prices[0] < ChannelLower) {
        State = -1;
    }

    // ==================== 特征缩放 ====================
    vars Sigs0 = series(clamp(Dist2Trends[0]*400,-100,100));
    vars Sigs1 = series(clamp((Regimes[0]-1.5)*200,-100,100));

    var MacdStd = StdDev(Moms,200);
    if(MacdStd < 0.00001) MacdStd = 0.00001;
    vars Sigs2 = series(clamp((Moms[0]/MacdStd)*33.3,-100,100));

    vars Sigs3 = series(clamp((VolaRatios[0]-1.0)*100,-100,100));
    vars Sigs4 = series(Aroons[0]);

	// ==================== 目标变量 ====================
    var Target = State*100;

	// ==================== 机器学习 ====================
	// 预测1天后的市场结构
	int Offset = ifelse(Train, 6, 0);
	var Prediction = adviseLong(DTREE, Target,
	                            Sigs0[Offset],
								Sigs1[Offset],
								Sigs2[Offset],
								Sigs3[Offset],
								Sigs4[Offset]);

	// ==================== 交易逻辑 ====================
	var EntryThresholdLong = 2;
	var EntryThresholdShort = -5;
	if(NumOpenLong > 0 && Prediction < 0)
	    exitLong();
	if(NumOpenShort > 0 && Prediction > 0)
	    exitShort();
	if(NumOpenLong == 0 && Prediction > EntryThresholdLong)
	    enterLong();
	if(NumOpenShort == 0 && Prediction < EntryThresholdShort)
	    enterShort();

	// ==================== 图表绘制 ====================
    if(Test && !is(LOOKBACK)) {
        // plot("Dist2Trend", Dist2Trends, NEW, RED);
        // plot("Regime", Regimes, NEW, RED);
        // plot("MACDHist", Moms, NEW, RED);
        // plot("VolaRatio", VolaRatios, NEW, RED);
        // plot("AroonOsc", Aroons, NEW, RED);

        // plot("Sig_Dist", Sigs0, NEW, BLUE);
        // plot("Sig_Regime", Sigs1, NEW, BLUE);
        // plot("Sig_MACD", Sigs2, NEW, BLUE);
        // plot("Sig_Vola", Sigs3, NEW, BLUE);
        // plot("Sig_Aroon", Sigs4, NEW, BLUE);

        plot("ChannelUpper", ChannelUpper, MAIN|BAND1, GREY);
        plot("ChannelLower", ChannelLower, MAIN|BAND2, GREEN+TRANSP);
        plot("State", State, NEW, RED);

        plot("Prediction", Prediction, NEW|BARS, GREEN+TRANSP);
        plot("Pred+", EntryThresholdLong, 0, GREY);
        plot("Pred-", EntryThresholdShort, 0, GREY);
    }
}

function run() {
    // --------------------------------------------------------- //
    // Zorro 环境设置
    // --------------------------------------------------------- //

    // 日志
    set(LOGFILE);
    Verbose = 3;

    // 图表
    set(PLOTNOW);
    setf(PlotMode,PL_DIFF);
    PlotScale = 8;
    PlotHeight2 = 320;

    // 训练参数
    set(PARAMETERS|RULES);
    setf(TrainMode,TRADES);
    DataSplit = 85;
    NumWFOCycles = 10;
    NumCores = 0;

    // K线生成规则:7*24小时交易机制
    resf(BarMode,BR_WEEKEND);
    StartWeek = 0;
    EndWeek = 62359;
    StartMarket = 0;
    EndMarket = 2359;
    BarPeriod = 240;
    BarZone = UTC;
    BarOffset = 0;
    TickFix = 60000;
	if(Live) TickFix = 0;

    // 测试样本区间
    StartDate = 20180101;
    EndDate = 20251230;
    LookBack = 5000;

    // 延迟成交模拟:下一根k线开盘价执行进/出场
    Fill = 3;

    // 固定规模
    Lots = 100;

    // --------------------------------------------------------- //
    // 策略主体调用
    // --------------------------------------------------------- //

    asset("BTCUSDT");
    Leverage = 5;
    MarginCost = priceClose()*LotAmount/Leverage;
    tradeStrategy();
}

5. 回测结果

WFO 样本外测试结果如下,表现差强人意:

  • 盈利因子: 1.23
  • 年均交易次数: 36 笔
  • 胜率: 36%

虽然实现了微弱盈利,但净值曲线未能呈现稳定上升,且最大回撤巨大,远远达不到实盘部署的标准。

分析历史交易发现:

  1. 决策树模型有效削减了中长期趋势回调时的"假动作"。例如在 2023 年 8-9 月,市场处于中期下行结构中,8 月末曾出现过极其暴力的超跌反弹。此时传统均线/动能指标纷纷发出多头反转信号,但在 DTREE 模型中,其连续合成的期望值依然为负数。它在宏观视角上坚守了空头观点,完美规避了这种反复挨打的宽幅震荡噪音。但这同时也牺牲了平仓的时效性,导致利润回撤较大。
  1. 该模型存在一个致命的逻辑盲区。在长期大牛市行情的末期(或者是多头结构内部的复杂整理期),上涨动能开始衰竭,价格往往会围绕长周期均线展开宽幅震荡。此时由于价格表现出"震荡向上"或"高位派发"的特征,长周期的多头逻辑其实并未破灭,但决策树模型对动能衰竭过于敏感,其输出持续低于 0 线。这直接导致策略在牛市的高位盘整区不断做空并亏损。
  1. 当前特征与设定目标所孕育出的策略,本质上仍属于"趋势跟随策略"。所以它逃脱不了趋势策略的核心宿命:在窄幅低波动震荡中,它做到了规避(相较于传统均线交叉显著减少了亏损频次);但在宽幅且方向反复的整理行情中,它依然会遭受持续亏损。

6. 经验总结

此次实验虽然尚未打造稳定盈利的策略,但验证了 Zorro 平台 adviseLong 对连续期望值的输出机制,同时证实了通过机器学习技术确实能对市场结构(而非绝对价格)进行概率建模。

针对"进场/平仓时机滞后"以及"宽幅震荡持续亏损"的问题,当前的决策树依然没能精确识别市场结构。接下来将围绕两个方向继续优化:

  1. 当前选用的 5 个特征预测能力有限,需要挖掘预测能力更强的特征。
  2. 机器学习的输出不必直接用来生成交易信号;更加成熟稳妥的做法,可能是利用决策树的分类能力,将其作为传统动能策略的趋势滤波器。
相关推荐
是瑶瑶子啦2 小时前
【机器学习】Test-Time Training (TTT) / Test-Time Adaptation (TTA)介绍
人工智能·机器学习
輕華2 小时前
矿物成分数据智能分类实战(二):以平均值填充数据集的 XGBoost 与 AdaBoost 为例
人工智能·机器学习·分类
计算机安禾2 小时前
【C语言程序设计】第33篇:二级指针与指针数组
c语言·开发语言·数据结构·c++·算法·visual studio code·visual studio
falldeep2 小时前
LLM中的强化学习方法分类
开发语言·人工智能·机器学习
落地加湿器2 小时前
Acwing算法课图论与搜索笔记
c++·笔记·算法·图论·dfs·bfs·图搜索算法
cui_ruicheng2 小时前
C++ 数据结构进阶:哈希表原理
数据结构·c++·算法·哈希算法
黎阳之光2 小时前
黎阳之光:AI硬核技术锚定十五五,赋能海空天全域智能感知
大数据·人工智能·算法·安全·数字孪生
天天爱吃肉82182 小时前
【新能源汽车NTC+VCU温度采集全链路解析:原理、试验与测不准根源定位】
功能测试·嵌入式硬件·机器学习·信息可视化·汽车
卷福同学2 小时前
【养虾日记】如何让Openclaw联网搜索技能
人工智能·算法