找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
首页论坛EAHub 外汇论坛
查看: 10|回复: 0

基于ATR的动态马丁EA

[复制链接]

4万

主题

54

回帖

4万

积分

管理员

积分
45954
发表于 2026-8-23 06:23:00 | 显示全部楼层 |阅读模式
这款马丁,基于ATR决定入场点,长期回测,无爆仓记录,克i呀测试使用,投资有风险,入场需谨慎

//+------------------------------------------------------------------+
//|                                                     ATR_Marti.mq5|
//|                       ATR+对冲加倍+回撤保护(Hedging账户)         |
//+------------------------------------------------------------------+
#property stricta
#property version   "1.10"
#property description "基于ATR的对冲加倍策略,只在新K线做决策 + 回撤保护。需要Hedging账户。"

#include <Trade/Trade.mqh>

//================== 输入参数 ==================//
input ENUM_TIMEFRAMES InpSignalTF         = PERIOD_CURRENT; // 触发决策的周期(新K线)
input ENUM_TIMEFRAMES InpATRTF            = PERIOD_CURRENT; // ATR计算周期
input int             InpATRPeriod        = 14;             // ATR周期
input double          InpATRMultiplier    = 1.0;            // ATR倍数
input bool            InpStartBuy         = true;           // 初始方向:true=BUY
input double          InpBaseLot          = 0.01;           // 初始手数
input double          InpMinProfitToClose = 0.0;            // 总浮盈达此值清仓(>0即盈利就平)
input double          InpMaxLot           = 10.0;           // 最大手数上限
input int             InpMaxSteps         = 20;             // 最大翻倍步数
input bool            InpOnlyOnNewBar     = true;           // 只在新K线执行
input int             InpSlippagePoints   = 10;             // 允许滑点(点)
input long            InpMagic            = 26091701;        // 魔术号
input ENUM_ORDER_TYPE_FILLING InpFilling  = ORDER_FILLING_IOC; // 成交策略

//—— 回撤保护 ——//
input bool   InpDD_Enable             = true;   // 开启回撤保护
input double InpDD_MaxDailyPct        = 5.0;    // 日内最大回撤(%),0=关闭
input double InpDD_MaxDailyMoney      = 0.0;    // 日内最大亏损(账户货币),0=关闭
input double InpDD_MaxTotalPct        = 20.0;   // 历史最大回撤(%),0=关闭(基于权益高水位)
input bool   InpDD_CloseAllOnTrigger  = true;   // 触发时是否立即清仓(本EA本品种)
input bool   InpDD_BlockOpenUntilReset= true;   // 触发后禁止开新仓,直到重置
input bool   InpDD_ResetOnNewDay      = true;   // 新的一天自动解除回撤阻断并重置日内基线
input int    InpDD_PauseMinutes       = 0;      // 额外暂停分钟数(0=不使用暂停时间)

//================== 全局对象与变量 ==================//
CTrade trade;
int    g_atr_handle = INVALID_HANDLE;
datetime g_last_bar_time = 0;

// 简单状态机
enum SEQ_STATE
{
   STATE_SINGLE = 0,
   STATE_MULTI_WAIT = 1,
   STATE_AFTER_ADD_SAME = 2
};
int    g_state = STATE_SINGLE;
string g_state_key;

// 仓位结构
struct PosInfo
{
   ulong               ticket;
   datetime            time;
   long                time_msc;
   ENUM_POSITION_TYPE  type;
   double              volume;
   double              open_price;
   double              profit;
};

double   g_point, g_tick_size, g_min_lot, g_max_lot, g_lot_step;

//—— 回撤保护相关 ——//
int      g_day_stamp = 0;           // yyyymmdd
double   g_day_start_equity = 0.0;  // 日内基线权益
double   g_equity_peak = 0.0;       // 历史权益高水位
bool     g_dd_block_open = false;   // 是否阻止开新仓
datetime g_dd_block_until = 0;      // 暂停到期时间(可选)

//================== 工具函数 ==================//
double NormalizeLot(double v)
{
   if(g_lot_step <= 0.0) return v;
   double lot = MathRound(v / g_lot_step) * g_lot_step;
   lot = MathMax(g_min_lot, MathMin(lot, MathMin(g_max_lot, InpMaxLot)));
   return lot;
}

