【GoldBreakoutEA】模拟效果很好

| 发表于 前天 16:45 | 显示全部楼层 |复制链接

//+------------------------------------------------------------------+
//|                                            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, "]");
}
//+------------------------------------------------------------------+


1.png
2.png
最近访问 头像模式
举报

评论 使用道具

发新帖
EA交易
您需要登录后才可以评论 登录 | 立即注册