「用 LDA 把高维行情压成可交易信号」
「用 LDA 把高维行情压成可交易信号」
线性判别分析(LDA)本质是一种有监督降维:它把带类别标签的高维数据,线性投影到低维子空间,让不同类尽量分开。子空间维度不会超过类别数,所以做二分类时最多压到 1 维。和 PCA 找全样本方差最大轴不同,LDA 直接按已知分类找分离轴——高方差特征不代表预测有用,这是实盘里容易踩的坑。 在 MT5 里落地,直接调 dataanalysis.mqh 里的 CLDA 类即可,它按特征值降序排好投影向量,取前 k 个就能用。以 2022 年 USDJPY 日线为例,把收盘价做成 4 种数据:离散化涨跌标签、归一化到 [-1.0,+1.0] 的原价、连续收盘价变化、未修改收盘价。每种都经 m_signal_regulizer 划出中性区降噪,训练点数量由 m_signal_points 控。 LDA 输出是系数向量,新数据点与之点积后得到投影值,再和训练集各类“质心”(类中值)比距离归类。为接进 ExpertSignal 的 0–100 权重,先把双精度投影标准化到 [-1.0,+1.0](负看跌、正看涨),再映射成整数。回测中,无修改收盘价数据集在信号模块给出持续正向表现,而资金管理模块里“离散化区间值变化”数据集最值得盯。外汇和贵金属杠杆高,LDA 只是降噪分类辅助,实盘前务必用策略测试器跑自有品种验证。
◍ 数据集方法的三种变量变换接口
在 MT5 自定义指标或 EA 里调用数据集方法时,核心是一个带索引和类型标记的读取函数。它从价格缓冲区按 int 索引取数,返回 double,具体数值含义取决于你选的数据集类型。 数据集类型用整数区分:0 代表离散化变量(Discretized),1 代表归一化变量(Normalized),2 代表连续化变量(Continuized)。实盘里若把 1 号归一化输出直接喂给分类器,金价在 30 根 K 线内的波动区间可能被压缩到 [0,1],肉眼难辨拐点。 调用前先确认 Variables 标记:它决定当前取的是原始变量还是分类器中间量。开 MT5 把这段声明贴进头文件,改 type 参数从 0 切到 2,能在策略测试器里对比三种变换对信号频率的影响,外汇与贵金属杠杆高,回测结论仅作概率参考。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Data Set method | class=class="str">"cmt">//| INPUT PARAMETERS | class=class="str">"cmt">//| Index - class="type">int, read index within price buffer. | class=class="str">"cmt">//| | class=class="str">"cmt">//| Variables | class=class="str">"cmt">//| - whether data component is variables or . | class=class="str">"cmt">//| classifier. | class=class="str">"cmt">//| OUTPUT | class=class="str">"cmt">//| class="type">class="kw">double - Data depending on data set type | class=class="str">"cmt">//| | class=class="str">"cmt">//| DATA SET TYPES | class=class="str">"cmt">//| class="num">1. Discretized variables. - class="num">0 | class=class="str">"cmt">//| | class=class="str">"cmt">//| class="num">2. Normalized variables. - class="num">1 | class=class="str">"cmt">//| | class=class="str">"cmt">//| class="num">3. Continuized variables. - class="num">2 | class=class="str">"cmt">//| |
信号原始数据的四种取法
CSignalDA::Data() 是方向性信号类的原始数据出口,靠 m_signal_type 在 0~3 之间切换四种算法。调用前先对 close / low / high 三个缓冲区做 Refresh(-1),确保拿到的是当前未定型 bar 的最新值,否则在实时 tick 下可能读到上一帧残留。 当 Variables 为 true 时,先统一用 fabs(ΔClose) < m_signal_regulizer * Range(Index) 做噪声过滤。m_signal_regulizer 是伸缩阈值,Range(Index) 取该 bar 的波动幅度,二者相乘才随品种波动自适应,硬阈值在黄金和欧美的表现会差出一个量级。 type 0 返回离散标签:达标给 1.0(视为无方向),否则按收盘价升跌返 2.0 或 0.0,适合做分类信号。type 1 把净价差除以 max(Point, 实体高度) 做归一,输出连续值,跨品种可比。type 2 直接返裸价差,type 3 在噪声区内返前一 bar 收盘价、区外返当前收盘价,偏重价格衔接。 实盘接 MT5 时,建议先把 m_signal_regulizer 从默认猜值改成 0.1~0.3 区间扫一遍,外汇与贵金属杠杆高、滑点跳空频繁,阈值过小会放大噪音触发。
class="type">class="kw">double CSignalDA::Data(class="type">int Index,class="type">bool Variables=true) { m_close.Refresh(-class="num">1); m_low.Refresh(-class="num">1); m_high.Refresh(-class="num">1); if(Variables) { if(m_signal_type==class="num">0) { class="kw">return(fabs(Close(StartIndex()+Index)-Close(StartIndex()+Index+class="num">1))<m_signal_regulizer*Range(Index)?class="num">1.0:((Close(StartIndex()+Index)>Close(StartIndex()+Index+class="num">1))?class="num">2.0:class="num">0.0)); } else if(m_signal_type==class="num">1) { if(fabs(Close(StartIndex()+Index)-Close(StartIndex()+Index+class="num">1))<m_signal_regulizer*Range(Index)) { class="kw">return(class="num">0.0); } class="kw">return((Close(StartIndex()+Index)-Close(StartIndex()+Index+class="num">1))/fmax(m_symbol.Point(),fmax(High(StartIndex()+Index),High(StartIndex()+Index+class="num">1))-fmin(Low(StartIndex()+Index),Low(StartIndex()+Index+class="num">1)))); } else if(m_signal_type==class="num">2) { if(fabs(Close(StartIndex()+Index)-Close(StartIndex()+Index+class="num">1))<m_signal_regulizer*Range(Index)) { class="kw">return(class="num">0.0); } class="kw">return(Close(StartIndex()+Index)-Close(StartIndex()+Index+class="num">1)); } else if(m_signal_type==class="num">3) { if(fabs(Close(StartIndex()+Index)-Close(StartIndex()+Index+class="num">1))<m_signal_regulizer*Range(Index)) { class="kw">return(Close(StartIndex()+Index+class="num">1)); } class="kw">return(Close(StartIndex()+Index)); } }
「决策向量到止损价位的映射」
分类器给出 _da 后,并不是直接拿去下单,而是把它换算成具体止损坐标。先看多头分支:_da 乘以最近 m_trailing_points 根 K 线最低价的极差(_max_l-_min_l),再加 _min_l,得到挂多单的止损参考 m_long_sl。
空头分支镜像处理,用最高价极差(_max_h-_min_h)叠加 _max_h,逻辑对称。这里隐含一个前提:你传入的 m_trailing_points 最好 ≥ 20,否则极差采样太窄,止损容易被毛刺打掉,外汇与贵金属品种尤其要当心这种高频假突破的高风险。
实盘落地时,代码里用 m_symbol.StopsLevel()*m_symbol.Point() 去卡平台最小止损距离,再 NormalizeDouble 到品种小数位。若持仓已有止损(pos_sl≠0),就以 pos_sl 为基准,否则退回开仓价——这条分支决定了 trailing stop 是否覆盖原止损,开 MT5 把 m_trailing_points 从 20 调到 50 跑一遍回测就能看出滑点差异。
class="kw">return(fabs(Close(StartIndex()+Index)-Close(StartIndex()+Index+class="num">1))<m_signal_regulizer*Range(Index)?class="num">1.0:((Close(StartIndex()+Index)>Close(StartIndex()+Index+class="num">1))?class="num">2.0:class="num">0.0)); } class=class="str">"cmt">// best eigen vector is the first for(class="type">int v=class="num">0;v<__S_VARS;v++){ _unknown_centroid+= (_w[class="num">0][v]*_z[class="num">0][v]); } class=class="str">"cmt">// if(fabs(_centroids[__S_BULLISH]-_unknown_centroid)<fabs(_centroids[__S_BEARISH]-_unknown_centroid) && fabs(_centroids[__S_BULLISH]-_unknown_centroid)<fabs(_centroids[__S_WHIPSAW]-_unknown_centroid)) { _da=(class="num">1.0-(fabs(_centroids[__S_BULLISH]-_unknown_centroid)/(fabs(_centroids[__S_BULLISH]-_unknown_centroid)+fabs(_centroids[__S_WHIPSAW]-_unknown_centroid)+fabs(_centroids[__S_BEARISH]-_unknown_centroid)))); } else if(fabs(_centroids[__S_BEARISH]-_unknown_centroid)<fabs(_centroids[__S_BULLISH]-_unknown_centroid) && fabs(_centroids[__S_BEARISH]-_unknown_centroid)<fabs(_centroids[__S_WHIPSAW]-_unknown_centroid)) { _da=-class="num">1.0*(class="num">1.0-(fabs(_centroids[__S_BEARISH]-_unknown_centroid)/(fabs(_centroids[__S_BULLISH]-_unknown_centroid)+fabs(_centroids[__S_WHIPSAW]-_unknown_centroid)+fabs(_centroids[__S_BEARISH]-_unknown_centroid)))); } if(_da>class="num">0.0) { result=class="type">int(round(class="num">100.0*_da)); } if(_da<class="num">0.0) { result=class="type">int(round(-class="num">100.0*_da)); } class="type">int _index =StartIndex(); class="type">class="kw">double _min_l=Low(_index),_max_l=Low(_index),_min_h=High(_index),_max_h=High(_index); for(class="type">int d=_index;d<m_trailing_points+_index;d++) { _min_l=fmin(_min_l,Low(d)); _max_l=fmax(_max_l,Low(d)); _min_h=fmin(_min_h,High(d)); _max_h=fmax(_max_h,High(d)); } if(Type==POSITION_TYPE_BUY) { _da*=(_max_l-_min_l); _da+=_min_l; } else if(Type==POSITION_TYPE_SELL) { _da*=(_max_h-_min_h); _da+=_max_h; } m_long_sl=ProcessDA(StartIndex(),POSITION_TYPE_BUY); class="type">class="kw">double level =NormalizeDouble(m_symbol.Bid()-m_symbol.StopsLevel()*m_symbol.Point(),m_symbol.Digits()); class="type">class="kw">double new_sl=NormalizeDouble(m_long_sl,m_symbol.Digits()); class="type">class="kw">double pos_sl=position.StopLoss(); class="type">class="kw">double base =(pos_sl==class="num">0.0) ? position.PriceOpen() : pos_sl; class=class="str">"cmt">//---
◍ 止损位与仓位的计算落点
这段逻辑把动态止损和仓位挂在同一个波动度量 _da 上:先取从 StartIndex 起 m_money_points 根 K 线的高低极值差,再乘加得到 _da,最后用 Bid 减去它得出买单止损价。 仓位部分用账户自由保证金的百分比反推手数:_da_1_lot_loss 是每点波动对应的 1 手金额,lot = (百分比/100 × 自由保证金) / 该值。若保证金校验为负,就退回平台允许的最大手数,再走 Optimize 微调。 外汇与贵金属杠杆高,波动放大时 _da 会快速拉伸,止损距离和所需保证金同步跳升,实盘前建议在 MT5 用不同 m_money_points 跑一遍观察止损跨度变化。
sl=EMPTY_VALUE; tp=EMPTY_VALUE; if(new_sl>base && new_sl<level) sl=new_sl; class="type">int _index =StartIndex(); class="type">class="kw">double _min_l=Low(_index),_max_h=High(_index); for(class="type">int d=_index;d<m_money_points+_index;d++) { _min_l=fmin(_min_l,Low(d)); _max_h=fmax(_max_h,High(d)); } _da*=(_max_h-_min_l); _da+=(_max_h-_min_l); class="type">class="kw">double _da=ProcessDA(StartIndex()); if(m_symbol==NULL) class="kw">return(class="num">0.0); sl=m_symbol.Bid()-_da; class=class="str">"cmt">//--- select lot size class="type">class="kw">double _da_1_lot_loss=(_da/m_symbol.TickSize())*m_symbol.TickValue(); class="type">class="kw">double lot=((m_percent/class="num">100.0)*m_account.FreeMargin())/_da_1_lot_loss; class=class="str">"cmt">//--- calculate margin requirements for class="num">1 lot if(m_account.FreeMarginCheck(m_symbol.Name(),ORDER_TYPE_BUY,lot,m_symbol.Ask())<class="num">0.0) { printf(__FUNCSIG__" insufficient margin for sl lot! "); lot=m_account.MaxLotCheck(m_symbol.Name(),ORDER_TYPE_BUY,m_symbol.Ask(),m_percent); } class=class="str">"cmt">//--- class="kw">return trading volume class="kw">return(Optimize(lot));