bool IsHedgingAccount()
{
   long mode = (long)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
   return (mode == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
}

bool IsNewBar()
{
   datetime t = iTime(_Symbol, InpSignalTF, 0);
   if(t == 0) return false;
   if(g_last_bar_time == 0)
   {
      g_last_bar_time = t;
      return true; // 首次允许动作
   }
   if(t != g_last_bar_time)
   {
      g_last_bar_time = t;
      return true;
   }
   return false;
}

bool GetATR(double &atr_val)
{
   atr_val = 0.0;
   if(g_atr_handle == INVALID_HANDLE) return false;
   if(BarsCalculated(g_atr_handle) < InpATRPeriod+5) return false;

   double buf[];
   ArraySetAsSeries(buf, true);
   if(CopyBuffer(g_atr_handle, 0, 1, 1, buf) <= 0) return false; // 上一根bar的ATR
   atr_val = buf[0] * InpATRMultiplier;
   return (atr_val > 0.0);
}

int GetPositions(PosInfo &arr[])
{
   ArrayResize(arr, 0);
   int total = PositionsTotal();
   for(int i=0;i<total;i++)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0) continue;
      if(!PositionSelectByTicket(ticket)) continue;

      string sym = PositionGetString(POSITION_SYMBOL);
      long magic  = PositionGetInteger(POSITION_MAGIC);
      if(sym != _Symbol || magic != InpMagic) continue;

      PosInfo p;
      p.ticket     = ticket;
      p.type       = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      p.volume     = PositionGetDouble(POSITION_VOLUME);
      p.open_price = PositionGetDouble(POSITION_PRICE_OPEN);
      p.profit     = PositionGetDouble(POSITION_PROFIT);
      p.time       = (datetime)PositionGetInteger(POSITION_TIME);
      p.time_msc   = (long)PositionGetInteger(POSITION_TIME_MSC);

      int n = ArraySize(arr);
      ArrayResize(arr, n+1);
      arr[n] = p;
   }
   // 按时间升序(最早在前)
   int n = ArraySize(arr);
   for(int i=0;i<n-1;i++)
      for(int j=i+1;j<n;j++)
         if(arr.time_msc > arr[j].time_msc)
         {
            PosInfo tmp = arr;
            arr = arr[j];
            arr[j] = tmp;
         }
   return ArraySize(arr);
}

double TotalProfit(PosInfo &arr[])
{
   double sum = 0.0;
   for(int i=0;i<ArraySize(arr);i++)
      sum += arr.profit;
   return sum;
}

int DirFromType(ENUM_POSITION_TYPE t) { return (t == POSITION_TYPE_BUY ? +1 : -1); }

// 与开仓价的距离(价格单位),BUY用Bid,SELL用Ask
double DistanceFromOpenPrice(const PosInfo &p)
{
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   if(p.type == POSITION_TYPE_BUY)
      return (bid - p.open_price);
   else
      return (p.open_price - ask);
}

// 注意:comment 不用引用,避免“reference cannot be initialized”编译错误
bool OpenByDir(int dir, double lots, const string comment = "")
{
   // 回撤保护:触发后可阻止开新仓
   if(InpDD_Enable && InpDD_BlockOpenUntilReset && g_dd_block_open)
   {
      Print("DD protection active, skip opening: ", comment);
      return false;
   }

   lots = NormalizeLot(lots);
   if(lots < g_min_lot - 1e-12) return false;

   trade.SetExpertMagicNumber(InpMagic);
   trade.SetTypeFilling(InpFilling);
   trade.SetDeviationInPoints(InpSlippagePoints);

   bool ok = false;
   if(dir > 0) ok = trade.Buy(lots, _Symbol, 0.0, 0.0, 0.0, comment);
   else        ok = trade.Sell(lots, _Symbol, 0.0, 0.0, 0.0, comment);

   if(!ok)
      Print("OpenByDir failed. err=", GetLastError(), " lots=", lots, " dir=", dir);
   return ok;
}

bool CloseByTicket(ulong ticket)
{
   trade.SetExpertMagicNumber(InpMagic);
   trade.SetTypeFilling(InpFilling);
   trade.SetDeviationInPoints(InpSlippagePoints);
   bool ok = trade.PositionClose(ticket);
   if(!ok)
      Print("Close position failed ticket=", ticket, " err=", GetLastError());
   return ok;
}

void CloseAll()
{
   ulong tickets[];
   int total = PositionsTotal();
   for(int i=0;i<total;i++)
   {
      ulong t = PositionGetTicket(i);
      if(t == 0) continue;
      if(!PositionSelectByTicket(t)) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      if(PositionGetInteger(POSITION_MAGIC) != InpMagic) continue;

      int n = ArraySize(tickets);
      ArrayResize(tickets, n+1);
      tickets[n] = t;
   }
   for(int i=0;i<ArraySize(tickets);i++)
      CloseByTicket(tickets);
}

void SetState(int st)
{
   g_state = st;
   if(g_state_key != "")
      GlobalVariableSet(g_state_key, (double)st);
}

