//+------------------------------------------------------------------+
//| GoldBreakoutEA.mq4 |
//| Copyright 2024, DClaw Tech |
//| https://www.dclaw.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, DClaw Tech"
#property link "https://www.dclaw.com"
#property version "1.00"
#property description "黄金突破自动化交易系统 — 基于价格突破的固定手数策略"
#property description "集成双向突破挂单、动态保本损、移动止盈、循环挂单、孤挂单重挂"
#property description "适用于 XAUUSD 等趋势性品种,全天候不间断运行"
#property strict
//+------------------------------------------------------------------+
//| 枚举定义(必须在 input 参数前声明) |
//+------------------------------------------------------------------+
enum ENUM_TRADE_MODE {
TRADE_MODE_BOTH = 0, // 双向交易
TRADE_MODE_BUY = 1, // 仅做多
TRADE_MODE_SELL = 2 // 仅做空
};
//+------------------------------------------------------------------+
//| 输入参数 |
//+------------------------------------------------------------------+
input group "=== 基本参数 ==="
input int MagicNumber = 88888; // 魔术编号(唯一标识) — 方案B
input string TradeComment = "GoldBreakout"; // 订单注释
input ENUM_TRADE_MODE TradeMode = TRADE_MODE_BOTH; // 交易模式(双向/仅多/仅空)
input group "=== 仓位管理 ==="
input double InitialLot = 0.1; // 初始手数
input group "=== 挂单设置 ==="
input int PendingDistance = 200; // 挂单间距(点)
input int StopLossPoints = 500; // 初始止损点数
input datetime OrderExpiration = 0; // 挂单到期时间(0=永不)
input group "=== 保本止损 ==="
input bool EnableBreakeven = true; // 启用动态保本损
input int BreakevenTrigger = 300; // 保本触发浮盈(点)
input int BreakevenLock = 50; // 保本锁定偏移(点)
input group "=== 移动止盈 ==="
input bool EnableTrailing = true; // 启用移动止盈
input int TrailingTrigger = 400; // 移动止盈触发浮盈(点)
input int TrailingStep = 100; // 移动止盈步长(点)
input group "=== 孤挂单管理 ==="
input int OrphanTimeout = 300; // 孤挂单超时(秒)
input bool OrphanCheckOnInit = true; // 启动时立即检查孤挂单
input group "=== 风控设置 ==="
input int MaxSpread = 80; // 最大允许点差(点)
input double MaxLotSize = 10.0; // 最大手数限制
input int Slippage = 30; // 允许滑点(点)
input bool UseEquityProtection = true; // 启用权益保护
input double MaxDrawdownPercent = 50.0; // 最大回撤百分比(禁用EA)
input group "=== 布林格过滤(可选, 已被JMA斜率过滤替代) ==="
input bool EnableBandsFilter = false; // 启用布林带宽度过滤(窄幅震荡不开仓); 默认由JMA斜率过滤替代, 可叠加开启
input int BandsPeriod = 20; // 布林带周期
input double BandsDeviation = 2.0; // 布林带标准差倍数
input int MinBandsWidthPoints = 400; // 布林带最小宽度(点), 小于此值不开仓
input group "=== JMA 趋势斜率过滤(方案B: power=1, 主条件之一) ==="
input bool EnableJmaFilter = true; // 启用JMA斜率过滤(无趋势/震荡行情不开仓)
input int JmaPeriod = 20; // JMA周期(与回测探索一致)
input double JmaPower = 1.0; // JMA power: 1=方案B(J1); 2=更平滑(方案A/J2)
input double JmaMinSlopePoints = 100.0; // JMA斜率最小绝对值(点/bar); 方案B甜点≈100(=JMA价格差1.0); 小于此值不开仓
input int JmaLookbackBars = 400; // JMA收敛回溯根数(需 >= period*8 以保证充分收敛)
input group "=== ATR% 低波动过滤(方案B: 组合条件之二) ==="
input bool EnableAtrFilter = true; // 启用ATR%过滤(清淡行情不开仓); 方案B组合条件
input double AtrMinPct = 0.05; // ATR/收盘价*100 最小百分比; 低于此值不开仓(回测甜点0.05)
input int AtrLookbackBars = 200; // ATR平滑回溯根数(需 >= 60 充分收敛)
input group "=== 时间过滤(仅交易时段) ==="
input bool EnableTimeFilter = false; // 启用开仓时间过滤
input int TradeStartTime = 20; // 允许开仓起始小时(北京时间 GMT+8, 0-23, 含)
input int TradeEndTime = 0; // 允许开仓结束小时(北京时间 0=00:00, 即23:59结束; 跨午夜区间)
input bool CloseOutsideWindow = false; // 非交易时段强制平仓(否则仅停止开新仓)
//+------------------------------------------------------------------+
//| 全局变量 |
//+------------------------------------------------------------------+
int g_magicNumber;
string g_comment;
double g_initialLot;
bool g_hadPosition = false; // 是否曾经持有过仓位
int g_lastHistCount = 0; // 上次历史订单数
// 孤挂单管理
datetime g_lastOrphanActionTime = 0; // 上次处理孤挂单的时间
int g_orphanHandledTicket = 0; // 已处理的孤挂单ticket
// 移动止盈跟踪
double g_peakPrice = 0.0; // 追踪的最高/最低价
int g_trailTicket = 0; // 被追踪的订单号
bool g_trailActive = false; // 追踪是否已激活
bool g_breakevenDone = false; // 保本损是否已执行
//+------------------------------------------------------------------+
//| 初始化函数 |
//+------------------------------------------------------------------+
int OnInit() {
g_magicNumber = MagicNumber;
g_comment = TradeComment;
g_initialLot = InitialLot;
g_lastHistCount = OrdersHistoryTotal();
g_lastOrphanActionTime = 0;
g_orphanHandledTicket = 0;
g_peakPrice = 0.0;
g_trailTicket = 0;
g_trailActive = false;
g_breakevenDone = false;
// 恢复已有订单状态
RecoverExistingOrders();
// 启动时检查孤挂单
if (OrphanCheckOnInit && CountPendingOrders() == 1) {
int ticket = GetSinglePendingTicket();
if (ticket > 0) {
g_orphanHandledTicket = ticket;
HandleOrphanOrder();
}
}
// 验证参数
ValidateParameters();
Print("╔══════════════════════════════════════════════╗");
Print("║ GoldBreakoutEA 初始化完成 ║");
Print("║ 魔术编号: ", g_magicNumber, " ║");
Print("║ 初始手数: ", DoubleToString(g_initialLot, 2), " ║");
Print("╚══════════════════════════════════════════════╝");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| 反初始化函数 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
Print("GoldBreakoutEA 停止, 原因代码: ", reason);
}
//+------------------------------------------------------------------+
//| 布林带宽度过滤:上下轨间距过小(波动率收缩)不开仓 |
//+------------------------------------------------------------------+
bool IsBandsWidthOK() {
if (!EnableBandsFilter) return true;
double upper = iBands(Symbol(), 0, BandsPeriod, BandsDeviation, 0, PRICE_CLOSE, MODE_UPPER, 0);
double lower = iBands(Symbol(), 0, BandsPeriod, BandsDeviation, 0, PRICE_CLOSE, MODE_LOWER, 0);
if (upper <= 0.0 || lower <= 0.0) return true; // 指标获取失败不阻止交易
double widthPoints = (upper - lower) / Point;
if (widthPoints < MinBandsWidthPoints) {
static datetime lastWarn = 0;
if (TimeCurrent() - lastWarn > 60) {
Print("布林带宽度不足: ", DoubleToString(widthPoints, 1), " 点 < ", MinBandsWidthPoints, " 点, 暂停挂突破单");
lastWarn = TimeCurrent();
}
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Jurik JMA 趋势斜率过滤 (与 Python 回测探索版逐字对齐) |
//| 基于最近 JmaLookbackBars 根收盘价, 计算 JMA 最后两根的 |
//| 斜率(点/bar) = (JMA_now - JMA_prev) / Point |
//| 仅在 |斜率| >= JmaMinSlopePoints 时允许开仓, 规避无趋势震荡 |
//+------------------------------------------------------------------+
double JmaSlopePoints() {
int lookback = JmaLookbackBars;
if (lookback < JmaPeriod * 8) lookback = JmaPeriod * 8;
if (lookback < 5) lookback = 5;
double src[];
ArrayResize(src, lookback);
for (int k = 0; k < lookback; k++) {
// 时间顺序: 最老 -> 最新 (i=0 为最老 bar)
double c = iClose(_Symbol, 0, lookback - 1 - k);
if (c <= 0.0) return 0.0; // 数据不足 -> 斜率0 -> 不开仓
src[k] = c;
}
double phase = 0.0; // 与探索一致: phase=0 -> pr=1.5
double pr = (phase < -100) ? 0.5 : (phase > 100 ? 2.5 : phase / 100.0 + 1.5);
double beta = 0.45 * (JmaPeriod - 1) / (0.45 * (JmaPeriod - 1) + 2);
double alpha = MathPow(beta, JmaPower);
double e0 = 0.0, e1 = 0.0, e2 = 0.0, jv = 0.0;
double jv_prev = 0.0;
for (int i = 0; i < lookback; i++) {
jv_prev = jv;
e0 = (1.0 - alpha) * src[i] + alpha * e0;
e1 = (src[i] - e0) * (1.0 - beta) + beta * e1;
e2 = (e0 + pr * e1 - jv) * (1.0 - alpha * alpha) + alpha * alpha * e2;
jv = e2 + e0 + pr * e1;
}
return (jv - jv_prev) / Point;
}
bool IsJmaSlopeOK() {
if (!EnableJmaFilter) return true;
double slope = JmaSlopePoints();
if (MathAbs(slope) < JmaMinSlopePoints) {
static datetime lastWarn = 0;
if (TimeCurrent() - lastWarn > 60) {
Print("JMA斜率不足: ", DoubleToString(MathAbs(slope), 2),
" 点/bar < ", JmaMinSlopePoints, " 点/bar, 暂停挂突破单 (无趋势/震荡行情)");
lastWarn = TimeCurrent();
}
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| ATR% 低波动过滤 (方案B 组合条件之二) |
//| 与 Python 回测探索版逐字对齐: |
//| TR = max(H-L, |H-Cprev|, |L-Cprev|) |
//| ATR = Wilder 平滑, alpha=2/15 (≈7.5期) |
//| ATR% = ATR / Close * 100 |
//+------------------------------------------------------------------+
double AtrPercent() {
int lookback = AtrLookbackBars;
if (lookback < 60) lookback = 60;
if (lookback > 2000) lookback = 2000;
double atr = 0.0;
bool first = true;
for (int i = lookback; i >= 1; i--) {
double H = iHigh(_Symbol, 0, i);
double L = iLow(_Symbol, 0, i);
double Cprev = iClose(_Symbol, 0, i + 1);
if (H <= 0.0 || L <= 0.0 || Cprev <= 0.0) return 0.0;
double tr = MathMax(H - L, MathMax(MathAbs(H - Cprev), MathAbs(L - Cprev)));
if (first) { atr = tr; first = false; }
else atr = tr * (2.0 / 15.0) + atr * (1.0 - 2.0 / 15.0);
}
double closeNow = iClose(_Symbol, 0, 1);
if (closeNow <= 0.0) return 0.0;
return atr / closeNow * 100.0;
}
bool IsAtrOk() {
if (!EnableAtrFilter) return true;
double pct = AtrPercent();
if (pct < AtrMinPct) {
static datetime lastWarn = 0;
if (TimeCurrent() - lastWarn > 60) {
Print("ATR%不足: ", DoubleToString(pct, 3),
"% < ", AtrMinPct, "%, 暂停挂突破单 (清淡行情)");
lastWarn = TimeCurrent();
}
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| 主Tick处理函数 |
//+------------------------------------------------------------------+
void OnTick() {
// 交易环境检查
if (!IsTradingAllowed()) return;
// 权益保护检查
if (UseEquityProtection && CheckDrawdown()) {
if (CountPositions() > 0 || CountPendingOrders() > 0) {
CloseAllOrders("回撤过大, 平仓保护");
}
return;
}
// 刷新费率
RefreshRates();
int pendingCount = CountPendingOrders();
int positionCount = CountPositions();
// 时间过滤:若启用且当前处于非交易时段,按设置强制平仓或仅停止开新仓
if (EnableTimeFilter && !IsTradingTime()) {
if (CloseOutsideWindow && (positionCount > 0 || pendingCount > 0)) {
CloseAllOrders("非交易时段, 强制平仓");
}
return; // 非交易时段不再挂新单
}
// ── 状态1: 没有任何订单 ──
if (pendingCount == 0 && positionCount == 0&&IsTradingTime()) {
// JMA 趋势斜率过滤(方案B: power=1, 斜率≥100点/bar): 无趋势/震荡行情不开仓
if (EnableJmaFilter && !IsJmaSlopeOK()) {
return;
}
// ATR% 低波动过滤(方案B 组合条件): 清淡行情不开仓
if (EnableAtrFilter && !IsAtrOk()) {
return;
}
// 布林带宽度过滤(可选附加): 间距过小不开仓
if (EnableBandsFilter && !IsBandsWidthOK()) {
return;
}
// 检测到新平仓后重置状态
if (g_hadPosition || OrdersHistoryTotal() > g_lastHistCount) {
g_hadPosition = false;
g_lastHistCount = OrdersHistoryTotal();
}
// 重置追踪状态
ResetTrailingState();
// 挂双向单
PlaceDualOrders();
return;
}
// ── 状态2: 有持仓 ──
if (positionCount > 0) {
g_hadPosition = true;
g_breakevenDone = false;
ManagePosition();
return;
}
// ── 状态3: 仅有挂单 ──
if (pendingCount >= 1) {
// 如果某侧已成交,检查另一侧是否该取消
if (pendingCount == 1) {
HandleOrphanOrder();
}
// pendingCount == 2 则正常等待突破
}
}
//+------------------------------------------------------------------+
//| 判断当前是否处于清仓时段(周五晚 21 点后) |
//+------------------------------------------------------------------+
bool ClearTrading()
{
MqlDateTime dt;
TimeCurrent(dt);
int weekday = dt.day_of_week;
int hour = dt.hour;
if(weekday == 5 && hour > 21)
return true;
else
return false;
}
//+------------------------------------------------------------------+
//| 判断当前是否处于交易时段 |
//+------------------------------------------------------------------+
bool IsTradingTime()
{
if(!EnableTimeFilter) return true;
// 分析口径为【北京时间 GMT+8】。用 TimeGMT() 换算, 不依赖经纪商服务器时区。
MqlDateTime gmt;
TimeGMT(gmt);
int bjHour = (gmt.hour + 8) % 24; // 北京时间小时
int bjDow = gmt.day_of_week; // 北京时间星期(用于周末判定)
if(bjDow == 6) return false; // 北京周六全天休市,禁止开仓
// 区间 [TradeStartTime, TradeEndTime),单位均为北京时间
// 当 TradeEndTime <= TradeStartTime 时表示跨午夜(如 20 → 0 即 20:00-23:59)
if(TradeEndTime <= TradeStartTime)
return bjHour >= TradeStartTime;
else
return (bjHour >= TradeStartTime && bjHour < TradeEndTime);
}
//+------------------------------------------------------------------+
//| 参数验证 |
//+------------------------------------------------------------------+
void ValidateParameters() {
// 检查最小止损距离
double minStopLevel = (double)MarketInfo(Symbol(), MODE_STOPLEVEL);
if (minStopLevel > 0 && StopLossPoints < (int)minStopLevel) {
Print("? 止损点数(", StopLossPoints, ")小于经纪商最小止损距离(",
(int)minStopLevel, "), 建议调整为更大值");
}
// 检查保本触发应小于移动止盈触发
if (EnableBreakeven && EnableTrailing && BreakevenTrigger >= TrailingTrigger) {
Print("? 保本触发(", BreakevenTrigger, ")应小于移动止盈触发(",
TrailingTrigger, "), 否则移动止盈可能无法激活");
}
// 检查挂单间距合理性
int minDist = (int)MarketInfo(Symbol(), MODE_STOPLEVEL);
if (PendingDistance < minDist) {
Print("? 挂单间距(", PendingDistance, ")过小, 经纪商最小距离:", minDist);
}
}
//+------------------------------------------------------------------+
//| 检查回撤保护 |
//+------------------------------------------------------------------+
bool CheckDrawdown() {
if (!UseEquityProtection || MaxDrawdownPercent <= 0.0) return false;
double initBalance = AccountBalance();
double equity = AccountEquity();
if (initBalance > 0.0) {
double ddPercent = (1.0 - equity / initBalance) * 100.0;
if (ddPercent >= MaxDrawdownPercent) {
Print("? 回撤已达 ", DoubleToString(ddPercent, 1), "%, 超过设定 ",
DoubleToString(MaxDrawdownPercent, 1), "%, 启动保护");
return true;
}
}
return false;
}
//+------------------------------------------------------------------+
//| 检查交易环境 |
//+------------------------------------------------------------------+
bool IsTradingAllowed() {
// 检查是否连接
if (!IsConnected()) {
return false;
}
// 检查是否允许交易
if (!IsTradeAllowed()) {
return false;
}
// 检查点差
double spread = MarketInfo(Symbol(), MODE_SPREAD);
if (spread > MaxSpread && MaxSpread > 0) {
static datetime lastSpreadWarning = 0;
if (TimeCurrent() - lastSpreadWarning > 60) {
Print("? 点差过大: ", DoubleToString(spread, 1), " 点, 限制: ", MaxSpread);
lastSpreadWarning = TimeCurrent();
}
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| 恢复已有订单(重启/重编译后找回) |
//+------------------------------------------------------------------+
void RecoverExistingOrders() {
bool foundPosition = false;
g_trailTicket = 0;
g_trailActive = false;
g_breakevenDone = false;
// 扫描持仓
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderMagicNumber() != g_magicNumber) continue;
if (OrderSymbol() != Symbol()) continue;
if (OrderType() == OP_BUY || OrderType() == OP_SELL) {
foundPosition = true;
g_hadPosition = true;
g_trailTicket = OrderTicket();
// 如果已设置止损且止损优于开仓价,说明保本已激活
if (OrderType() == OP_BUY && OrderStopLoss() > OrderOpenPrice()) {
g_breakevenDone = true;
}
if (OrderType() == OP_SELL && OrderStopLoss() < OrderOpenPrice()) {
g_breakevenDone = true;
}
Print("? 恢复持仓 #", OrderTicket(), " ", OrderTypeStr(OrderType()));
}
}
// 扫描挂单
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderMagicNumber() != g_magicNumber) continue;
if (OrderSymbol() != Symbol()) continue;
if (OrderType() == OP_BUYSTOP || OrderType() == OP_SELLSTOP) {
Print("? 恢复挂单 #", OrderTicket(), " ", OrderTypeStr(OrderType()));
}
}
if (!foundPosition) {
g_hadPosition = false;
}
}
//+------------------------------------------------------------------+
//| 计算固定开仓手数(马丁机制已移除: 手数恒为 InitialLot) |
//+------------------------------------------------------------------+
double GetFixedLot() {
double lot = g_initialLot;
// 约束到经纪商允许范围
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MathMin(MarketInfo(Symbol(), MODE_MAXLOT), MaxLotSize);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
lot = MathMax(lot, minLot);
lot = MathMin(lot, maxLot);
// 对齐到最小步长
if (lotStep > 0.0) {
lot = MathRound(lot / lotStep) * lotStep;
}
return lot;
}
//+------------------------------------------------------------------+
//| 挂双向突破单 |
//+------------------------------------------------------------------+
void PlaceDualOrders() {
// JMA 趋势斜率过滤(兜底, 方案B: power=1)
if (EnableJmaFilter && !IsJmaSlopeOK()) {
return;
}
// ATR% 低波动过滤(兜底, 方案B 组合条件)
if (EnableAtrFilter && !IsAtrOk()) {
return;
}
// 布林带宽度过滤(可选附加兜底)
if (EnableBandsFilter && !IsBandsWidthOK()) {
return;
}
// 获取中心价
double centerPrice = GetCenterPrice();
double slPoints = StopLossPoints * Point;
// Buy Stop
if (TradeMode != TRADE_MODE_SELL) {
double buyStopPrice = NormalizeDouble(centerPrice + PendingDistance * Point, Digits);
double buySL = NormalizeDouble(buyStopPrice - slPoints, Digits);
// 验证止损距离
buySL = ValidateStopLoss(buyStopPrice, buySL, OP_BUYSTOP);
int ticket = OrderSend(Symbol(), OP_BUYSTOP, GetFixedLot(), buyStopPrice,
Slippage, buySL, 0, GetOrderComment("B"), g_magicNumber,
(datetime)OrderExpiration, clrNONE);
if (ticket > 0) {
Print("? BuyStop 已挂 #", ticket, " 价格: ", buyStopPrice, " 止损: ", buySL,
" 手数: ", DoubleToString(GetFixedLot(), 2));
} else {
LogOrderError("挂 BuyStop 失败", GetLastError());
}
}
// Sell Stop
if (TradeMode != TRADE_MODE_BUY) {
double sellStopPrice = NormalizeDouble(centerPrice - PendingDistance * Point, Digits);
double sellSL = NormalizeDouble(sellStopPrice + slPoints, Digits);
// 验证止损距离
sellSL = ValidateStopLoss(sellStopPrice, sellSL, OP_SELLSTOP);
int ticket = OrderSend(Symbol(), OP_SELLSTOP, GetFixedLot(), sellStopPrice,
Slippage, sellSL, 0, GetOrderComment("S"), g_magicNumber,
(datetime)OrderExpiration, clrNONE);
if (ticket > 0) {
Print("? SellStop 已挂 #", ticket, " 价格: ", sellStopPrice, " 止损: ", sellSL,
" 手数: ", DoubleToString(GetFixedLot(), 2));
} else {
LogOrderError("挂 SellStop 失败", GetLastError());
}
}
}
//+------------------------------------------------------------------+
//| 获取中心价 (Ask+Bid)/2 |
//+------------------------------------------------------------------+
double GetCenterPrice() {
return NormalizeDouble((Ask + Bid) / 2.0, Digits);
}
//+------------------------------------------------------------------+
//| 验证止损距离(适配经纪商最小止损距离) |
//+------------------------------------------------------------------+
double ValidateStopLoss(double orderPrice, double slPrice, int orderType) {
double stopLevel = MarketInfo(Symbol(), MODE_STOPLEVEL) * Point;
if (stopLevel <= 0.0) stopLevel = 10 * Point; // 默认最小值
switch (orderType) {
case OP_BUY:
case OP_BUYSTOP:
if (slPrice > 0.0 && orderPrice - slPrice < stopLevel) {
slPrice = orderPrice - stopLevel;
Print("? Buy止损距离不足,已调整为: ", slPrice);
}
break;
case OP_SELL:
case OP_SELLSTOP:
if (slPrice > 0.0 && slPrice - orderPrice < stopLevel) {
slPrice = orderPrice + stopLevel;
Print("? Sell止损距离不足,已调整为: ", slPrice);
}
break;
}
return NormalizeDouble(slPrice, Digits);
}
//+------------------------------------------------------------------+
//| 取消指定方向的挂单 |
//+------------------------------------------------------------------+
void CancelOppositeOrder(int filledType) {
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderMagicNumber() != g_magicNumber) continue;
if (OrderSymbol() != Symbol()) continue;
bool isPending = (OrderType() == OP_BUYSTOP || OrderType() == OP_SELLSTOP);
if (!isPending) continue;
// Buy成交 → 删 SellStop; Sell成交 → 删 BuyStop
if ((filledType == OP_BUY && OrderType() == OP_SELLSTOP) ||
(filledType == OP_SELL && OrderType() == OP_BUYSTOP)) {
if (OrderDelete(OrderTicket())) {
Print("? 已撤销反方向挂单 #", OrderTicket());
} else {
LogOrderError("撤销挂单失败 #" + IntegerToString(OrderTicket()), GetLastError());
}
}
}
}
//+------------------------------------------------------------------+
//| 清除所有残留挂单(持仓存在时调用) |
//+------------------------------------------------------------------+
void CancelRemainingPendingOrders() {
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderMagicNumber() != g_magicNumber) continue;
if (OrderSymbol() != Symbol()) continue;
if (OrderType() == OP_BUYSTOP || OrderType() == OP_SELLSTOP) {
if (OrderDelete(OrderTicket())) {
Print("? 清除残留挂单 #", OrderTicket());
} else {
LogOrderError("清除残留挂单失败 #" + IntegerToString(OrderTicket()), GetLastError());
}
}
}
}
//+------------------------------------------------------------------+
//| 孤挂单处理 |
//+------------------------------------------------------------------+
void HandleOrphanOrder() {
int ticket = GetSinglePendingTicket();
if (ticket <= 0) return;
// 防止重复处理同一个孤挂单
if (ticket == g_orphanHandledTicket) {
// 如果上次处理后已过超时时间,允许重新处理
if (TimeCurrent() - g_lastOrphanActionTime < OrphanTimeout) {
return;
}
}
if (!OrderSelect(ticket, SELECT_BY_TICKET)) return;
// 计算挂单已存在的时间
int secondsPending = (int)(TimeCurrent() - OrderOpenTime());
if (secondsPending >= OrphanTimeout) {
Print("? 检测到孤挂单 #", ticket, " ", OrderTypeStr(OrderType()),
" 已超时 ", secondsPending, "秒, 准备重挂");
// 删除孤挂单
if (OrderDelete(ticket)) {
Print("? 已删除孤挂单 #", ticket);
g_orphanHandledTicket = ticket;
g_lastOrphanActionTime = TimeCurrent();
// 稍等片刻让订单状态更新
Sleep(500);
RefreshRates();
// 重新挂双向单
PlaceDualOrders();
} else {
LogOrderError("删除孤挂单失败 #" + IntegerToString(ticket), GetLastError());
}
}
}
//+------------------------------------------------------------------+
//| 获取唯一的挂单ticket |
//+------------------------------------------------------------------+
int GetSinglePendingTicket() {
int ticket = -1;
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderMagicNumber() != g_magicNumber) continue;
if (OrderSymbol() != Symbol()) continue;
if (OrderType() == OP_BUYSTOP || OrderType() == OP_SELLSTOP) {
if (ticket > 0) return -1; // 不止一个挂单
ticket = OrderTicket();
}
}
return ticket;
}
//+------------------------------------------------------------------+
//| 仓位管理(保本损 + 移动止盈) |
//+------------------------------------------------------------------+
void ManagePosition() {
// 清除可能残留的反方向挂单(当挂单成交时自动清理)
CancelRemainingPendingOrders();
// 查找持仓
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderMagicNumber() != g_magicNumber) continue;
if (OrderSymbol() != Symbol()) continue;
if (OrderType() == OP_BUY || OrderType() == OP_SELL) {
int ticket = OrderTicket();
// 检查当前浮盈(点数)
double profitPoints = GetProfitInPoints();
// 保本损
if (EnableBreakeven && profitPoints >= BreakevenTrigger && !g_breakevenDone) {
if (ApplyBreakeven(ticket)) {
g_breakevenDone = true;
}
}
// 移动止盈
if (EnableTrailing && profitPoints >= TrailingTrigger) {
if (!g_trailActive) {
// 首次激活追踪
g_trailActive = true;
g_peakPrice = GetPeakPrice(ticket);
g_trailTicket = ticket;
Print("? 移动止盈激活 #", ticket, " 峰值: ", g_peakPrice);
} else {
ApplyTrailingStop(ticket);
}
}
break; // 一次只处理一个持仓
}
}
}
//+------------------------------------------------------------------+
//| 计算当前浮盈(点数) |
//+------------------------------------------------------------------+
double GetProfitInPoints() {
if (OrderType() == OP_BUY) {
return (Bid - OrderOpenPrice()) / Point;
} else if (OrderType() == OP_SELL) {
return (OrderOpenPrice() - Ask) / Point;
}
return 0.0;
}
//+------------------------------------------------------------------+
//| 获取当前峰值价格 |
//+------------------------------------------------------------------+
double GetPeakPrice(int ticket) {
if (!OrderSelect(ticket, SELECT_BY_TICKET)) return 0.0;
if (OrderType() == OP_BUY) {
// 追踪最高价
return MathMax(g_peakPrice, Bid);
} else {
// 追踪最低价
double lowPeak = (g_peakPrice > 0.0) ? MathMin(g_peakPrice, Ask) : Ask;
return lowPeak;
}
}
//+------------------------------------------------------------------+
//| 应用保本损 |
//+------------------------------------------------------------------+
bool ApplyBreakeven(int ticket) {
if (!OrderSelect(ticket, SELECT_BY_TICKET)) return false;
double newSL = 0.0;
double lockPoints = BreakevenLock * Point;
if (OrderType() == OP_BUY) {
newSL = OrderOpenPrice() + lockPoints;
// 确保新止损优于当前止损
if (newSL <= OrderStopLoss()) return true; // 已更优
} else if (OrderType() == OP_SELL) {
newSL = OrderOpenPrice() - lockPoints;
if (newSL >= OrderStopLoss() && OrderStopLoss() > 0.0) return true;
} else {
return false;
}
// 验证止损距离(使用当前市场价作为基准)
double refPrice = (OrderType() == OP_BUY) ? Bid : Ask;
newSL = ValidateStopLoss(refPrice, newSL, OrderType());
if (OrderModify(ticket, OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE)) {
Print("? 保本损已激活 #", ticket, " 新止损: ", newSL,
" 锁定利润: ", BreakevenLock, "点");
return true;
} else {
LogOrderError("保本损修改失败 #" + IntegerToString(ticket), GetLastError());
return false;
}
}
//+------------------------------------------------------------------+
//| 应用移动止盈 |
//+------------------------------------------------------------------+
void ApplyTrailingStop(int ticket) {
if (!OrderSelect(ticket, SELECT_BY_TICKET)) return;
// 更新峰值
double currentPeak = GetPeakPrice(ticket);
// 对于Buy: 峰值只升不降
if (OrderType() == OP_BUY && currentPeak > g_peakPrice) {
g_peakPrice = currentPeak;
}
// 对于Sell: 峰值只降不升
if (OrderType() == OP_SELL && (currentPeak < g_peakPrice || g_peakPrice == 0.0)) {
g_peakPrice = currentPeak;
}
double trailDistance = TrailingStep * Point;
double newSL = 0.0;
bool needModify = false;
if (OrderType() == OP_BUY) {
// 止损应跟随峰值上移: 峰值 - 步长
double idealSL = g_peakPrice - trailDistance;
if (idealSL > OrderStopLoss()) {
newSL = idealSL;
needModify = true;
}
} else if (OrderType() == OP_SELL) {
// 止损应跟随峰值下移: 峰值 + 步长
double idealSL = g_peakPrice + trailDistance;
if (idealSL < OrderStopLoss() || OrderStopLoss() == 0.0) {
newSL = idealSL;
needModify = true;
}
}
if (needModify) {
// 使用当前市场价作为验证基准
double refPrice = (OrderType() == OP_BUY) ? Bid : Ask;
newSL = ValidateStopLoss(refPrice, newSL, OrderType());
if (OrderModify(ticket, OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE)) {
Print("↗ 移动止盈更新 #", ticket, " 止损→", newSL,
" 峰值:", g_peakPrice);
} else {
LogOrderError("移动止盈修改失败 #" + IntegerToString(ticket), GetLastError());
}
}
}
//+------------------------------------------------------------------+
//| 重置追踪状态 |
//+------------------------------------------------------------------+
void ResetTrailingState() {
g_peakPrice = 0.0;
g_trailTicket = 0;
g_trailActive = false;
g_breakevenDone = false;
}
//+------------------------------------------------------------------+
//| 关闭所有订单(权益保护时调用) |
//+------------------------------------------------------------------+
void CloseAllOrders(string reason) {
Print("? 开始全部平仓: ", reason);
// 关闭持仓
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderMagicNumber() != g_magicNumber) continue;
if (OrderSymbol() != Symbol()) continue;
if (OrderType() == OP_BUY) {
if (OrderClose(OrderTicket(), OrderLots(), Bid, Slippage, clrNONE)) {
Print("? 平仓 Buy #", OrderTicket());
}
} else if (OrderType() == OP_SELL) {
if (OrderClose(OrderTicket(), OrderLots(), Ask, Slippage, clrNONE)) {
Print("? 平仓 Sell #", OrderTicket());
}
} else if (OrderType() == OP_BUYSTOP || OrderType() == OP_SELLSTOP) {
if (OrderDelete(OrderTicket())) {
Print("? 删除挂单 #", OrderTicket());
}
}
}
g_hadPosition = false;
ResetTrailingState();
}
//+------------------------------------------------------------------+
//| 统计函数群 |
//+------------------------------------------------------------------+
// 统计挂单数
int CountPendingOrders() {
int count = 0;
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderMagicNumber() != g_magicNumber) continue;
if (OrderSymbol() != Symbol()) continue;
if (OrderType() == OP_BUYSTOP || OrderType() == OP_SELLSTOP) {
count++;
}
}
return count;
}
// 统计持仓数
int CountPositions() {
int count = 0;
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if (OrderMagicNumber() != g_magicNumber) continue;
if (OrderSymbol() != Symbol()) continue;
if (OrderType() == OP_BUY || OrderType() == OP_SELL) {
count++;
}
}
return count;
}
//+------------------------------------------------------------------+
//| 辅助函数 |
//+------------------------------------------------------------------+
// 生成订单注释
string GetOrderComment(string side) {
return g_comment + "_" + side;
}
// 订单类型转为字符串
string OrderTypeStr(int type) {
switch (type) {
case OP_BUY: return "BUY";
case OP_SELL: return "SELL";
case OP_BUYSTOP: return "BUY_STOP";
case OP_SELLSTOP: return "SELL_STOP";
default: return "UNKNOWN";
}
}
// 错误日志
void LogOrderError(string action, int error) {
string errDesc = "";
switch (error) {
case 130: errDesc = "ERR_INVALID_STOPS"; break;
case 138: errDesc = "ERR_REQUOTE"; break;
case 145: errDesc = "ERR_TRADE_MODIFY_DENIED"; break;
case 148: errDesc = "ERR_TRADE_CONTEXT_BUSY"; break;
case 4108: errDesc = "ERR_UNKNOWN_SYMBOL"; break;
case 4110: errDesc = "ERR_BROKER_BUSY"; break;
case 4756: errDesc = "ERR_TOO_MANY_ORDERS"; break;
default: errDesc = "ERR_" + IntegerToString(error); break;
}
Print("? ", action, " [", errDesc, "]");
}
//+------------------------------------------------------------------+
|