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