近期我尝试利用 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) |
在将其喂入机器学习模型前,数据标准化(归一化)处理至关重要。考虑到不同指标的数学特性不同,我们采用了两种策略分类处理:静态阈值映射与动态滚动标准化。
- 均线偏离度 (Distance2Trend)
- 特性: 取值主要在 -0.2 到 0.2 之间震荡,0 表示价格贴合均线。不受绝对标的价格影响,属于典型的平稳时间序列。
- 处理公式:
clamp(Dist2Trends[0] * 400, -100, 100)
- 分形维数 (FractalDimension)
- 特性: 取值严格在 1.0 到 2.0 之间波动。我们使用静态数学映射,以理论中心点 1.5 作为 0 轴,1.0 映射为 -100,2.0 映射为 100。
- 处理公式:
clamp((Regimes[0] - 1.5) * 200, -100, 100)
- 动能加速度 (MACDHistogram)
- 特性: 没有明确的物理边界,且其绝对波动率会随着行情级别动态变化,属于非平稳时间序列。静态映射会失效,因此采用计算滚动标准差的方法(Rolling Z-score)进行标准化。
- 波动性比率 (AtrRatio)
- 特性: 数据大致分布在 0.5 到 2.0 之间,中心值为 1.0。高于 1.0 代表短期波动性爆发,低于则为极度萎缩状态。
- 处理公式:
clamp((VolaRatios[0] - 1.0) * 100, -100, 100)
- 阿隆振荡器 (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%
虽然实现了微弱盈利,但净值曲线未能呈现稳定上升,且最大回撤巨大,远远达不到实盘部署的标准。

分析历史交易发现:
- 决策树模型有效削减了中长期趋势回调时的"假动作"。例如在 2023 年 8-9 月,市场处于中期下行结构中,8 月末曾出现过极其暴力的超跌反弹。此时传统均线/动能指标纷纷发出多头反转信号,但在 DTREE 模型中,其连续合成的期望值依然为负数。它在宏观视角上坚守了空头观点,完美规避了这种反复挨打的宽幅震荡噪音。但这同时也牺牲了平仓的时效性,导致利润回撤较大。

- 该模型存在一个致命的逻辑盲区。在长期大牛市行情的末期(或者是多头结构内部的复杂整理期),上涨动能开始衰竭,价格往往会围绕长周期均线展开宽幅震荡。此时由于价格表现出"震荡向上"或"高位派发"的特征,长周期的多头逻辑其实并未破灭,但决策树模型对动能衰竭过于敏感,其输出持续低于 0 线。这直接导致策略在牛市的高位盘整区不断做空并亏损。

- 当前特征与设定目标所孕育出的策略,本质上仍属于"趋势跟随策略"。所以它逃脱不了趋势策略的核心宿命:在窄幅低波动震荡中,它做到了规避(相较于传统均线交叉显著减少了亏损频次);但在宽幅且方向反复的整理行情中,它依然会遭受持续亏损。
6. 经验总结
此次实验虽然尚未打造稳定盈利的策略,但验证了 Zorro 平台 adviseLong 对连续期望值的输出机制,同时证实了通过机器学习技术确实能对市场结构(而非绝对价格)进行概率建模。
针对"进场/平仓时机滞后"以及"宽幅震荡持续亏损"的问题,当前的决策树依然没能精确识别市场结构。接下来将围绕两个方向继续优化:
- 当前选用的 5 个特征预测能力有限,需要挖掘预测能力更强的特征。
- 机器学习的输出不必直接用来生成交易信号;更加成熟稳妥的做法,可能是利用决策树的分类能力,将其作为传统动能策略的趋势滤波器。