void RebuildStateFromPositions()
{
   PosInfo arr[];
   int n = GetPositions(arr);
   if(n <= 1) { SetState(STATE_SINGLE); return; }
   SetState(STATE_MULTI_WAIT);
}

//—— 日期工具 ——//
int DayStamp(datetime t)
{
   MqlDateTime dt; TimeToStruct(t, dt);
   return dt.year*10000 + dt.mon*100 + dt.day;
}

// 每天0点或首次运行时,刷新日内权益基线;若设置“新的一天自动解除回撤阻断”,则一起解除
void UpdateDailyAnchorIfNeeded(bool force=false)
{
   int ds = DayStamp(TimeCurrent());
   if(force || g_day_stamp == 0 || (InpDD_ResetOnNewDay && ds != g_day_stamp))
   {
      g_day_stamp = ds;
      g_day_start_equity = AccountInfoDouble(ACCOUNT_EQUITY);
      if(InpDD_ResetOnNewDay)
      {
         g_dd_block_open = false;
         g_dd_block_until = 0;
      }
      PrintFormat("Daily anchor reset. Day=%d, StartEquity=%.2f, Unblock=%s",
                  g_day_stamp, g_day_start_equity, InpDD_ResetOnNewDay ? "Yes":"No");
   }
}

// 检查回撤是否越界;越界则标记阻断,并可选清仓
bool CheckDrawdownAndMaybeBlock(string &reason, double &dd_day_pct, double &dd_total_pct)
{
   reason = "";
   dd_day_pct = 0.0; dd_total_pct = 0.0;
   if(!InpDD_Enable) return false;

   datetime now = TimeCurrent();
   double equity = AccountInfoDouble(ACCOUNT_EQUITY);

   // 更新高水位
   if(g_equity_peak <= 0.0 || equity > g_equity_peak)
      g_equity_peak = equity;

   // 计算日内与历史回撤
   double daily_loss_money = MathMax(0.0, g_day_start_equity - equity);
   if(g_day_start_equity > 0) dd_day_pct = 100.0 * daily_loss_money / g_day_start_equity;
   if(g_equity_peak > 0)      dd_total_pct = 100.0 * MathMax(0.0, g_equity_peak - equity) / g_equity_peak;

   bool breach = false;
   if(InpDD_MaxDailyPct > 0 && dd_day_pct >= InpDD_MaxDailyPct)         { breach = true; reason += (reason==""?"":"|"); reason += "DailyPct"; }
   if(InpDD_MaxDailyMoney > 0 && daily_loss_money >= InpDD_MaxDailyMoney){ breach = true; reason += (reason==""?"":"|"); reason += "DailyMoney"; }
   if(InpDD_MaxTotalPct > 0 && dd_total_pct >= InpDD_MaxTotalPct)        { breach = true; reason += (reason==""?"":"|"); reason += "TotalPct"; }

   // 暂停时间到期自动解除
   if(g_dd_block_open && InpDD_PauseMinutes > 0 && now >= g_dd_block_until)
   {
      g_dd_block_open = false;
      g_dd_block_until = 0;
      Print("DD pause expired. Trading unblocked.");
   }

   if(breach)
   {
      if(!g_dd_block_open)
         PrintFormat("DD Breach: reason=%s, DayDD=%.2f%%, TotalDD=%.2f%%, equity=%.2f",
                     reason, dd_day_pct, dd_total_pct, equity);

      g_dd_block_open = true;
      if(InpDD_PauseMinutes > 0)
         g_dd_block_until = now + InpDD_PauseMinutes * 60;
   }
   return breach;
}

//================== 标准事件 ==================//
int OnInit()
{
   trade.SetExpertMagicNumber(InpMagic);
   trade.SetTypeFilling(InpFilling);
   trade.SetDeviationInPoints(InpSlippagePoints);

   g_point      = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   g_tick_size  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   g_min_lot    = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   g_max_lot    = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   g_lot_step   = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   if(!IsHedgingAccount())
      Print("警告:当前账户不是Hedging模式,策略无法同时持有多空,将无法按策略执行。");

   g_atr_handle = iATR(_Symbol, InpATRTF, InpATRPeriod);
   if(g_atr_handle == INVALID_HANDLE)
   {
      Print("创建ATR失败");
      return(INIT_FAILED);
   }

   g_state_key = StringFormat("ATR_Marti_State_%s_%I64d", _Symbol, InpMagic);
   if(GlobalVariableCheck(g_state_key))
      g_state = (int)GlobalVariableGet(g_state_key);
   else
      RebuildStateFromPositions();

   g_last_bar_time = 0;

   // 回撤保护基线
   g_equity_peak = AccountInfoDouble(ACCOUNT_EQUITY);
   UpdateDailyAnchorIfNeeded(true);

   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   if(g_atr_handle != INVALID_HANDLE)
      IndicatorRelease(g_atr_handle);
}

