这款马丁,基于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); // 已经同向
- }
- }
- }
- }
复制代码
|