量化魔方

标题: 手工单自动止损 + 追踪止损 + 能量衰竭平仓(源码) [打印本页]

作者: 量化魔方    时间: 昨天 15:55
标题: 手工单自动止损 + 追踪止损 + 能量衰竭平仓(源码)
//+------------------------------------------------------------------+
//|                                  ManualProtection_EnergyExit.mq5 |
//|         手工单自动止损 + 追踪止损 + 能量衰竭平仓                   |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025"
#property link      ""
#property version   "1.10"

#include <Trade\Trade.mqh>

//+------------------------------------------------------------------+
//| 输入参数                                                          |
//+------------------------------------------------------------------+
input group "══════════ 手工单保护 ══════════"
input bool     InpAutoStopManual   = true;   // 自动设置止损 (固定350点)
input bool     InpUseTrailing      = true;   // 启用追踪止损
input bool     InpUseATRTrailing   = true;   // ATR动态距离 (否则固定点数)
input double   InpTrailingATRMult  = 1.5;    // 追踪止损ATR倍数
input int      InpFixedTrailing    = 350;    // 固定追踪点数
input int      InpTrailingStep     = 80;     // 最小移动步长(点)
input int      InpATRPeriod        = 14;     // ATR计算周期

input group "══════════ 能量衰竭平仓 ══════════"
input bool     InpUseEnergyExit    = true;   // 启用能量衰竭平仓
input int      RegPeriod           = 20;     // 线性回归周期
input double   InpMinSlopeATR      = 0.02;   // 最小标准化斜率 (基准阈值)
input int      InpATRPercentileWin = 100;    // ATR百分位窗口(根K线)
input double   InpChanWidthUp      = 0.9;    // 通道宽(仅用于判断波动率状态)
input double   InpChanWidthDn      = 2.1;

//+------------------------------------------------------------------+
//| 全局变量                                                          |
//+------------------------------------------------------------------+
CTrade   m_tradeManual;      // 修改/平仓手工单 (无魔术号)
int      atrHandle;          // ATR指标句柄

//+------------------------------------------------------------------+
//| 线性回归                                                          |
//+------------------------------------------------------------------+
bool CalcLinearRegression(const double &y[], int period, int startIdx,
                          double &a, double &b)
  {
   if(period <= 0 || startIdx < 0)
     { a = 0; b = 0; return false; }

   double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
   for(int i = 0; i < period; i++)
     {
      int    idx = startIdx + i;
      double x   = i;
      double v   = y[idx];
      sumX  += x;
      sumY  += v;
      sumXY += x * v;
      sumX2 += x * x;
     }

   double n     = (double)period;
   double denom = n * sumX2 - sumX * sumX;
   if(MathAbs(denom) < 1e-10)
     {
      a = 0;
      b = sumY / n;
     }
   else
     {
      a = (n * sumXY - sumX * sumY) / denom;
      b = (sumY - a * sumX) / n;
     }
   return true;
  }

//+------------------------------------------------------------------+
//| ATR 百分位计算                                                    |
//+------------------------------------------------------------------+
double CalcATRPercentile(int lookback)
  {
   if(lookback < 5) return 50.0;
   double atr[];
   ArraySetAsSeries(atr, true);
   if(CopyBuffer(atrHandle, 0, 0, lookback, atr) < lookback)
      return 50.0;

   double current = atr[0];
   int    lessCnt = 0;
   for(int i = 1; i < lookback; i++)
      if(atr[i] < current) lessCnt++;
   return 100.0 * (double)lessCnt / (double)(lookback - 1);
  }

//+------------------------------------------------------------------+
//| 波动率状态 (低 / 正常 / 高)                                       |
//+------------------------------------------------------------------+
enum ENUM_VOL_REGIME
  {
   VOL_LOW = 0,
   VOL_NORMAL = 1,
   VOL_HIGH = 2
  };

ENUM_VOL_REGIME GetVolRegime()
  {
   double pct = CalcATRPercentile(InpATRPercentileWin);
   if(pct < 25.0) return VOL_LOW;
   if(pct > 75.0) return VOL_HIGH;
   return VOL_NORMAL;
  }

//+------------------------------------------------------------------+
//| 计算通道 (用于获取标准化斜率)                                     |
//+------------------------------------------------------------------+
bool CalcChannel(double &normSlope)
  {
   int need = MathMax(RegPeriod + 5, InpATRPercentileWin + 5);
   double close[], atr[];
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(atr, true);
   if(CopyClose(_Symbol, _Period, 0, need, close) < need) return false;
   if(CopyBuffer(atrHandle, 0, 0, need, atr) < need)      return false;

   double slope, intercept;
   if(!CalcLinearRegression(close, RegPeriod, 0, slope, intercept))
      return false;

   normSlope = (atr[0] > 0) ? slope / atr[0] : 0;
   return true;
  }