void OnTick()
{
   // 1) 不受新K线限制的回撤保护(先执行)
   UpdateDailyAnchorIfNeeded(false);
   string dd_reason; double dd_day=0, dd_total=0;
   bool dd_breach = CheckDrawdownAndMaybeBlock(dd_reason, dd_day, dd_total);
   if((dd_breach || g_dd_block_open) && InpDD_CloseAllOnTrigger)
   {
      // 若有仓则清掉本EA在当前品种的所有仓位
      PosInfo tmp[];
      if(GetPositions(tmp) > 0)
         CloseAll();

      // 触发后不再执行本tick的策略逻辑(避免多余操作)
      return;
   }

   // 2) 若设置只在新K线执行
   if(InpOnlyOnNewBar && !IsNewBar())
      return;

   // 3) 正常策略逻辑
   PosInfo arr[];
   int n = GetPositions(arr);

   // 无仓 -> 开启新一轮(若未被回撤阻断)
   if(n == 0)
   {
      double lots = NormalizeLot(InpBaseLot);
      int dir = InpStartBuy ? +1 : -1;
      OpenByDir(dir, lots, "Start");
      SetState(STATE_SINGLE);
      return;
   }

   // 有仓 -> 计算ATR与总盈亏
   double atr = 0.0;
   if(!GetATR(atr)) return;

   double totalProfit = TotalProfit(arr);

   // 总浮盈达到目标,清仓重置
   if(totalProfit >= InpMinProfitToClose)
   {
      CloseAll();
      SetState(STATE_SINGLE);
      return;
   }

   // 单仓位阶段
   if(n == 1)
   {
      PosInfo p = arr[0];
      double dist = DistanceFromOpenPrice(p);
      if(dist >= atr) // 盈利超过ATR -> 平仓
      {
         CloseByTicket(p.ticket);
         SetState(STATE_SINGLE);
         return;
      }
      if(dist <= -atr) // 亏损超过ATR -> 反向加倍
      {
         double newLots = NormalizeLot(p.volume * 2.0);
         if(InpMaxSteps > 0)
         {
            int steps = 0;
            double v = InpBaseLot;
            while(v + 1e-12 < newLots && steps < 100) { v *= 2.0; steps++; }
            if(steps > InpMaxSteps)
            {
               Print("达到最大加仓步数,忽略加仓。");
               return;
            }
         }
         int dir = (p.type == POSITION_TYPE_BUY) ? -1 : +1; // 反向
         OpenByDir(dir, newLots, "Add Opposite");
         SetState(STATE_MULTI_WAIT);
      }
      return;
   }

   // 对冲阶段:两单或以上
   if(totalProfit < InpMinProfitToClose)
   {
      if(g_state == STATE_MULTI_WAIT)
      {
         PosInfo first  = arr[0];
         PosInfo latest = arr[ArraySize(arr)-1];
         double first_dist = DistanceFromOpenPrice(first);

         if(first_dist > 0) // 首单盈利 -> 平首单 + 同向加倍
         {
            CloseByTicket(first.ticket);

            double newLots = NormalizeLot(latest.volume * 2.0);
            int first_dir  = DirFromType(first.type);

            if(InpMaxSteps > 0)
            {
               int steps = 0;
               double v = InpBaseLot;
               while(v + 1e-12 < newLots && steps < 100) { v *= 2.0; steps++; }
               if(steps > InpMaxSteps)
               {
                  Print("达到最大加仓步数,忽略加仓。");
                  return;
               }
            }
            OpenByDir(first_dir, newLots, "Add SameAsFirst");
            SetState(STATE_AFTER_ADD_SAME);
            return;
         }
      }
      else if(g_state == STATE_AFTER_ADD_SAME)
      {
         PosInfo latest = arr[ArraySize(arr)-1];
         int latest_dir = DirFromType(latest.type);

         int idx_to_close = -1;
         for(int i=ArraySize(arr)-2; i>=0; i--)
         {
            if(DirFromType(arr.type) != latest_dir)
            {
               idx_to_close = i;
               break;
            }
         }
         if(idx_to_close != -1)
         {
            CloseByTicket(arr[idx_to_close].ticket);
            SetState(STATE_MULTI_WAIT);
            return;
         }
         else
         {
            SetState(STATE_SINGLE); // 已经同向
         }
      }
   }
}
复制代码
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|EAHub外汇论坛

GMT+8, 2026-10-13 10:09 , Processed in 0.108319 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表