用Python重塑经典策略:移动平均线交叉·综合运用
◍ 用逻辑回归把双均线信号变成概率
这段 MT5 代码把「短均线是否上穿慢均线」压成了一个 0~1 的概率输出,核心是一个极简的逻辑回归模型。输入只有两个:5 周期 EMA 读数、50 周期 EMA 读数,靠三个系数 b_nort、b_one、b_two 加权后过 sigmoid。 训练函数里学习率写死 0.3,按 size 条样本做梯度下降,每次用当前预测和 target 的误差反推三个系数。注意更新式里乘了 current_prediction*(1-current_prediction),这就是 sigmoid 求导的结果,手敲容易漏这项。 实盘取数在 current_forecast 里完成:iMA 取当前图表的 5 与 50 EMA,CopyBuffer 抓最新一根。预测值过 0.5 倾向开多、低于 0.5 倾向开空,但外汇和贵金属杠杆高,这个阈值只是模型置信切分,不代表胜率保证。 让小布替你跑这套 把 learning_rate 从 0.3 改成 0.05 在 EURUSD 的 M15 上回测,能看到系数震荡更小,但训练收敛需要的样本数明显变多,属于典型的bias-variance权衡。
class="type">class="kw">double model_predict(class="type">class="kw">double input_one,class="type">class="kw">double input_two) { class=class="str">"cmt">//We simply class="kw">return the probability that the shorter moving average will rise above the slower moving average class="type">class="kw">double prediction = class="num">1 / (class="num">1 + MathExp(-(b_nort + (b_one * input_one) + (b_two * input_two)))); class="kw">return prediction; } class=class="str">"cmt">//+----------------------------------------------------------------------+ class=class="str">"cmt">//|This function is responsible for training our model | class=class="str">"cmt">//+----------------------------------------------------------------------+ class="type">bool train_model(class="type">void) { class=class="str">"cmt">//Update the coefficients class="type">class="kw">double learning_rate = class="num">0.3; for(class="type">int i = class="num">0; i < size; i++) { class=class="str">"cmt">//Get a prediction from the model current_prediction = model_predict(ma_5_reading[i],ma_50_reading[i]); class=class="str">"cmt">//Update each coefficient b_nort = b_nort + learning_rate * (target[i] - current_prediction) * current_prediction * (class="num">1 - current_prediction) * class="num">1; b_one = b_one + learning_rate * (target[i] - current_prediction) * current_prediction * (class="num">1 - current_prediction) * ma_5_reading[i]; b_two = b_two + learning_rate * (target[i] - current_prediction) * current_prediction * (class="num">1 - current_prediction) * ma_50_reading[i]; Print(current_prediction); } class=class="str">"cmt">//Show updated coefficient values Print("Updated coefficient values"); Print(b_nort); Print(b_one); Print(b_two); class="kw">return(true); } class=class="str">"cmt">//Get the model&class="macro">#x27;s current forecast class="type">void current_forecast() { class=class="str">"cmt">//Get indicator data ma_50 = iMA(_Symbol,PERIOD_CURRENT,class="num">50,class="num">0,MODE_EMA,PRICE_CLOSE); ma_5 = iMA(_Symbol,PERIOD_CURRENT,class="num">5,class="num">0,MODE_EMA,PRICE_CLOSE); CopyBuffer(ma_50,class="num">0,class="num">0,class="num">1,ma_50_reading); CopyBuffer(ma_5,class="num">0,class="num">0,class="num">1,ma_5_reading); class=class="str">"cmt">//Get model forecast model_predict(ma_5_reading[class="num">0],ma_50_reading[class="num">0]); interpret_forecast(); } class=class="str">"cmt">//+----------------------------------------------------------------------+ class=class="str">"cmt">//|This function is responsible for taking action on our model&class="macro">#x27;s forecast| class=class="str">"cmt">//+----------------------------------------------------------------------+ class="type">void interpret_forecast(class="type">void) { if(current_prediction > class="num">0.5) { state = class="num">1; Trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,min_volume * lot_multiple,ask,class="num">0,class="num">0,"Volatitlity Doctor AI"); } if(current_prediction < class="num">0.5) { state = class="num">0; Trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,min_volume * lot_multiple,bid,class="num">0,class="num">0,"Volatitlity Doctor AI"); } } class=class="str">"cmt">//+----------------------------------------------------------------------+ class=class="str">"cmt">//|This function is responsible for calculating our SL & TP values | class=class="str">"cmt">//+----------------------------------------------------------------------+ class="type">void CheckAtrStop() {
「按品种遍历持仓并套用 ATR 止损」
EA 要改挂止损,第一步不是算指标,而是先把当前账户里所有持仓翻一遍。用 PositionsTotal() 拿到总数,倒序循环避免删除元素时错位,再用 PositionGetSymbol(i) 取每个持仓的品种名,只处理跟当前图表 _Symbol 一致的仓位。 匹配到本品种后,依次用 PositionGetInteger 和 PositionGetDouble 抓 ticket、开仓价、持仓类型、当前 SL。外汇与贵金属杠杆高,持仓方向错了却没止损,浮亏可能快速吞掉保证金,所以这段过滤必须在下单逻辑之前跑通。 买仓的 ATR 止损放在 ask 减去 (min_distance*sl_width)/2,止盈放 ask 加上 min_distance*sl_width;卖仓镜像处理用 bid。只有当原 SL 小于新算出的 ATR 止损,或原 SL 为 0 时,才调用 Trade.PositionModify 改写,避免每 tick 无谓下单。 下面这段是原逻辑的核心骨架,直接贴进 MT5 能看清字段取值顺序。
for(class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--) { class="type">class="kw">string symbol = PositionGetSymbol(i); if(_Symbol == symbol) { class="type">class="kw">ulong ticket = PositionGetInteger(POSITION_TICKET); class="type">class="kw">double position_price = PositionGetDouble(POSITION_PRICE_OPEN); class="type">long type = PositionGetInteger(POSITION_TYPE); class="type">class="kw">double current_stop_loss = PositionGetDouble(POSITION_SL); if(type == POSITION_TYPE_BUY) { class="type">class="kw">double atr_stop_loss = NormalizeDouble(ask - ((min_distance * sl_width)/class="num">2),_Digits); class="type">class="kw">double atr_take_profit = NormalizeDouble(ask + (min_distance * sl_width),_Digits); if((current_stop_loss < atr_stop_loss) || (current_stop_loss == class="num">0)) { Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit); } } else if(type == POSITION_TYPE_SELL) { class="type">class="kw">double atr_stop_loss = NormalizeDouble(bid + ((min_distance * sl_width)/class="num">2),_Digits); class="type">class="kw">double atr_take_profit = NormalizeDouble(bid - (min_distance * sl_width),_Digits); } } }
把 ATR 止损接进持仓生命周期
这段逻辑干一件事:只要当前止损比 ATR 算出来的止损更宽,或者止损还是 0(没设),就用 PositionModify 把止损止盈改成 ATR 值。注意条件是「大于」才改,意味着它只收紧不放宽,避免被小幅波动反复改写。 ManageTrade 本身只是个壳,实际调用了 CheckAtrStop 去执行上面的判断和修改。这种分层写法方便你以后把别的风控规则也挂进 ManageTrade,而不用动 OnTick 主干。 初始化时先抓两个硬限制:min_volume 来自 SYMBOL_VOLUME_MIN,min_distance 来自 SYMBOL_TRADE_STOPS_LEVEL,这两个值决定了订单最小手数和止损最少要离市价多少点。外汇和贵金属杠杆高,stops level 在重大数据前可能瞬间扩大,改 SL 失败要在日志里兜底捕获。 OnTick 里分两条线:无持仓就跑 current_forecast 找信号,有持仓就进 ManageTrade 管风控。你可以直接把这段塞进自己的 EA 模板,把 ATR 周期和倍数调成自己品种的参数(例如 XAUUSD 用 14 周期、2.5 倍)到 MT5 回测看胜率变化。
if((current_stop_loss > atr_stop_loss) || (current_stop_loss == class="num">0)) { Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit); } } } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|This function is responsible for updating our SL&TP values | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ManageTrade() { CheckAtrStop(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//Define important global variables min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); class=class="str">"cmt">//Train the model get_training_data(); if(train_model()) { interpret_forecast(); } class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//Get updates bid and ask prices bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); if(PositionsTotal() == class="num">0) { current_forecast(); } if(PositionsTotal() > class="num">0) { ManageTrade(); } }
◍ 一点提醒
把均线交叉当预测目标,比直接猜价格涨跌更省算力,这背后有个直观现象:两小时内价格可能反复转向两三次,同周期均线却可能一次都没交叉,方向切换更慢,模型更容易抓规律。 向后选择做特征消除时,每一步按特征对准确率的影响剔掉一个,能留下信息量最高的那批,但噪声一大就可能误删其实有用的特征,这点调参时要留神。 额外叠了 MACD、AO、Aroon、CCI、Percent Return 这些指标想抬升交叉预测率,实测它们对模型表现的贡献飘忽,选哪些更靠经验而非公式。外汇和贵金属波动剧烈、杠杆风险高,这类统计策略只证明「交叉可预测」的概率倾向,落地前务必在 MT5 用附带的 Moving_Average_CrossoverAI.mq5 跑自有品种验证。