//+------------------------------------------------------------------+
//| 能量衰竭平仓 (所有手工单)                                         |
//+------------------------------------------------------------------+
void CheckEnergyExhaustion()
  {
   if(!InpUseEnergyExit) return;
   if(PositionsTotal() == 0) return;

   double normSlope;
   if(!CalcChannel(normSlope)) return;

   double slopeAbs = MathAbs(normSlope);
   ENUM_VOL_REGIME regime = GetVolRegime();
   bool exit = false;

   switch(regime)
     {
      case VOL_LOW:    if(slopeAbs < InpMinSlopeATR * 0.5) exit = true; break;
      case VOL_NORMAL: if(slopeAbs < InpMinSlopeATR * 0.3) exit = true; break;
      case VOL_HIGH:   if(slopeAbs < InpMinSlopeATR * 0.2) exit = true; break;
     }

   if(!exit) return;

   // 平掉所有手工单
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(!PositionSelectByTicket(ticket)) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;

      double profit = PositionGetDouble(POSITION_PROFIT);
      if(m_tradeManual.PositionClose(_Symbol))
         PrintFormat("能量衰竭平仓(持仓 %I64u): 盈亏=%.2f  波动=%s  斜率=%.4f",
                     ticket, profit, (regime==0?"低"regime==1?"正常":"高")), normSlope);
     }
  }

//+------------------------------------------------------------------+
//| 手工单自动止损 (固定350点)                                         |
//+------------------------------------------------------------------+
void AutoSetStopForManualPositions()
  {
   if(!InpAutoStopManual) return;

   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(!PositionSelectByTicket(ticket)) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;

      double sl = PositionGetDouble(POSITION_SL);
      if(sl == 0)
        {
         long   type      = PositionGetInteger(POSITION_TYPE);
         double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
         double tp        = PositionGetDouble(POSITION_TP);
         double slDist    = 350 * _Point;
         double newSl     = (type == POSITION_TYPE_BUY) ?
                            NormalizeDouble(openPrice - slDist, _Digits) :
                            NormalizeDouble(openPrice + slDist, _Digits);
         if(m_tradeManual.PositionModify(_Symbol, newSl, tp))
            PrintFormat("手工单 %I64u 自动设置止损 %.5f", ticket, newSl);
        }
     }
  }

//+------------------------------------------------------------------+
//| 手工单追踪止损                                                    |
//+------------------------------------------------------------------+
void TrailingStop()
  {
   if(!InpUseTrailing) return;

   double atr[1];
   if(InpUseATRTrailing)
     {
      ArraySetAsSeries(atr, true);
      if(CopyBuffer(atrHandle, 0, 0, 1, atr) != 1) return;
     }

   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(!PositionSelectByTicket(ticket)) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;

      double trailingDist = InpUseATRTrailing ?
                            InpTrailingATRMult * atr[0] :
                            InpFixedTrailing * _Point;
      double minStep = InpTrailingStep * _Point;

      double sl   = PositionGetDouble(POSITION_SL);
      double tp   = PositionGetDouble(POSITION_TP);
      long   type = PositionGetInteger(POSITION_TYPE);
      double bid  = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double ask  = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

      if(type == POSITION_TYPE_BUY)
        {
         double newSl = NormalizeDouble(bid - trailingDist, _Digits);
         if(newSl > sl + minStep)
            if(m_tradeManual.PositionModify(_Symbol, newSl, tp))
               PrintFormat("追踪止损上调(持仓 %I64u): %.5f", ticket, newSl);
        }
      else
        {
         double newSl = NormalizeDouble(ask + trailingDist, _Digits);
         if(newSl < sl - minStep)
            if(m_tradeManual.PositionModify(_Symbol, newSl, tp))
               PrintFormat("追踪止损下调(持仓 %I64u): %.5f", ticket, newSl);
        }
     }
  }

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   m_tradeManual.SetExpertMagicNumber(0);
   m_tradeManual.SetDeviationInPoints(30);
   m_tradeManual.SetTypeFilling(ORDER_FILLING_FOK);

   atrHandle = iATR(_Symbol, _Period, InpATRPeriod);
   if(atrHandle == INVALID_HANDLE)
     {
      Print("ATR指标句柄创建失败");
      return INIT_FAILED;
     }

   Print("手工单保护EA v1.10 (止损+追踪+能量衰竭)");
   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(atrHandle != INVALID_HANDLE)
      IndicatorRelease(atrHandle);
   Print("手工单保护EA卸载");
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   AutoSetStopForManualPositions();   // 补止损
   TrailingStop();                    // 追踪
   CheckEnergyExhaustion();           // 能量衰竭平仓
  }
//+------------------------------------------------------------------+






欢迎光临 量化魔方 (https://www.115ku.com/) Powered by Discuz! X3.5