用Python重塑经典策略:移动平均线交叉·综合运用
📘

用Python重塑经典策略:移动平均线交叉·综合运用

第 3/3 篇

◍ 用逻辑回归把双均线信号变成概率

这段 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权衡。

MQL5 / C++
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 能看清字段取值顺序。

MQL5 / C++
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 回测看胜率变化。

MQL5 / C++
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 跑自有品种验证。

常见问题

可以用逻辑回归把历史均线交叉后的价格涨跌作为标签训练,输出交叉信号成立的概率,只对高概率信号下单。
遍历当前所有持仓,按各自品种的 ATR 值乘以系数计算止损距离,批量挂止损单即可。
小布可接入你的策略逻辑,对品种页实时计算均线交叉概率与 ATR 止损参考,省去手动回测。
建议在持仓生命周期里每根 K 线刷新 ATR,若突破原止损距则移动止损,锁定部分利润。
样本内准确率很高但实盘连亏,多是用了未来函数或品种特性过强,需交叉验证并限制特征。