MT5专业交易面板

MT5专业交易面板

 

使用此功能强大的可视化面板,轻松进行紧急头寸管理。

🎯作用

专业的智能交易系统,为您的图表添加了一个时尚的控制面板,可通过高级过滤选项即时平仓。非常适合紧急情况、新闻事件或快速风险管理决策。

✨ 主要功能

- 一键即时关闭所有仓位

- 仅关闭买入头寸

- 仅关闭卖出仓位

- 仅关闭当前符号的仓位

- 仅关闭盈利仓位

- 仅关闭亏损仓位

- 实时仓位计数器和损益显示

- 彩色盈亏指标(绿色=盈利,红色=亏损)

- 平仓前显示损益的安全确认对话框

- 简易面板移除按钮

- 完全自定义颜色和面板位置

- 适用于所有符号和时间框架

- 零延迟,即时执行

🛡️ 安全第一

每个平仓操作都需要您的确认,执行前对话框会显示准确的损益金额。您绝不会意外平仓。

适用于

✓ 突发新闻时的紧急仓位退出

✓ 达到目标时快速获利了结

✓ 快速减少损失,保护您的账户

日终头寸清理

专业风险管理

剥头皮者和日间交易者

需要即时退出的新闻交易者

完全自定义

- 选择面板位置(4 个角中的任意一个)

- 自定义与角的 X 和 Y 距离

- 更改所有颜色(背景、按钮、文本)

- 包含专业配色方案

实时信息

面板持续更新显示

- 未结头寸总数

- 以账户货币计算的总损益

- 买入头寸数量和损益

- 卖出头寸数量和损益

- 所有内容均以颜色标示,一目了然

⚙️ 输入参数

所有设置都很简单,并有详细说明:

- 面板角落 - 选择显示位置

- 面板 X/Y 距离 - 微调位置

- 颜色 - 背景、按钮、文本

- 所有参数都有清晰的说明

超级易用

1.将 EA 拖到任何图表上

2.启用自动交易(工具栏中的绿色按钮)

3.面板自动出现

4.点击任何按钮关闭仓位

5.确认操作

6.完成!完成后取下面板

⚡ 高性能

- 超轻量级代码

- 无 CPU 开销或内存问题

- 即时按钮响应

- 轻松处理 100 多个位置

- 即使在剧烈波动时也不会出现延迟

经过测试,性能可靠

在多个经纪商、账户类型和市场条件下进行了全面测试。包括全面的错误处理、详细的日志记录和智能订单填写模式(FOK/IOC/RETURN),以实现最大兼容性。

完全免费

- 无隐藏费用

- 无仓位限制

- 无终止日期

- 无试用期

- 100% 永久免费

卓越的支持

我致力于帮助所有粉丝从这款工具中获得最大收益。

⭐⭐⭐⭐⭐ 如有帮助,请给予评价!⭐⭐⭐⭐⭐

记得 ,关注,转发,点赞,投币

如果此 EA 为您节省了时间或金钱,请花 10 秒钟为其评分!

您的评价有助于其他交易者发现这款免费工具帮助到更多人。

感谢您的支持!🙏

📥安装说明

1.下载文件

2.打开 MetaTrader 5

3.打开 MetaEditor(按 F4 或工具 → MetaQuotes 语言编辑器)

4.文件 → 打开数据文件夹

5.将 EA 文件复制到 MQL5 → Experts

6.重启 MT5 或刷新导航器(右键单击 → 刷新)

7.将导航器 → 专家顾问中的 "一键关闭所有仓位 - 专业交易面板 "拖到任意图表上

8.在设置窗口中点击 "OK

9.确保工具栏中的 "自动交易 "按钮已启用(绿色) 10.

10.面板出现在图表上,即可使用!

 

⚠️ 重要要求

- MetaTrader 5 平台

- 必须启用自动交易(绿色按钮)

- 适用于任何符号和时间框架

- 支持模拟和真实账户

- 兼容所有经纪商

 

🔧故障排除

如果按钮不起作用

1.检查自动交易是否打开(绿色按钮)

2.检查工具 → 选项 → 智能交易系统 → "允许自动交易 "是否选中

3.如果需要,重新编译 EA(MetaEditor 中的 F7)

4.检查 "专家 "选项卡是否有错误信息

 

欢迎反馈

我一直在寻求改进!如果您有功能要求或建议,请留言。

---

版本历史

v1.00 - 初次发布

- 六种仓位平仓过滤器

- 实时损益显示

- 安全确认

- 完全自定义

- 专业图形用户界面

 

//+------------------------------------------------------------------+
//|                                   一键关闭所有仓位 - 专业交易面板.mq5 |
//|                                                      最最最棒的男孩 |
//|                                                  WeChat:34373430 |
//+------------------------------------------------------------------+
#property copyright "最最最棒的男孩"
#property link      "WeChat:34373430"
#property version   "1.00"
#property description "一键关闭所有仓位 - 专业交易面板"

//--- 输入参数(可在MT5界面直接修改,无需改代码)
input ENUM_BASE_CORNER PanelCorner = CORNER_LEFT_UPPER;  // 面板显示的角落位置(默认左上)
input int PanelX = 10;                                    // 面板X轴偏移距离(像素)
input int PanelY = 50;                                    // 面板Y轴偏移距离(像素)
input color PanelBackgroundColor = clrDarkSlateGray;      // 面板背景颜色(深石板灰)
input color ButtonColorClose = clrCrimson;                // 总平仓按钮颜色(深红色)
input color ButtonColorProfit = clrForestGreen;           // 平仓盈利单按钮颜色(森林绿)
input color ButtonColorLoss = clrOrangeRed;               // 平仓亏损单按钮颜色(橙红)
input color TextColor = clrWhite;                         // 面板文字颜色(白色)

//--- 全局变量(面板样式配置,统一管理便于修改)
string prefix = "OCA_";          // 所有图表对象的前缀(避免和其他EA/指标命名冲突)
int panelWidth = 280;            // 面板总宽度(像素)
int panelHeight = 380;           // 面板总高度(像素)
int buttonHeight = 35;           // 按钮高度(像素)
int buttonSpacing = 5;           // 按钮之间的间距(像素)

//+------------------------------------------------------------------+
//| EA初始化函数(EA加载时执行一次,核心做初始化校验和面板创建)       |
//+------------------------------------------------------------------+
int OnInit()
  {
   // 第一步:删除已存在的同名面板对象(避免重复创建导致界面混乱)
   DeleteAllObjects();

   // 第二步:校验1 - 检查终端是否开启算法交易(MT5全局开关)
   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
     {
      Alert("ERROR: 终端算法交易已禁用!请在 工具->选项->智能交易 中开启");
      Print("ERROR: 终端算法交易已禁用!");
     }

   // 第三步:校验2 - 检查当前EA是否开启自动交易(工具栏AutoTrading按钮)
   if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
     {
      Alert("ERROR: 自动交易已禁用!请点击工具栏的'自动交易'按钮开启");
      Print("ERROR: 当前EA的自动交易权限已禁用!");
     }

   // 第四步:创建可视化面板(按钮、文字、背景)
   CreatePanel();
   // 第五步:初始化持仓信息显示(首次加载时刷新数据)
   UpdatePositionInfo();

   // 打印初始化日志(便于排查问题)
   Print("=== 一键平仓面板EA初始化完成 ===");
   Print("账户号: ", AccountInfoInteger(ACCOUNT_LOGIN));
   Print("当前图表品种: ", _Symbol);
   Print("初始持仓数量: ", PositionsTotal());

   return(INIT_SUCCEEDED);  // 返回初始化成功
  }

//+------------------------------------------------------------------+
//| EA卸载函数(EA移除/图表关闭/终端退出时执行)                     |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   // 仅当不是"图表切换"原因时,删除面板对象(避免切换图表误删)
   if(reason != REASON_CHARTCHANGE)
      DeleteAllObjects();
  }

//+------------------------------------------------------------------+
//| 行情Tick函数(每收到一次行情数据执行一次)                       |
//+------------------------------------------------------------------+
void OnTick()
  {
   // 实时更新持仓信息(盈亏、数量),保证面板数据和实际持仓一致
   UpdatePositionInfo();
  }

//+------------------------------------------------------------------+
//| 图表事件处理函数(监听鼠标点击、键盘操作等交互事件)             |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,                  // 事件类型ID
                  const long &lparam,            // 长整型参数(暂未使用)
                  const double &dparam,          // 浮点型参数(暂未使用)
                  const string &sparam)          // 字符串参数(点击的对象名称)
  {
   // 仅处理"对象点击"事件(按钮点击属于此类)
   if(id == CHARTEVENT_OBJECT_CLICK)
     {
      // 1. 点击"平仓所有"按钮
      if(sparam == prefix + "BtnCloseAll")
        {
         // 重置按钮状态(避免点击后一直高亮)
         ObjectSetInteger(0, sparam, OBJPROP_STATE, false);

         // 弹窗确认(显示总盈亏,防止误操作)
         if(MessageBox("确认平仓所有持仓?\n\n总盈亏: " + DoubleToString(GetTotalProfit(), 2) + " " + AccountInfoString(ACCOUNT_CURRENCY),
                       "确认平仓", MB_YESNO | MB_ICONQUESTION) == IDYES)
           {
            // 用户点击"是",执行平仓所有
            CloseAllPositions();
           }
        }
      // 2. 点击"平仓所有多单"按钮
      else if(sparam == prefix + "BtnCloseBuy")
           {
            ObjectSetInteger(0, sparam, OBJPROP_STATE, false);

            if(MessageBox("确认平仓所有多单?\n\n多单盈亏: " + DoubleToString(GetProfitByType(POSITION_TYPE_BUY), 2) + " " + AccountInfoString(ACCOUNT_CURRENCY),
                          "确认平仓多单", MB_YESNO | MB_ICONQUESTION) == IDYES)
              {
               ClosePositionsByType(POSITION_TYPE_BUY);
              }
           }
      // 3. 点击"平仓所有空单"按钮
      else if(sparam == prefix + "BtnCloseSell")
            {
             ObjectSetInteger(0, sparam, OBJPROP_STATE, false);

             if(MessageBox("确认平仓所有空单?\n\n空单盈亏: " + DoubleToString(GetProfitByType(POSITION_TYPE_SELL), 2) + " " + AccountInfoString(ACCOUNT_CURRENCY),
                           "确认平仓空单", MB_YESNO | MB_ICONQUESTION) == IDYES)
               {
                ClosePositionsByType(POSITION_TYPE_SELL);
               }
            }
      // 4. 点击"平仓当前品种所有持仓"按钮
      else if(sparam == prefix + "BtnCloseSymbol")
             {
              ObjectSetInteger(0, sparam, OBJPROP_STATE, false);

              if(MessageBox("确认平仓" + _Symbol + "的所有持仓?\n\n该品种盈亏: " + DoubleToString(GetProfitBySymbol(_Symbol), 2) + " " + AccountInfoString(ACCOUNT_CURRENCY),
                            "确认平仓当前品种", MB_YESNO | MB_ICONQUESTION) == IDYES)
                {
                 ClosePositionsBySymbol(_Symbol);
                }
             }
      // 5. 点击"平仓所有盈利单"按钮
      else if(sparam == prefix + "BtnCloseProfit")
              {
               ObjectSetInteger(0, sparam, OBJPROP_STATE, false);

               if(MessageBox("确认平仓所有盈利持仓?\n\n盈利总额: " + DoubleToString(GetProfitablePositionsProfit(), 2) + " " + AccountInfoString(ACCOUNT_CURRENCY),
                             "确认平仓盈利单", MB_YESNO | MB_ICONQUESTION) == IDYES)
                 {
                  ClosePositionsByProfit(true);
                 }
              }
      // 6. 点击"平仓所有亏损单"按钮
      else if(sparam == prefix + "BtnCloseLoss")
               {
                ObjectSetInteger(0, sparam, OBJPROP_STATE, false);

                if(MessageBox("确认平仓所有亏损持仓?\n\n亏损总额: " + DoubleToString(GetLosingPositionsProfit(), 2) + " " + AccountInfoString(ACCOUNT_CURRENCY),
                              "确认平仓亏损单", MB_YESNO | MB_ICONQUESTION) == IDYES)
                  {
                   ClosePositionsByProfit(false);
                  }
               }
      // 7. 点击"移除面板"按钮
      else if(sparam == prefix + "BtnClose")
                {
                 ObjectSetInteger(0, sparam, OBJPROP_STATE, false);
                 ExpertRemove();  // 卸载当前EA(自动触发OnDeinit删除面板)
                }

      // 延迟100ms(避免高频操作导致终端卡顿)
      Sleep(100);
      // 平仓后刷新持仓信息
      UpdatePositionInfo();
      // 强制重绘图表(保证界面实时更新)
      ChartRedraw(0);
     }
  }

//+------------------------------------------------------------------+
//| 创建可视化面板(背景、标题、文字、按钮)                         |
//+------------------------------------------------------------------+
void CreatePanel()
  {
   // 1. 创建面板背景(矩形)
   CreateRectangle(prefix + "Background", PanelX, PanelY, panelWidth, panelHeight, PanelBackgroundColor);

   // 2. 创建面板标题
   CreateLabel(prefix + "Title", PanelX + 10, PanelY + 10, "一键平仓面板", TextColor, 11, "Arial Bold");

   // 3. 创建持仓信息标签(初始值为0,后续UpdatePositionInfo更新)
   CreateLabel(prefix + "InfoPositions", PanelX + 10, PanelY + 40, "持仓数量: 0", TextColor, 9, "Arial");
   CreateLabel(prefix + "InfoProfit", PanelX + 10, PanelY + 60, "总盈亏: 0.00", TextColor, 9, "Arial");
   CreateLabel(prefix + "InfoBuy", PanelX + 10, PanelY + 80, "多单: 0 | 盈亏: 0.00", TextColor, 9, "Arial");
   CreateLabel(prefix + "InfoSell", PanelX + 10, PanelY + 100, "空单: 0 | 盈亏: 0.00", TextColor, 9, "Arial");

   // 4. 创建分隔线(视觉区分信息区和按钮区)
   CreateRectangle(prefix + "Separator", PanelX + 10, PanelY + 125, panelWidth - 20, 1, clrGray);

   // 5. 创建功能按钮(按位置依次排列)
   int btnY = PanelY + 135;  // 第一个按钮的Y轴起始位置
   // 5.1 平仓所有按钮
   CreateButton(prefix + "BtnCloseAll", PanelX + 10, btnY, panelWidth - 20, buttonHeight, "平仓所有持仓", ButtonColorClose);

   // 5.2 多单/空单平仓按钮(左右并排)
   btnY += buttonHeight + buttonSpacing;  // 按钮Y轴位置下移(高度+间距)
   CreateButton(prefix + "BtnCloseBuy", PanelX + 10, btnY, (panelWidth - 25) / 2, buttonHeight, "平仓多单", clrDodgerBlue);
   CreateButton(prefix + "BtnCloseSell", PanelX + 15 + (panelWidth - 25) / 2, btnY, (panelWidth - 25) / 2, buttonHeight, "平仓空单", clrTomato);

   // 5.3 平仓当前品种按钮
   btnY += buttonHeight + buttonSpacing;
   CreateButton(prefix + "BtnCloseSymbol", PanelX + 10, btnY, panelWidth - 20, buttonHeight, "平仓" + _Symbol, clrSlateBlue);

   // 5.4 平仓盈利单按钮
   btnY += buttonHeight + buttonSpacing;
   CreateButton(prefix + "BtnCloseProfit", PanelX + 10, btnY, panelWidth - 20, buttonHeight, "平仓盈利持仓", ButtonColorProfit);

   // 5.5 平仓亏损单按钮
   btnY += buttonHeight + buttonSpacing;
   CreateButton(prefix + "BtnCloseLoss", PanelX + 10, btnY, panelWidth - 20, buttonHeight, "平仓亏损持仓", ButtonColorLoss);

   // 5.6 移除面板按钮
   btnY += buttonHeight + buttonSpacing + 10;  // 额外加10像素间距,视觉更美观
   CreateButton(prefix + "BtnClose", PanelX + 10, btnY, panelWidth - 20, 30, "✖ 移除面板", clrDimGray);

   // 强制重绘图表(确保面板立即显示)
   ChartRedraw(0);
  }

//+------------------------------------------------------------------+
//| 更新持仓信息(实时计算并刷新面板文字显示)                       |
//+------------------------------------------------------------------+
void UpdatePositionInfo()
  {
   // 1. 获取核心持仓数据
   int totalPositions = PositionsTotal();          // 总持仓数量
   double totalProfit = GetTotalProfit();          // 总盈亏(含手续费/隔夜利息)
   int buyCount = CountPositionsByType(POSITION_TYPE_BUY);  // 多单数量
   int sellCount = CountPositionsByType(POSITION_TYPE_SELL); // 空单数量
   double buyProfit = GetProfitByType(POSITION_TYPE_BUY);    // 多单盈亏
   double sellProfit = GetProfitByType(POSITION_TYPE_SELL);  // 空单盈亏
   string currency = AccountInfoString(ACCOUNT_CURRENCY);    // 账户货币(如USD、CNY)

   // 2. 更新"持仓数量"标签
   ObjectSetString(0, prefix + "InfoPositions", OBJPROP_TEXT, "持仓数量: " + IntegerToString(totalPositions));

   // 3. 更新"总盈亏"标签(盈亏为正显示绿色,负显示红色)
   string profitText = "总盈亏: " + DoubleToString(totalProfit, 2) + " " + currency;
   color profitColor = totalProfit >= 0 ? clrLime : clrRed;
   ObjectSetString(0, prefix + "InfoProfit", OBJPROP_TEXT, profitText);
   ObjectSetInteger(0, prefix + "InfoProfit", OBJPROP_COLOR, profitColor);

   // 4. 更新"多单信息"标签(盈亏颜色区分)
   ObjectSetString(0, prefix + "InfoBuy", OBJPROP_TEXT, "多单: " + IntegerToString(buyCount) + " | 盈亏: " + DoubleToString(buyProfit, 2) + " " + currency);
   ObjectSetInteger(0, prefix + "InfoBuy", OBJPROP_COLOR, buyProfit >= 0 ? clrLightGreen : clrLightCoral);

   // 5. 更新"空单信息"标签(盈亏颜色区分)
   ObjectSetString(0, prefix + "InfoSell", OBJPROP_TEXT, "空单: " + IntegerToString(sellCount) + " | 盈亏: " + DoubleToString(sellProfit, 2) + " " + currency);
   ObjectSetInteger(0, prefix + "InfoSell", OBJPROP_COLOR, sellProfit >= 0 ? clrLightGreen : clrLightCoral);
  }

//+------------------------------------------------------------------+
//| 平仓所有持仓                                                     |
//+------------------------------------------------------------------+
void CloseAllPositions()
  {
   int total = PositionsTotal();  // 获取总持仓数
   int closed = 0;                // 成功平仓数量
   int failed = 0;                // 平仓失败数量

   Print("开始尝试平仓 ", total, " 个持仓...");

   // 遍历持仓(从后往前遍历,避免删除持仓后索引错乱)
   for(int i = total - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);  // 获取持仓单号(唯一标识)
      if(ticket > 0)  // 持仓单号有效(>0表示存在)
        {
         // 调用底层平仓函数,成功则closed+1,失败则failed+1
         if(ClosePosition(ticket))
            closed++;
         else
            failed++;
         Sleep(100); // 每平仓一个持仓延迟100ms(避免高频请求被经纪商拒绝)
        }
     }

   // 生成平仓结果提示(弹窗+日志)
   string message = "已平仓 " + IntegerToString(closed) + " / " + IntegerToString(total) + " 个持仓";
   if(failed > 0)
      message += "" + IntegerToString(failed) + " 个平仓失败)";

   Alert(message);  // 弹窗提示用户
   Print(message);  // 打印日志(便于排查失败原因)
  }

//+------------------------------------------------------------------+
//| 按类型平仓(多单/空单)                                         |
//+------------------------------------------------------------------+
void ClosePositionsByType(ENUM_POSITION_TYPE type)
  {
   int total = PositionsTotal();
   int closed = 0;
   int failed = 0;

   // 转换类型为文字(便于日志/提示)
   string typeName = type == POSITION_TYPE_BUY ? "多单" : "空单";
   Print("开始尝试平仓所有", typeName, "...");

   // 遍历持仓,仅平仓指定类型
   for(int i = total - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      // 持仓有效 + 类型匹配
      if(ticket > 0 && PositionGetInteger(POSITION_TYPE) == type)
        {
         if(ClosePosition(ticket))
            closed++;
         else
            failed++;
         Sleep(100);
        }
     }

   string message = "已平仓 " + IntegerToString(closed) + "" + typeName;
   if(failed > 0)
      message += "" + IntegerToString(failed) + " 个平仓失败)";

   Alert(message);
   Print(message);
  }

//+------------------------------------------------------------------+
//| 按品种平仓(仅平指定品种的持仓)                                 |
//+------------------------------------------------------------------+
void ClosePositionsBySymbol(string symbol)
  {
   int total = PositionsTotal();
   int closed = 0;
   int failed = 0;

   Print("开始尝试平仓", symbol, "的所有持仓...");

   // 遍历持仓,仅平仓指定品种
   for(int i = total - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      // 持仓有效 + 品种匹配
      if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == symbol)
        {
         if(ClosePosition(ticket))
            closed++;
         else
            failed++;
         Sleep(100);
        }
     }

   string message = "已平仓 " + IntegerToString(closed) + "" + symbol + "持仓";
   if(failed > 0)
      message += "" + IntegerToString(failed) + " 个平仓失败)";

   Alert(message);
   Print(message);
  }

//+------------------------------------------------------------------+
//| 按盈亏平仓(仅平盈利单/仅平亏损单)                             |
//+------------------------------------------------------------------+
void ClosePositionsByProfit(bool closeProfitable)
  {
   int total = PositionsTotal();
   int closed = 0;
   int failed = 0;

   // 转换类型为文字
   string typeName = closeProfitable ? "盈利单" : "亏损单";
   Print("开始尝试平仓所有", typeName, "...");

   // 遍历持仓,仅平仓指定盈亏类型
   for(int i = total - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket > 0)
        {
         // 持仓总盈亏 = 平仓盈亏 + 隔夜利息(Swap)
         double profit = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);

         // 盈利单:profit>0;亏损单:profit<0(等于0的持仓不处理)
         if((closeProfitable && profit > 0) || (!closeProfitable && profit < 0))
           {
            if(ClosePosition(ticket))
               closed++;
            else
               failed++;
            Sleep(100);
           }
        }
     }

   string message = "已平仓 " + IntegerToString(closed) + "" + typeName;
   if(failed > 0)
      message += "" + IntegerToString(failed) + " 个平仓失败)";

   Alert(message);
   Print(message);
  }

//+------------------------------------------------------------------+
//| 底层平仓函数(单持仓平仓核心逻辑,适配不同经纪商成交规则)       |
//+------------------------------------------------------------------+
bool ClosePosition(ulong ticket)
  {
   Print("=== 开始平仓持仓 #", ticket, " ===");

   // 第一步:校验持仓是否存在(避免平仓已关闭的持仓)
   if(!PositionSelectByTicket(ticket))
     {
      Print("ERROR: 持仓单号 ", ticket, " 不存在或已平仓");
      return false;
     }

   // 第二步:获取持仓详细信息(用于构造平仓请求)
   string symbol = PositionGetString(POSITION_SYMBOL);          // 持仓品种
   double volume = PositionGetDouble(POSITION_VOLUME);          // 持仓手数
   ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // 持仓类型(多/空)
   ulong magic = PositionGetInteger(POSITION_MAGIC);            // 魔术号(EA标识)
   double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);   // 开仓价格
   double currentProfit = PositionGetDouble(POSITION_PROFIT);   // 当前盈亏

   // 打印持仓详情(便于排查问题)
   Print("持仓详情: 品种=", symbol, " 手数=", volume, " 类型=", EnumToString(posType),
         " 开仓价=", openPrice, " 当前盈亏=", currentProfit);

   // 第三步:校验品种是否允许交易(避免平停牌/禁止交易的品种)
   if(!SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE))
     {
      Print("ERROR: 品种", symbol, "当前禁止交易");
      return false;
     }

   // 第四步:构造平仓请求(MQL5标准交易请求结构体)
   MqlTradeRequest request;  // 交易请求
   MqlTradeResult result;    // 交易结果
   ZeroMemory(request);      // 初始化请求(清空内存)
   ZeroMemory(result);       // 初始化结果

   request.action = TRADE_ACTION_DEAL;  // 交易动作:直接成交(平仓)
   request.position = ticket;           // 要平仓的持仓单号
   request.symbol = symbol;             // 品种
   request.volume = volume;             // 平仓手数(和持仓一致)
   request.deviation = 50;              // 滑点容忍度(50点,适配波动大的品种)
   request.magic = magic;               // 魔术号(和持仓一致)

   // 获取品种的成交模式(FOK/IOC/RETURN,不同经纪商支持不同)
   int filling = (int)SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE);

   // 第五步:设置平仓订单类型(多单平空,空单平多)
   if(posType == POSITION_TYPE_BUY)
     {
      request.type = ORDER_TYPE_SELL;                           // 多单平仓=卖出
      request.price = SymbolInfoDouble(symbol, SYMBOL_BID);     // 平仓价格=买价(BID)
     }
   else
     {
      request.type = ORDER_TYPE_BUY;                            // 空单平仓=买入
      request.price = SymbolInfoDouble(symbol, SYMBOL_ASK);     // 平仓价格=卖价(ASK)
     }

   Print("平仓订单: 类型=", EnumToString(request.type), " 价格=", request.price);

   // 第六步:尝试不同成交模式(提高平仓成功率,适配不同经纪商)
   // 模式1:FOK(全部成交否则取消)
   if(filling && SYMBOL_FILLING_FOK)
     {
      request.type_filling = ORDER_FILLING_FOK;
      Print("尝试FOK成交模式...");
      if(OrderSend(request, result))
        {
         if(result.retcode == TRADE_RETCODE_DONE)
           {
            Print("SUCCESS: 持仓", ticket, "通过FOK模式平仓成功");
            return true;
           }
        }
      Print("FOK模式失败: 错误码=", result.retcode, " - 原因=", result.comment);
     }

   // 模式2:IOC(立即成交可成交部分,剩余取消)
   if(filling && SYMBOL_FILLING_IOC)
     {
      request.type_filling = ORDER_FILLING_IOC;
      Print("尝试IOC成交模式...");
      if(OrderSend(request, result))
        {
         if(result.retcode == TRADE_RETCODE_DONE)
           {
            Print("SUCCESS: 持仓", ticket, "通过IOC模式平仓成功");
            return true;
           }
        }
      Print("IOC模式失败: 错误码=", result.retcode, " - 原因=", result.comment);
     }

   // 模式3:RETURN(默认模式,失败则返回)
   request.type_filling = ORDER_FILLING_RETURN;
   Print("尝试RETURN成交模式...");
   if(OrderSend(request, result))
     {
      if(result.retcode == TRADE_RETCODE_DONE)
        {
         Print("SUCCESS: 持仓", ticket, "通过RETURN模式平仓成功");
         return true;
        }
     }

   // 第七步:平仓失败,打印详细错误信息(便于排查)
   Print("FAILED: 持仓", ticket, "平仓失败");
   Print("最终错误: 错误码=", result.retcode, " - 原因=", result.comment);
   Print("系统错误码: ", GetLastError());

   // 错误码解析(快速定位问题)
   switch(result.retcode)
     {
      case TRADE_RETCODE_INVALID:
         Print("问题诊断: 请求参数无效");
         break;
      case TRADE_RETCODE_INVALID_VOLUME:
         Print("问题诊断: 平仓手数无效(如超过品种最小/最大手数)");
         break;
      case TRADE_RETCODE_INVALID_PRICE:
         Print("问题诊断: 平仓价格无效(如超出品种价格范围)");
         break;
      case TRADE_RETCODE_INVALID_STOPS:
         Print("问题诊断: 止损/止盈无效(此处平仓无止损,可忽略)");
         break;
      case TRADE_RETCODE_TRADE_DISABLED:
         Print("问题诊断: 终端/品种交易已禁用");
         break;
      case TRADE_RETCODE_MARKET_CLOSED:
         Print("问题诊断: 品种市场已收盘(如非交易时间)");
         break;
      case TRADE_RETCODE_NO_MONEY:
         Print("问题诊断: 账户资金不足(如保证金不够)");
         break;
      case TRADE_RETCODE_PRICE_CHANGED:
         Print("问题诊断: 价格已变化(滑点超出容忍度,需重新请求)");
         break;
      case TRADE_RETCODE_REJECT:
         Print("问题诊断: 经纪商拒绝请求(需联系经纪商)");
         break;
      case TRADE_RETCODE_ERROR:
         Print("问题诊断: 通用错误(终端/网络问题)");
         break;
      default:
         Print("问题诊断: 未知错误码");
     }

   return false;  // 平仓失败
  }

//+------------------------------------------------------------------+
//| 获取所有持仓的总盈亏(含Swap隔夜利息)                           |
//+------------------------------------------------------------------+
double GetTotalProfit()
  {
   double profit = 0;
   // 遍历所有持仓,累加盈亏
   for(int i = 0; i < PositionsTotal(); i++)
     {
      if(PositionGetTicket(i) > 0)
         profit += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
     }
   return profit;
  }

//+------------------------------------------------------------------+
//| 按类型获取盈亏(多单/空单)                                     |
//+------------------------------------------------------------------+
double GetProfitByType(ENUM_POSITION_TYPE type)
  {
   double profit = 0;
   // 遍历持仓,仅累加指定类型的盈亏
   for(int i = 0; i < PositionsTotal(); i++)
     {
      if(PositionGetTicket(i) > 0 && PositionGetInteger(POSITION_TYPE) == type)
         profit += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
     }
   return profit;
  }

//+------------------------------------------------------------------+
//| 按品种获取盈亏                                                   |
//+------------------------------------------------------------------+
double GetProfitBySymbol(string symbol)
  {
   double profit = 0;
   // 遍历持仓,仅累加指定品种的盈亏
   for(int i = 0; i < PositionsTotal(); i++)
     {
      if(PositionGetTicket(i) > 0 && PositionGetString(POSITION_SYMBOL) == symbol)
         profit += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
     }
   return profit;
  }

//+------------------------------------------------------------------+
//| 按类型统计持仓数量(多单/空单)                                 |
//+------------------------------------------------------------------+
int CountPositionsByType(ENUM_POSITION_TYPE type)
  {
   int count = 0;
   // 遍历持仓,仅统计指定类型的数量
   for(int i = 0; i < PositionsTotal(); i++)
     {
      if(PositionGetTicket(i) > 0 && PositionGetInteger(POSITION_TYPE) == type)
         count++;
     }
   return count;
  }

//+------------------------------------------------------------------+
//| 获取所有盈利持仓的总盈利额                                       |
//+------------------------------------------------------------------+
double GetProfitablePositionsProfit()
  {
   double profit = 0;
   // 遍历持仓,仅累加盈利单(profit>0)的盈亏
   for(int i = 0; i < PositionsTotal(); i++)
     {
      if(PositionGetTicket(i) > 0)
        {
         double posProfit = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
         if(posProfit > 0)
            profit += posProfit;
        }
     }
   return profit;
  }

//+------------------------------------------------------------------+
//| 获取所有亏损持仓的总亏损额                                       |
//+------------------------------------------------------------------+
double GetLosingPositionsProfit()
  {
   double profit = 0;
   // 遍历持仓,仅累加亏损单(profit<0)的盈亏
   for(int i = 0; i < PositionsTotal(); i++)
     {
      if(PositionGetTicket(i) > 0)
        {
         double posProfit = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
         if(posProfit < 0)
            profit += posProfit;
        }
     }
   return profit;
  }

//+------------------------------------------------------------------+
//| 创建按钮(封装重复逻辑,便于批量创建)                           |
//+------------------------------------------------------------------+
void CreateButton(string name, int x, int y, int width, int height, string text, color clr)
  {
   // 创建按钮对象(OBJ_BUTTON为MQL5按钮类型)
   ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0);
   // 设置按钮锚点(和面板一致)
   ObjectSetInteger(0, name, OBJPROP_CORNER, PanelCorner);
   // 设置按钮位置(X/Y偏移)
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   // 设置按钮尺寸(宽/高)
   ObjectSetInteger(0, name, OBJPROP_XSIZE, width);
   ObjectSetInteger(0, name, OBJPROP_YSIZE, height);
   // 设置按钮文字
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   // 设置文字颜色
   ObjectSetInteger(0, name, OBJPROP_COLOR, TextColor);
   // 设置按钮背景色
   ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clr);
   // 设置按钮边框颜色(黑色)
   ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, clrBlack);
   // 设置文字大小
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9);
   // 设置文字字体(粗体Arial)
   ObjectSetString(0, name, OBJPROP_FONT, "Arial Bold");
  }

//+------------------------------------------------------------------+
//| 创建文字标签(封装重复逻辑)                                     |
//+------------------------------------------------------------------+
void CreateLabel(string name, int x, int y, string text, color clr, int size, string font)
  {
   // 创建文字标签对象(OBJ_LABEL为MQL5文字类型)
   ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
   // 设置锚点
   ObjectSetInteger(0, name, OBJPROP_CORNER, PanelCorner);
   // 设置位置
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   // 设置文字内容
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   // 设置文字颜色
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   // 设置文字大小
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, size);
   // 设置字体
   ObjectSetString(0, name, OBJPROP_FONT, font);
  }

//+------------------------------------------------------------------+
//| 创建矩形(用于面板背景/分隔线)                                 |
//+------------------------------------------------------------------+
void CreateRectangle(string name, int x, int y, int width, int height, color clr)
  {
   // 创建矩形对象(OBJ_RECTANGLE_LABEL为带背景的矩形)
   ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   // 设置锚点
   ObjectSetInteger(0, name, OBJPROP_CORNER, PanelCorner);
   // 设置位置
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   // 设置尺寸
   ObjectSetInteger(0, name, OBJPROP_XSIZE, width);
   ObjectSetInteger(0, name, OBJPROP_YSIZE, height);
   // 设置背景色
   ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clr);
   // 设置边框样式(扁平)
   ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   // 设置边框颜色(黑色)
   ObjectSetInteger(0, name, OBJPROP_COLOR, clrBlack);
  }

//+------------------------------------------------------------------+
//| 删除所有面板对象(避免残留)                                     |
//+------------------------------------------------------------------+
void DeleteAllObjects()
  {
   // 删除所有以prefix为前缀的对象(精准删除当前EA创建的面板)
   ObjectsDeleteAll(0, prefix);
   // 强制重绘图表(立即生效)
   ChartRedraw(0);
  }
//+------------------------------------------------------------------+

// ===================== 扩展运用方式 =====================
/**
 * 1. 基础部署使用
 *    - 步骤1:将代码保存为OneClickCloseAll.mq5,放入MT5的MQL5/Experts目录;
 *    - 步骤2:在MT5中编译代码(F7),确保无报错;
 *    - 步骤3:将EA拖到任意品种图表,在EA设置中调整面板位置/颜色,勾选"允许自动交易";
 *    - 步骤4:点击面板按钮即可按条件平仓,所有操作有弹窗确认+日志记录。
 *
 * 2. 功能扩展方向(二次开发)
 *    - 扩展1:按魔术号平仓(新增按钮+函数,筛选PositionGetInteger(POSITION_MAGIC) == 指定魔术号);
 *    - 扩展2:按盈亏比例平仓(如平仓盈亏>10%的持仓,修改ClosePositionsByProfit的判断条件);
 *    - 扩展3:批量平仓指定品种(新增输入参数,支持输入多个品种,遍历平仓);
 *    - 扩展4:添加平仓确认密码(防止误操作,在MessageBox后增加密码输入校验);
 *    - 扩展5:自动平仓(如亏损超过X金额自动平仓,在OnTick中添加判断逻辑)。
 *
 * 3. 注意事项
 *    - 务必开启MT5的"算法交易"和"自动交易"开关,否则无法平仓;
 *    - 滑点参数(deviation=50)可根据品种调整(如外汇默认50,加密货币可设100);
 *    - 平仓失败时查看MT5日志(终端->专家),根据错误码排查问题(如经纪商禁用FOK/IOC模式);
 *    - 建议先在模拟账户测试,确认功能正常后再用于实盘。
 */

 

//+------------------------------------------------------------------+
//| 智能移动止损(终极严谨版:带Magic过滤 + 无单直接退出)              |
//| 功能:遍历当前品种持仓,按盈利自动移动止损,支持Magic过滤           |
//| 规则:                                                            |
//|  1. BUY  新止损 = 旧止损 + 当前盈利                                 |
//|  2. SELL 新止损 = 旧止损 - 当前盈利                                 |
//|  3. 必须盈利 > 0                                                   |
//|  4. 新旧止损间距 ≥ 5倍点差 才允许移动(防频繁修改)                  |
//|  5. 止损只向盈利方向移动,绝不回退                                  |
//|  6. Magic 过滤:magic=0 处理所有订单;magic>0 只处理对应Magic订单    |
//|  7. 优化:无任何持仓时,函数直接退出,不做任何多余计算               |
//|  8. 纯原生MQL5,无DLL、无第三方依赖                                 |
//+------------------------------------------------------------------+

void TrailSLByProfitFinal(int magic = 0)
{
   //======================= 【提前判断:无订单直接退出】 =======================
   // 先检查账户总持仓数,如果没有任何持仓,直接返回,不执行后面任何逻辑
   if(PositionsTotal() == 0)
      return;

   //======================= 基础数据获取(只有存在订单时才获取) =======================
   // 品种最小报价单位(1点对应的价格)
   double point    = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   // 当前品种点差(单位:点)
   double spread   = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
   // 止损最小移动距离:5倍点差(换算成价格)
   double min_move = spread * 5.0 * point;

   //======================= 遍历所有持仓单 =======================
   for(int i = 0; i < PositionsTotal(); i++)
   {
      // 获取当前循环到的持仓单号
      ulong pos_ticket = PositionGetTicket(i);
      if(pos_ticket == 0) continue;

      //----------------------
      // 过滤1:只处理当前图表品种
      //----------------------
      if(PositionGetString(POSITION_SYMBOL) != _Symbol)
         continue;

      //----------------------
      // 过滤2:Magic 过滤(核心)
      //----------------------
      ulong pos_magic = PositionGetInteger(POSITION_MAGIC);
      // 如果设置了magic(≠0),并且当前订单magic不匹配
      // 则跳过这个订单,继续处理下一个订单,不修改它
      if(magic != 0 && pos_magic != magic)
         continue;

      //----------------------
      // 读取当前订单基础信息
      //----------------------
      ENUM_POSITION_TYPE pos_type   = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      double             pos_open   = PositionGetDouble(POSITION_PRICE_OPEN);
      double             pos_sl_old = PositionGetDouble(POSITION_SL);
      double             pos_tp     = PositionGetDouble(POSITION_TP);
      double profit   = 0.0;
      double sl_new   = 0.0;
      bool   modify   = false;

      //----------------------
      // 多单 BUY 处理
      //----------------------
      if(pos_type == POSITION_TYPE_BUY)
      {
         double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
         profit = bid - pos_open;
         if(profit > 0)
         {
            sl_new = pos_sl_old + profit;
            // 必须同时满足3个条件才修改:
            // 1. 新止损比旧止损更高(只向前不后退)
            // 2. 移动距离 >= 5倍点差(防频繁修改)
            // 3. 新止损必须在当前买价下方(合法止损)
            if(sl_new > pos_sl_old && 
               (sl_new - pos_sl_old) >= min_move && 
               sl_new < bid)
            {
               modify = true;
            }
         }
      }
      //----------------------
      // 空单 SELL 处理
      //----------------------
      else if(pos_type == POSITION_TYPE_SELL)
      {
         double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
         profit = pos_open - ask;
         if(profit > 0)
         {
            sl_new = pos_sl_old - profit;
            // 必须同时满足3个条件才修改:
            // 1. 新止损比旧止损更低(只向前不后退)
            // 2. 移动距离 >= 5倍点差(防频繁修改)
            // 3. 新止损必须在当前卖价上方(合法止损)
            if(sl_new < pos_sl_old && 
               (pos_sl_old - sl_new) >= min_move && 
               sl_new > ask)
            {
               modify = true;
            }
         }
      }

      //----------------------
      // 执行修改止损
      //----------------------
      if(modify)
      {
         bool result = PositionModify(pos_ticket, sl_new, pos_tp);
         if(result)
         {
            Print("Magic:", magic, " 订单:", pos_ticket, " 止损移动成功 → 新SL:", DoubleToString(sl_new, _Digits));
         }
         else
         {
            Print("Magic:", magic, " 订单:", pos_ticket, " 止损移动失败 错误:", GetLastError());
         }
      }
   }
}

 

在 MT5 中识别 Pinbar 和 吞没形态 主要有两种方法:一是依靠肉眼在图表上识别(适合手动交易),二是使用自定义指标自动标记(适合辅助盯盘)。
这两种形态都是经典的反转信号,但它们的结构和识别逻辑完全不同。下面我为你详细拆解如何在 MT5 中识别它们:

1. Pinbar (针形线/Pinocchio Bar) 的识别

Pinbar 的核心特征是“拒绝”——价格试图向某个方向突破,但最终被强力推回,留下一根长长的影线。

🧐 肉眼识别标准(K线结构)

在 MT5 图表上,你需要寻找满足以下条件的单根 K 线:
  • 长影线:影线(上影线或下影线)的长度至少是实体部分的 2 倍(有些策略要求 3 倍)。
  • 小实体:实体部分很小,且通常位于 K 线的顶部或底部。
  • 位置
    • 看涨 Pinbar:出现在下跌趋势中,下影线很长,像一根针一样探底回升。
    • 看跌 Pinbar:出现在上涨趋势中,上影线很长,表明上方抛压重。
  • 鼻子的位置:在更严格的定义中,Pinbar 的开盘价和收盘价(实体)应该位于这根 K 线总范围的 1/3 或 1/4 处,且最好处于近期高低点的极端位置。

⚙️ MT5 自动识别(工具辅助)

由于肉眼在海量 K 线中寻找 Pinbar 很费时,你可以使用 MT5 的自定义指标:
  • Pin Bar Detector:这是 MT5 社区非常流行的指标。安装后,它会自动扫描所有时间框架,当发现符合“影线/实体比例”的 K 线时,会在图表上画出箭头笑脸标记。
  • 参数设置:你可以在指标设置中调整“影线倍数”(如设为 2 或 3),倍数越高,筛选出的信号越稀缺但通常越可靠。

2. 吞没形态 (Engulfing Pattern) 的识别

吞没形态由两根 K 线组成,核心逻辑是“力量覆盖”——后一根 K 线的实体完全“吃掉”了前一根 K 线的实体,代表市场情绪发生了剧烈反转。

🧐 肉眼识别标准(K线组合)

在 MT5 图表上,寻找以下组合:
  • 第一根 K 线:实体较小,顺应当前的趋势(如跌势中的阴线)。
  • 第二根 K 线
    • 颜色相反:必须与第一根颜色相反。
    • 完全覆盖:第二根的实体部分(开盘价到收盘价)必须完全包住第一根的实体。
    • 注意:严格来说,不需要包住影线,只要包住实体即可,但包住影线信号更强。
  • 类型
    • 看涨吞没:下跌趋势末端,大阳线包住前一根小阴线。
    • 看跌吞没:上涨趋势末端,大阴线包住前一根小阳线。

⚙️ MT5 自动识别(工具辅助)

MT5 内置或第三方指标可以帮你快速定位:
  • Candlestick Pattern Indicators:MT5 代码库中有许多名为“Engulfing Pattern”的指标。它们会在形成吞没形态的第二根 K 线上方或下方绘制图标。
  • 编程逻辑:如果你懂一点 MQL5,识别逻辑很简单——判断 Close[1] > Open[0] 且 Open[1] < Close[0](看涨情况),即第二根的收盘价高于第一根开盘价,且第二根开盘价低于第一根收盘价。

📊 总结:MT5 识别对比表

表格
 
特征Pinbar (针形线)吞没形态 (Engulfing)
K线数量 1 根 2 根 (组合)
核心视觉 长影线,小实体,像“锤子”或“流星” 后一根大实体完全覆盖前一根小实体
市场含义 拒绝:价格试探后被推回 吞噬:反向力量彻底压倒原趋势
MT5 识别技巧 关注影线与实体的比例 (如 > 2:1) 关注第二根 K 线实体是否大于第一根
最佳辅助工具 Pin Bar Detector 指标 Engulfing Pattern 扫描指标

💡 专家提示:如何过滤假信号?

在 MT5 上识别出形态只是第一步,不要看到形态就无脑进场,否则胜率会很低。你需要结合以下两点进行“二次确认”:
  1. 关键位置 (Key Level)
    • 形态必须出现在支撑位/阻力位斐波那契回调位趋势线附近
    • 例如:一个看涨吞没如果出现在半山腰(无明显支撑),往往是陷阱;如果出现在强支撑位,则是高胜率机会。
  2. 成交量 (Volume)
    • 吞没形态的第二根 K 线,或者 Pinbar 的形成过程,最好伴随成交量的放大。这代表有大资金在参与反转。
建议你在 MT5 上加载一个 Volume Profile (成交量分布) 或简单的成交量指标,配合形态识别使用,效果会好很多。
 
//+------------------------------------------------------------------+
//|                                      GoldMultiStrategyEA.mq5     |
//|                        Multi-Strategy XAUUSD Expert Advisor      |
//|                        M1 Timeframe | Adaptive AI Scoring        |
//+------------------------------------------------------------------+
#property copyright   "GoldMultiStrategyEA"
#property link        ""
#property version     "1.00"
#property strict
#property description "Multi-strategy EA for XAUUSD M1: Aggressive Scalping, SMC/ICT, Sniper Reversal"
#property description "Features: AI Scoring, Adaptive Learning, Volatility Adaptation, Profit Protection"

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include <Trade\SymbolInfo.mqh>

//+------------------------------------------------------------------+
//| ENUMS                                                            |
//+------------------------------------------------------------------+
enum ENUM_STRATEGY_MODE
  {
   MODE_AUTO        = 0,  // Auto (AI selects)
   MODE_SCALPING    = 1,  // Mode 1: Aggressive Scalping
   MODE_SMC         = 2,  // Mode 2: Smart Money (SMC/ICT)
   MODE_REVERSAL    = 3   // Mode 3: Sniper Reversal
  };

enum ENUM_VOLATILITY
  {
   VOL_LOW    = 0,
   VOL_MEDIUM = 1,
   VOL_HIGH   = 2
  };

enum ENUM_MARKET_STATE
  {
   STATE_TREND  = 0,
   STATE_RANGE  = 1
  };

//+------------------------------------------------------------------+
//| INPUT PARAMETERS                                                 |
//+------------------------------------------------------------------+
input group "═══════════ STRATEGY ═══════════"
input ENUM_STRATEGY_MODE InpStrategyMode  = MODE_AUTO;     // Strategy Mode
input bool               InpAutoMode      = true;          // Enable Auto Mode Switching

input group "═══════════ RISK MANAGEMENT ═══════════"
input double InpRiskPerTrade      = 1.0;    // Risk Per Trade (% of balance)
input int    InpMaxTrades         = 3;      // Max Simultaneous Trades
input double InpDailyProfitTarget = 3.0;    // Daily Profit Target (%, 0=off)
input double InpDailyLossLimit    = 2.0;    // Daily Loss Limit (%, 0=off)

input group "═══════════ VOLATILITY / ATR ═══════════"
input int    InpATRPeriod         = 14;     // ATR Period
input double InpATRLowThresh      = 0.8;    // ATR Low Threshold (multiplier of avg)
input double InpATRHighThresh     = 1.5;    // ATR High Threshold (multiplier of avg)

input group "═══════════ AI SCORING ═══════════"
input double InpMinScore          = 0.70;   // Minimum Trade Score (0-1)
input double InpWeightStructure   = 0.25;   // Weight: Structure
input double InpWeightLiquidity   = 0.25;   // Weight: Liquidity
input double InpWeightVolume      = 0.20;   // Weight: Volume
input double InpWeightVolatility  = 0.15;   // Weight: Volatility
input double InpWeightEntry       = 0.15;   // Weight: Entry Precision

input group "═══════════ TRADE MANAGEMENT ═══════════"
input bool   InpUseBreakeven      = true;   // Enable Break-Even at 1R
input bool   InpUsePartialClose   = true;   // Enable 50% Partial Close at 1.5R
input bool   InpUseTrailingStop   = true;   // Enable Trailing Stop
input double InpTrailingATRMult   = 1.5;    // Trailing Stop ATR Multiplier

input group "═══════════ PROFIT PROTECTION ═══════════"
input double InpReduceRiskAt3     = 3.0;    // Reduce risk 50% at this % profit
input double InpReduceRiskAt5     = 5.0;    // Reduce risk 70% at this % profit
input double InpDrawdownFromPeak  = 2.0;    // Stop trading: drawdown from peak (%)
input int    InpConsecWinReduce   = 3;      // Reduce lot after N consecutive wins

input group "═══════════ EXECUTION ═══════════"
input int    InpMagicNumber       = 77701;  // Magic Number
input double InpMaxSpread         = 35.0;   // Max Spread (points)
input bool   InpDebugLog          = true;   // Enable Debug Logs

//+------------------------------------------------------------------+
//| GLOBAL OBJECTS                                                   |
//+------------------------------------------------------------------+
CTrade         g_trade;
CPositionInfo  g_position;
CAccountInfo   g_account;
CSymbolInfo    g_symbol;

//+------------------------------------------------------------------+
//| TRADE HISTORY RECORD                                             |
//+------------------------------------------------------------------+
struct TradeRecord
  {
   double         score;
   double         pnl;
   bool           isWin;
   ENUM_STRATEGY_MODE mode;
  };

//+------------------------------------------------------------------+
//| GLOBAL STATE                                                     |
//+------------------------------------------------------------------+
// ATR / Volatility
int               g_atrHandle        = INVALID_HANDLE;
double            g_atrBuffer[];
double            g_currentATR       = 0;
ENUM_VOLATILITY   g_volatility       = VOL_MEDIUM;

// Market State
ENUM_MARKET_STATE g_marketState      = STATE_RANGE;
ENUM_STRATEGY_MODE g_activeMode      = MODE_SCALPING;

// Daily P&L tracking
double            g_dailyStartBalance = 0;
double            g_dailyProfit       = 0;
double            g_peakProfit        = 0;
bool              g_dailyStopped      = false;
int               g_lastDay           = -1;

// Consecutive wins
int               g_consecutiveWins   = 0;

// Adaptive learning
TradeRecord       g_tradeHistory[];
int               g_historyCount      = 0;
double            g_adaptiveThreshold = 0;  // added to InpMinScore
double            g_adaptiveLotMult   = 1.0;

// Signal weights (adaptive)
double            g_wStructure, g_wLiquidity, g_wVolume, g_wVolatility, g_wEntry;

// Candle data
double            g_open[], g_high[], g_low[], g_close[];
long              g_volume[];

// Tick volume handle
int               g_volHandle = INVALID_HANDLE;

// Bar tracking
datetime          g_lastBarTime = 0;

//+------------------------------------------------------------------+
//| Expert initialization                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   // Validate symbol
   if(StringFind(Symbol(), "XAUUSD") < 0 && StringFind(Symbol(), "GOLD") < 0)
     {
      Print("WARNING: This EA is designed for XAUUSD. Current symbol: ", Symbol());
     }

   // Initialize trade object
   g_trade.SetExpertMagicNumber(InpMagicNumber);
   g_trade.SetDeviationInPoints(20);
   g_trade.SetTypeFilling(ORDER_FILLING_IOC);

   // Initialize symbol info
   g_symbol.Name(Symbol());
   g_symbol.Refresh();

   // Create ATR indicator
   g_atrHandle = iATR(Symbol(), PERIOD_M1, InpATRPeriod);
   if(g_atrHandle == INVALID_HANDLE)
     {
      Print("ERROR: Failed to create ATR indicator");
      return INIT_FAILED;
     }

   // Set array directions
   ArraySetAsSeries(g_atrBuffer, true);
   ArraySetAsSeries(g_open, true);
   ArraySetAsSeries(g_high, true);
   ArraySetAsSeries(g_low, true);
   ArraySetAsSeries(g_close, true);
   ArraySetAsSeries(g_volume, true);

   // Initialize weights
   g_wStructure  = InpWeightStructure;
   g_wLiquidity  = InpWeightLiquidity;
   g_wVolume     = InpWeightVolume;
   g_wVolatility = InpWeightVolatility;
   g_wEntry      = InpWeightEntry;

   // Initialize trade history
   ArrayResize(g_tradeHistory, 0);
   g_historyCount = 0;

   // Initialize daily tracking
   ResetDailyState();

   DebugLog("EA initialized. Mode: " + EnumToString(InpStrategyMode) +
            " | AutoMode: " + (InpAutoMode ? "ON" : "OFF") +
            " | Risk: " + DoubleToString(InpRiskPerTrade, 2) + "%");

   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Expert deinitialization                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(g_atrHandle != INVALID_HANDLE)
      IndicatorRelease(g_atrHandle);
   DebugLog("EA deinitialized. Reason: " + IntegerToString(reason));
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   // Refresh symbol data
   g_symbol.Refresh();
   g_symbol.RefreshRates();

   // Check for new day → reset daily state
   MqlDateTime dt;
   TimeCurrent(dt);
   if(dt.day_of_year != g_lastDay)
     {
      ResetDailyState();
      g_lastDay = dt.day_of_year;
     }

   // Update daily P&L
   UpdateDailyPnL();

   // Check daily limits
   if(g_dailyStopped)
      return;

   if(CheckDailyLimits())
     {
      g_dailyStopped = true;
      DebugLog("DAILY LIMIT HIT. Trading stopped for today.");
      return;
     }

   // Manage existing positions (every tick for responsiveness)
   ManageOpenPositions();

   // Only process signals on new bar
   if(!IsNewBar())
      return;

   // Load candle data
   if(!LoadCandleData())
      return;

   // Update ATR
   if(!UpdateATR())
      return;

   // Spread filter
   if(!CheckSpread())
      return;

   // Max trades check
   if(CountOpenPositions() >= InpMaxTrades)
      return;

   // Detect volatility regime
   DetectVolatility();

   // Detect market state
   DetectMarketState();

   // Determine active mode
   DetermineActiveMode();

   // Generate signal based on active mode
   int signal = 0;       // 1=BUY, -1=SELL, 0=none
   double sl = 0, tp = 0;
   double score = 0;

   switch(g_activeMode)
     {
      case MODE_SCALPING:
         signal = SignalScalping(sl, tp, score);
         break;
      case MODE_SMC:
         signal = SignalSMC(sl, tp, score);
         break;
      case MODE_REVERSAL:
         signal = SignalReversal(sl, tp, score);
         break;
      default:
         signal = SignalScalping(sl, tp, score);
         break;
     }

   // Apply adaptive threshold
   double effectiveThreshold = MathMin(InpMinScore + g_adaptiveThreshold, 0.95);

   if(signal != 0 && score >= effectiveThreshold)
     {
      ExecuteTrade(signal, sl, tp, score);
     }
  }

//+------------------------------------------------------------------+
//| UTILITY FUNCTIONS                                                |
//+------------------------------------------------------------------+
void DebugLog(string msg)
  {
   if(InpDebugLog)
      Print("[GMSE] ", msg);
  }

bool IsNewBar()
  {
   datetime currentBarTime = iTime(Symbol(), PERIOD_M1, 0);
   if(currentBarTime == g_lastBarTime)
      return false;
   g_lastBarTime = currentBarTime;
   return true;
  }

bool LoadCandleData()
  {
   if(CopyOpen(Symbol(), PERIOD_M1, 0, 100, g_open) < 100)   return false;
   if(CopyHigh(Symbol(), PERIOD_M1, 0, 100, g_high) < 100)   return false;
   if(CopyLow(Symbol(), PERIOD_M1, 0, 100, g_low) < 100)     return false;
   if(CopyClose(Symbol(), PERIOD_M1, 0, 100, g_close) < 100) return false;
   if(CopyTickVolume(Symbol(), PERIOD_M1, 0, 100, g_volume) < 100) return false;
   return true;
  }

bool UpdateATR()
  {
   if(CopyBuffer(g_atrHandle, 0, 0, 50, g_atrBuffer) < 50)
      return false;
   g_currentATR = g_atrBuffer[1]; // completed bar
   return (g_currentATR > 0);
  }

bool CheckSpread()
  {
   double spread = g_symbol.Spread();
   if(spread > InpMaxSpread)
     {
      return false;
     }
   return true;
  }

int CountOpenPositions()
  {
   int count = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(g_position.SelectByIndex(i))
        {
         if(g_position.Symbol() == Symbol() && g_position.Magic() == InpMagicNumber)
            count++;
        }
     }
   return count;
  }

//+------------------------------------------------------------------+
//| DAILY P&L MANAGEMENT                                             |
//+------------------------------------------------------------------+
void ResetDailyState()
  {
   g_dailyStartBalance = g_account.Balance();
   g_dailyProfit        = 0;
   g_peakProfit         = 0;
   g_dailyStopped       = false;
   DebugLog("Daily state reset. Start balance: " + DoubleToString(g_dailyStartBalance, 2));
  }

void UpdateDailyPnL()
  {
   double equity = g_account.Equity();
   g_dailyProfit = ((equity - g_dailyStartBalance) / g_dailyStartBalance) * 100.0;
   if(g_dailyProfit > g_peakProfit)
      g_peakProfit = g_dailyProfit;
  }

bool CheckDailyLimits()
  {
   // Profit target
   if(InpDailyProfitTarget > 0 && g_dailyProfit >= InpDailyProfitTarget)
     {
      DebugLog("Daily profit target reached: " + DoubleToString(g_dailyProfit, 2) + "%");
      return true;
     }

   // Loss limit
   if(InpDailyLossLimit > 0 && g_dailyProfit <= -InpDailyLossLimit)
     {
      DebugLog("Daily loss limit hit: " + DoubleToString(g_dailyProfit, 2) + "%");
      return true;
     }

   // Drawdown from peak (anti-giveback)
   if(g_peakProfit > InpDrawdownFromPeak && (g_peakProfit - g_dailyProfit) >= InpDrawdownFromPeak)
     {
      DebugLog("Drawdown from peak exceeded. Peak: " + DoubleToString(g_peakProfit, 2) +
               "% Current: " + DoubleToString(g_dailyProfit, 2) + "%");
      return true;
     }

   return false;
  }

//+------------------------------------------------------------------+
//| VOLATILITY DETECTION                                             |
//+------------------------------------------------------------------+
void DetectVolatility()
  {
   // Average ATR over last 50 bars
   double avgATR = 0;
   for(int i = 1; i <= 50; i++)
      avgATR += g_atrBuffer[i];
   avgATR /= 50.0;

   if(avgATR <= 0)
     {
      g_volatility = VOL_MEDIUM;
      return;
     }

   double ratio = g_currentATR / avgATR;

   if(ratio < InpATRLowThresh)
      g_volatility = VOL_LOW;
   else if(ratio > InpATRHighThresh)
      g_volatility = VOL_HIGH;
   else
      g_volatility = VOL_MEDIUM;
  }

//+------------------------------------------------------------------+
//| MARKET STATE DETECTION                                           |
//+------------------------------------------------------------------+
void DetectMarketState()
  {
   // Check for higher highs / higher lows (trend) vs range
   // Use last 20 bars, look at swing points
   int swingCount = 0;
   int hhCount = 0, hlCount = 0, lhCount = 0, llCount = 0;

   for(int i = 3; i < 18; i++)
     {
      // Swing high
      if(g_high[i] > g_high[i-1] && g_high[i] > g_high[i-2] &&
         g_high[i] > g_high[i+1] && g_high[i] > g_high[i+2])
        {
         swingCount++;
         // Compare with previous swing
         for(int j = i + 3; j < 20; j++)
           {
            if(g_high[j] > g_high[j-1] && g_high[j] > g_high[j-2] &&
               g_high[j] > g_high[j+1] && g_high[j] > g_high[j+2])
              {
               if(g_high[i] > g_high[j]) hhCount++;
               else lhCount++;
               break;
              }
           }
        }

      // Swing low
      if(g_low[i] < g_low[i-1] && g_low[i] < g_low[i-2] &&
         g_low[i] < g_low[i+1] && g_low[i] < g_low[i+2])
        {
         for(int j = i + 3; j < 20; j++)
           {
            if(g_low[j] < g_low[j-1] && g_low[j] < g_low[j-2] &&
               g_low[j] < g_low[j+1] && g_low[j] < g_low[j+2])
              {
               if(g_low[i] > g_low[j]) hlCount++;
               else llCount++;
               break;
              }
           }
        }
     }

   // ATR rising check
   bool atrRising = (g_atrBuffer[1] > g_atrBuffer[5] * 1.1);

   // Determine state
   if((hhCount >= 1 && hlCount >= 1) || (lhCount >= 1 && llCount >= 1))
     {
      if(atrRising || g_volatility == VOL_HIGH)
         g_marketState = STATE_TREND;
      else
         g_marketState = STATE_TREND;
     }
   else
     {
      g_marketState = STATE_RANGE;
     }
  }

//+------------------------------------------------------------------+
//| AUTO MODE SWITCHING                                              |
//+------------------------------------------------------------------+
void DetermineActiveMode()
  {
   if(InpStrategyMode != MODE_AUTO && !InpAutoMode)
     {
      g_activeMode = InpStrategyMode;
      return;
     }

   // Auto mode logic
   if(g_marketState == STATE_TREND && g_volatility == VOL_HIGH)
     {
      g_activeMode = MODE_SCALPING;  // Aggressive breakout scalping
     }
   else if(g_marketState == STATE_TREND && (g_volatility == VOL_MEDIUM || g_volatility == VOL_LOW))
     {
      g_activeMode = MODE_SMC;       // Structural trading
     }
   else // STATE_RANGE or exhaustion
     {
      g_activeMode = MODE_REVERSAL;  // Reversal / mean reversion
     }

   // Override: Low volatility → avoid aggressive modes
   if(g_volatility == VOL_LOW && g_activeMode == MODE_SCALPING)
      g_activeMode = MODE_REVERSAL;
  }

//+------------------------------------------------------------------+
//| HELPER: Body / Wick ratios                                       |
//+------------------------------------------------------------------+
double CandleBody(int idx)
  {
   return MathAbs(g_close[idx] - g_open[idx]);
  }

double CandleRange(int idx)
  {
   return g_high[idx] - g_low[idx];
  }

double UpperWick(int idx)
  {
   return g_high[idx] - MathMax(g_open[idx], g_close[idx]);
  }

double LowerWick(int idx)
  {
   return MathMin(g_open[idx], g_close[idx]) - g_low[idx];
  }

bool IsBullish(int idx)
  {
   return g_close[idx] > g_open[idx];
  }

bool IsBearish(int idx)
  {
   return g_close[idx] < g_open[idx];
  }

double AverageVolume(int start, int count)
  {
   double sum = 0;
   for(int i = start; i < start + count && i < ArraySize(g_volume); i++)
      sum += (double)g_volume[i];
   return sum / (double)count;
  }

//+------------------------------------------------------------------+
//| HELPER: Support / Resistance detection                           |
//+------------------------------------------------------------------+
double FindNearestSupport(int lookback)
  {
   double minLow = g_low[1];
   for(int i = 2; i < lookback; i++)
     {
      // Swing low
      if(i + 2 < lookback)
        {
         if(g_low[i] < g_low[i-1] && g_low[i] < g_low[i+1])
           {
            if(g_close[1] > g_low[i]) // price above this level
              {
               return g_low[i];
              }
           }
        }
     }
   // Fallback: lowest low
   for(int i = 1; i < lookback; i++)
      if(g_low[i] < minLow) minLow = g_low[i];
   return minLow;
  }

double FindNearestResistance(int lookback)
  {
   double maxHigh = g_high[1];
   for(int i = 2; i < lookback; i++)
     {
      if(i + 2 < lookback)
        {
         if(g_high[i] > g_high[i-1] && g_high[i] > g_high[i+1])
           {
            if(g_close[1] < g_high[i])
              {
               return g_high[i];
              }
           }
        }
     }
   for(int i = 1; i < lookback; i++)
      if(g_high[i] > maxHigh) maxHigh = g_high[i];
   return maxHigh;
  }

//+------------------------------------------------------------------+
//| HELPER: Fibonacci levels                                         |
//+------------------------------------------------------------------+
double FibLevel(double swingHigh, double swingLow, double level)
  {
   return swingHigh - (swingHigh - swingLow) * level;
  }

//+------------------------------------------------------------------+
//| HELPER: Find swing high/low in range                             |
//+------------------------------------------------------------------+
double SwingHigh(int start, int count)
  {
   double val = g_high[start];
   for(int i = start; i < start + count && i < ArraySize(g_high); i++)
      if(g_high[i] > val) val = g_high[i];
   return val;
  }

double SwingLow(int start, int count)
  {
   double val = g_low[start];
   for(int i = start; i < start + count && i < ArraySize(g_low); i++)
      if(g_low[i] < val) val = g_low[i];
   return val;
  }

//+------------------------------------------------------------------+
//| MODE 1: AGGRESSIVE SCALPING SIGNAL                               |
//+------------------------------------------------------------------+
int SignalScalping(double &sl, double &tp, double &score)
  {
   int signal = 0;
   double ask = g_symbol.Ask();
   double bid = g_symbol.Bid();
   double point = g_symbol.Point();

   // Sub-scores
   double sStructure = 0, sLiquidity = 0, sVolume = 0, sVolatility = 0, sEntry = 0;

   // Average volume
   double avgVol = AverageVolume(2, 20);
   bool volumeSpike = ((double)g_volume[1] > avgVol * 1.5);

   // Momentum candle: body > 70% of range
   bool momentumBull = IsBullish(1) && CandleBody(1) > CandleRange(1) * 0.7;
   bool momentumBear = IsBearish(1) && CandleBody(1) > CandleRange(1) * 0.7;

   // Breakout detection: price breaks recent high/low
   double recentHigh = SwingHigh(2, 15);
   double recentLow  = SwingLow(2, 15);

   bool breakoutUp   = g_close[1] > recentHigh;
   bool breakoutDown = g_close[1] < recentLow;

   // Retest: after breakout, price pulls back slightly
   bool retestUp   = breakoutUp && (g_low[1] <= recentHigh + g_currentATR * 0.3);
   bool retestDown = breakoutDown && (g_high[1] >= recentLow - g_currentATR * 0.3);

   // Dynamic SL range: 50-120 points based on ATR
   double slPoints = MathMax(50, MathMin(120, g_currentATR / point));

   // Volatility adjustment
   if(g_volatility == VOL_LOW)
      slPoints *= 0.7;
   else if(g_volatility == VOL_HIGH)
      slPoints *= 1.3;

   // BUY SIGNAL
   if((breakoutUp || retestUp) && momentumBull && volumeSpike)
     {
      signal = 1;
      sl = ask - slPoints * point;
      tp = ask + slPoints * point * 2.0;  // 1:2 RR

      // Score components
      sStructure  = breakoutUp ? 0.8 : 0.5;
      sLiquidity  = (g_close[1] > recentHigh) ? 0.7 : 0.4;
      sVolume     = volumeSpike ? 0.9 : 0.4;
      sVolatility = (g_volatility == VOL_HIGH) ? 0.9 : (g_volatility == VOL_MEDIUM ? 0.6 : 0.3);
      sEntry      = retestUp ? 0.9 : 0.6;
     }
   // SELL SIGNAL
   else if((breakoutDown || retestDown) && momentumBear && volumeSpike)
     {
      signal = -1;
      sl = bid + slPoints * point;
      tp = bid - slPoints * point * 2.0;

      sStructure  = breakoutDown ? 0.8 : 0.5;
      sLiquidity  = (g_close[1] < recentLow) ? 0.7 : 0.4;
      sVolume     = volumeSpike ? 0.9 : 0.4;
      sVolatility = (g_volatility == VOL_HIGH) ? 0.9 : (g_volatility == VOL_MEDIUM ? 0.6 : 0.3);
      sEntry      = retestDown ? 0.9 : 0.6;
     }

   // Compute weighted score
   score = sStructure  * g_wStructure +
           sLiquidity  * g_wLiquidity +
           sVolume     * g_wVolume +
           sVolatility * g_wVolatility +
           sEntry      * g_wEntry;

   if(signal != 0)
      DebugLog("SCALP Signal: " + (signal > 0 ? "BUY" : "SELL") +
               " | Score: " + DoubleToString(score, 3) +
               " [S:" + DoubleToString(sStructure,2) +
               " L:" + DoubleToString(sLiquidity,2) +
               " V:" + DoubleToString(sVolume,2) +
               " Vol:" + DoubleToString(sVolatility,2) +
               " E:" + DoubleToString(sEntry,2) + "]");

   return signal;
  }

//+------------------------------------------------------------------+
//| MODE 2: SMC / ICT SIGNAL                                        |
//+------------------------------------------------------------------+
int SignalSMC(double &sl, double &tp, double &score)
  {
   int signal = 0;
   double ask = g_symbol.Ask();
   double bid = g_symbol.Bid();
   double point = g_symbol.Point();

   double sStructure = 0, sLiquidity = 0, sVolume = 0, sVolatility = 0, sEntry = 0;

   // ── BOS / CHOCH Detection ──
   // Break of Structure: price breaks previous swing
   double prevSwingHigh = SwingHigh(5, 20);
   double prevSwingLow  = SwingLow(5, 20);
   double nearSwingHigh = SwingHigh(1, 5);
   double nearSwingLow  = SwingLow(1, 5);

   bool bosUp   = g_close[1] > prevSwingHigh;  // Bullish BOS
   bool bosDown = g_close[1] < prevSwingLow;   // Bearish BOS

   // CHOCH: Change of character - break against trend
   bool chochBull = false, chochBear = false;
   if(g_marketState == STATE_TREND)
     {
      // In downtrend, a break of recent high = CHOCH bullish
      if(g_close[2] < g_close[5] && bosUp)
         chochBull = true;
      if(g_close[2] > g_close[5] && bosDown)
         chochBear = true;
     }

   // ── Liquidity Sweep ──
   // Price spikes beyond a level then reverses
   bool sweepLow  = (g_low[1] < prevSwingLow && g_close[1] > prevSwingLow);
   bool sweepHigh = (g_high[1] > prevSwingHigh && g_close[1] < prevSwingHigh);

   // ── Order Block Detection ──
   // Last bearish candle before bullish move (demand OB)
   bool demandOB = false, supplyOB = false;
   double obLevel = 0;

   for(int i = 2; i < 15; i++)
     {
      // Demand OB: bearish candle followed by strong bullish move
      if(IsBearish(i) && IsBullish(i-1) && CandleBody(i-1) > CandleBody(i) * 1.5)
        {
         if(g_close[1] >= g_low[i] && g_close[1] <= g_high[i])
           {
            demandOB = true;
            obLevel = g_low[i];
            break;
           }
        }
      // Supply OB
      if(IsBullish(i) && IsBearish(i-1) && CandleBody(i-1) > CandleBody(i) * 1.5)
        {
         if(g_close[1] >= g_low[i] && g_close[1] <= g_high[i])
           {
            supplyOB = true;
            obLevel = g_high[i];
            break;
           }
        }
     }

   // ── Fair Value Gap ──
   bool fvgBull = false, fvgBear = false;
   double fvgTop = 0, fvgBottom = 0;

   for(int i = 2; i < 15; i++)
     {
      // Bullish FVG: gap between candle[i+1] high and candle[i-1] low
      if(i + 1 < ArraySize(g_low))
        {
         if(g_low[i-1] > g_high[i+1]) // gap up
           {
            fvgBull = true;
            fvgTop = g_low[i-1];
            fvgBottom = g_high[i+1];
            if(g_close[1] >= fvgBottom && g_close[1] <= fvgTop)
              {
               fvgBull = true;
               break;
              }
            fvgBull = false;
           }
         if(g_high[i-1] < g_low[i+1]) // gap down
           {
            fvgBear = true;
            fvgTop = g_low[i+1];
            fvgBottom = g_high[i-1];
            if(g_close[1] >= fvgBottom && g_close[1] <= fvgTop)
              {
               fvgBear = true;
               break;
              }
            fvgBear = false;
           }
        }
     }

   // ── Fibonacci Confluence ──
   double swH = SwingHigh(1, 30);
   double swL = SwingLow(1, 30);
   double fib50  = FibLevel(swH, swL, 0.50);
   double fib618 = FibLevel(swH, swL, 0.618);
   bool   fibConfluence = (g_close[1] >= fib618 && g_close[1] <= fib50);

   // ── Volume ──
   double avgVol = AverageVolume(2, 20);
   bool volConfirm = ((double)g_volume[1] > avgVol * 1.2);

   // ── Signal Assembly ──
   // BUY: Bullish BOS/CHOCH + (sweep low or demand OB or bullish FVG) + confluence
   int bullPoints = 0;
   if(bosUp || chochBull) bullPoints += 2;
   if(sweepLow)           bullPoints += 2;
   if(demandOB)           bullPoints += 2;
   if(fvgBull)            bullPoints += 1;
   if(fibConfluence)      bullPoints += 1;
   if(volConfirm)         bullPoints += 1;

   int bearPoints = 0;
   if(bosDown || chochBear) bearPoints += 2;
   if(sweepHigh)            bearPoints += 2;
   if(supplyOB)             bearPoints += 2;
   if(fvgBear)              bearPoints += 1;
   if(fibConfluence)        bearPoints += 1;
   if(volConfirm)           bearPoints += 1;

   double slDist = g_currentATR * 2.0;

   if(bullPoints >= 4 && bullPoints > bearPoints)
     {
      signal = 1;
      // SL behind liquidity
      double liquidityLevel = (sweepLow) ? g_low[1] - g_currentATR * 0.5 :
                              (demandOB) ? obLevel - g_currentATR * 0.3 :
                              ask - slDist;
      sl = liquidityLevel;
      // TP: next liquidity zone (resistance)
      double nextLiq = FindNearestResistance(30);
      tp = (nextLiq > ask) ? nextLiq : ask + slDist * 2.0;

      // Ensure minimum 1:2 RR
      double risk = ask - sl;
      if(risk > 0 && (tp - ask) / risk < 2.0)
         tp = ask + risk * 2.0;
     }
   else if(bearPoints >= 4 && bearPoints > bullPoints)
     {
      signal = -1;
      double liquidityLevel = (sweepHigh) ? g_high[1] + g_currentATR * 0.5 :
                              (supplyOB) ? obLevel + g_currentATR * 0.3 :
                              bid + slDist;
      sl = liquidityLevel;
      double nextLiq = FindNearestSupport(30);
      tp = (nextLiq < bid) ? nextLiq : bid - slDist * 2.0;

      double risk = sl - bid;
      if(risk > 0 && (bid - tp) / risk < 2.0)
         tp = bid - risk * 2.0;
     }

   // Score
   sStructure  = MathMin(1.0, (double)(MathMax(bullPoints, bearPoints)) / 7.0);
   sLiquidity  = (sweepLow || sweepHigh) ? 0.9 : (demandOB || supplyOB) ? 0.7 : 0.3;
   sVolume     = volConfirm ? 0.8 : 0.4;
   sVolatility = (g_volatility == VOL_MEDIUM) ? 0.8 : (g_volatility == VOL_HIGH ? 0.6 : 0.5);
   sEntry      = fibConfluence ? 0.9 : (fvgBull || fvgBear) ? 0.7 : 0.4;

   score = sStructure  * g_wStructure +
           sLiquidity  * g_wLiquidity +
           sVolume     * g_wVolume +
           sVolatility * g_wVolatility +
           sEntry      * g_wEntry;

   if(signal != 0)
      DebugLog("SMC Signal: " + (signal > 0 ? "BUY" : "SELL") +
               " | BullPts:" + IntegerToString(bullPoints) +
               " BearPts:" + IntegerToString(bearPoints) +
               " | Score: " + DoubleToString(score, 3) +
               " | BOS:" + (bosUp||bosDown?"Y":"N") +
               " CHOCH:" + (chochBull||chochBear?"Y":"N") +
               " Sweep:" + (sweepLow||sweepHigh?"Y":"N") +
               " OB:" + (demandOB||supplyOB?"Y":"N") +
               " FVG:" + (fvgBull||fvgBear?"Y":"N") +
               " Fib:" + (fibConfluence?"Y":"N"));

   return signal;
  }

//+------------------------------------------------------------------+
//| MODE 3: SNIPER REVERSAL SIGNAL                                   |
//+------------------------------------------------------------------+
int SignalReversal(double &sl, double &tp, double &score)
  {
   int signal = 0;
   double ask = g_symbol.Ask();
   double bid = g_symbol.Bid();
   double point = g_symbol.Point();

   double sStructure = 0, sLiquidity = 0, sVolume = 0, sVolatility = 0, sEntry = 0;

   // ── Spike Detection ──
   // Large candle relative to ATR
   bool spike = CandleRange(1) > g_currentATR * 1.8;

   // ── Exhaustion Wick ──
   // Long wick relative to body → rejection
   double bodySize = CandleBody(1);
   double range = CandleRange(1);
   bool exhaustionWickUp   = (UpperWick(1) > bodySize * 2.0 && UpperWick(1) > range * 0.5);
   bool exhaustionWickDown = (LowerWick(1) > bodySize * 2.0 && LowerWick(1) > range * 0.5);

   // ── Strong SNR Level ──
   // Price near a significant support/resistance
   double support    = FindNearestSupport(40);
   double resistance = FindNearestResistance(40);
   double tolerance  = g_currentATR * 0.5;

   bool atSupport    = (g_close[1] <= support + tolerance && g_close[1] >= support - tolerance);
   bool atResistance = (g_close[1] >= resistance - tolerance && g_close[1] <= resistance + tolerance);

   // ── Volume Climax ──
   double avgVol = AverageVolume(2, 30);
   bool volumeClimax = ((double)g_volume[1] > avgVol * 2.0);

   // ── Mean Reversion Target ──
   // Simple: 20-bar moving average
   double sum20 = 0;
   for(int i = 1; i <= 20; i++)
      sum20 += g_close[i];
   double mean20 = sum20 / 20.0;

   // ── Signal Assembly ──
   // BUY REVERSAL: at support + exhaustion wick down + volume climax
   int bullRev = 0;
   if(atSupport)          bullRev += 2;
   if(exhaustionWickDown) bullRev += 2;
   if(spike)              bullRev += 1;
   if(volumeClimax)       bullRev += 2;
   if(g_close[1] < mean20) bullRev += 1; // below mean → room for reversion

   int bearRev = 0;
   if(atResistance)       bearRev += 2;
   if(exhaustionWickUp)   bearRev += 2;
   if(spike)              bearRev += 1;
   if(volumeClimax)       bearRev += 2;
   if(g_close[1] > mean20) bearRev += 1;

   if(bullRev >= 5 && bullRev > bearRev)
     {
      signal = 1;
      // SL beyond spike low
      sl = g_low[1] - g_currentATR * 0.5;
      // TP: mean reversion
      tp = mean20;
      // Ensure minimum 1:3 RR
      double risk = ask - sl;
      if(risk > 0)
        {
         if((tp - ask) / risk < 3.0)
            tp = ask + risk * 3.0;
        }
     }
   else if(bearRev >= 5 && bearRev > bullRev)
     {
      signal = -1;
      sl = g_high[1] + g_currentATR * 0.5;
      tp = mean20;
      double risk = sl - bid;
      if(risk > 0)
        {
         if((bid - tp) / risk < 3.0)
            tp = bid - risk * 3.0;
        }
     }

   // Score
   sStructure  = MathMin(1.0, (double)(MathMax(bullRev, bearRev)) / 7.0);
   sLiquidity  = (atSupport || atResistance) ? 0.9 : 0.3;
   sVolume     = volumeClimax ? 0.95 : 0.3;
   sVolatility = spike ? 0.8 : 0.4;
   sEntry      = (exhaustionWickUp || exhaustionWickDown) ? 0.9 : 0.4;

   score = sStructure  * g_wStructure +
           sLiquidity  * g_wLiquidity +
           sVolume     * g_wVolume +
           sVolatility * g_wVolatility +
           sEntry      * g_wEntry;

   if(signal != 0)
      DebugLog("REVERSAL Signal: " + (signal > 0 ? "BUY" : "SELL") +
               " | BullRev:" + IntegerToString(bullRev) +
               " BearRev:" + IntegerToString(bearRev) +
               " | Score: " + DoubleToString(score, 3) +
               " | Spike:" + (spike?"Y":"N") +
               " ExhWick:" + (exhaustionWickUp||exhaustionWickDown?"Y":"N") +
               " SNR:" + (atSupport||atResistance?"Y":"N") +
               " VolClimax:" + (volumeClimax?"Y":"N"));

   return signal;
  }

//+------------------------------------------------------------------+
//| LOT SIZE CALCULATION                                             |
//+------------------------------------------------------------------+
double CalculateLotSize(double slDistance)
  {
   if(slDistance <= 0)
      return g_symbol.LotsMin();

   double balance = g_account.Balance();
   double riskPct = InpRiskPerTrade;

   // ── Profit Protection: reduce risk ──
   if(g_dailyProfit >= InpReduceRiskAt5 && InpReduceRiskAt5 > 0)
      riskPct *= 0.3;  // 70% reduction
   else if(g_dailyProfit >= InpReduceRiskAt3 && InpReduceRiskAt3 > 0)
      riskPct *= 0.5;  // 50% reduction

   // ── Consecutive wins reduction ──
   if(g_consecutiveWins >= InpConsecWinReduce && InpConsecWinReduce > 0)
      riskPct *= 0.8;

   // ── Adaptive lot multiplier ──
   riskPct *= g_adaptiveLotMult;

   double riskMoney = balance * riskPct / 100.0;
   double tickValue = g_symbol.TickValue();
   double tickSize  = g_symbol.TickSize();

   if(tickValue <= 0 || tickSize <= 0)
      return g_symbol.LotsMin();

   double lots = riskMoney / (slDistance / tickSize * tickValue);

   // Normalize
   double minLot  = g_symbol.LotsMin();
   double maxLot  = g_symbol.LotsMax();
   double lotStep = g_symbol.LotsStep();

   lots = MathFloor(lots / lotStep) * lotStep;
   lots = MathMax(minLot, MathMin(maxLot, lots));

   return lots;
  }

//+------------------------------------------------------------------+
//| EXECUTE TRADE                                                    |
//+------------------------------------------------------------------+
void ExecuteTrade(int signal, double sl, double tp, double score)
  {
   double ask = g_symbol.Ask();
   double bid = g_symbol.Bid();
   double point = g_symbol.Point();

   // Sanitize SL/TP
   int digits = (int)g_symbol.Digits();
   sl = NormalizeDouble(sl, digits);
   tp = NormalizeDouble(tp, digits);

   double price, slDist;
   string comment;

   if(signal == 1)
     {
      price  = ask;
      slDist = price - sl;
      comment = StringFormat("GMSE|%s|S%.2f", EnumToString(g_activeMode), score);
     }
   else
     {
      price  = bid;
      slDist = sl - price;
      comment = StringFormat("GMSE|%s|S%.2f", EnumToString(g_activeMode), score);
     }

   if(slDist <= 0)
     {
      DebugLog("Invalid SL distance. Skipping trade.");
      return;
     }

   double lots = CalculateLotSize(slDist);

   DebugLog("EXECUTING: " + (signal > 0 ? "BUY" : "SELL") +
            " | Lots: " + DoubleToString(lots, 2) +
            " | SL: " + DoubleToString(sl, digits) +
            " | TP: " + DoubleToString(tp, digits) +
            " | Score: " + DoubleToString(score, 3) +
            " | Mode: " + EnumToString(g_activeMode) +
            " | Vol: " + EnumToString(g_volatility) +
            " | State: " + EnumToString(g_marketState));

   bool result;
   if(signal == 1)
      result = g_trade.Buy(lots, Symbol(), price, sl, tp, comment);
   else
      result = g_trade.Sell(lots, Symbol(), price, sl, tp, comment);

   if(!result)
     {
      DebugLog("ORDER FAILED: " + IntegerToString(g_trade.ResultRetcode()) +
               " - " + g_trade.ResultRetcodeDescription());
     }
   else
     {
      DebugLog("ORDER PLACED: Ticket #" + IntegerToString((int)g_trade.ResultOrder()));
     }
  }

//+------------------------------------------------------------------+
//| MANAGE OPEN POSITIONS                                            |
//+------------------------------------------------------------------+
void ManageOpenPositions()
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(!g_position.SelectByIndex(i))
         continue;
      if(g_position.Symbol() != Symbol() || g_position.Magic() != InpMagicNumber)
         continue;

      double openPrice  = g_position.PriceOpen();
      double currentSL  = g_position.StopLoss();
      double currentTP  = g_position.TakeProfit();
      double profit     = g_position.Profit();
      double volume     = g_position.Volume();
      ulong  ticket     = g_position.Ticket();

      // Calculate initial risk (1R)
      double initialRisk = MathAbs(openPrice - currentSL);
      if(initialRisk <= 0)
         continue;

      double currentPrice;
      double priceDelta;

      if(g_position.PositionType() == POSITION_TYPE_BUY)
        {
         currentPrice = g_symbol.Bid();
         priceDelta   = currentPrice - openPrice;
        }
      else
        {
         currentPrice = g_symbol.Ask();
         priceDelta   = openPrice - currentPrice;
        }

      double rMultiple = priceDelta / initialRisk;

      // ── Break-even at 1R ──
      if(InpUseBreakeven && rMultiple >= 1.0)
        {
         double beLevel;
         if(g_position.PositionType() == POSITION_TYPE_BUY)
            beLevel = openPrice + g_symbol.Spread() * g_symbol.Point();
         else
            beLevel = openPrice - g_symbol.Spread() * g_symbol.Point();

         beLevel = NormalizeDouble(beLevel, (int)g_symbol.Digits());

         if(g_position.PositionType() == POSITION_TYPE_BUY && currentSL < beLevel)
           {
            g_trade.PositionModify(ticket, beLevel, currentTP);
            DebugLog("Break-even set for BUY #" + IntegerToString((int)ticket));
           }
         else if(g_position.PositionType() == POSITION_TYPE_SELL && (currentSL > beLevel || currentSL == 0))
           {
            g_trade.PositionModify(ticket, beLevel, currentTP);
            DebugLog("Break-even set for SELL #" + IntegerToString((int)ticket));
           }
        }

      // ── Partial Close 50% at 1.5R ──
      if(InpUsePartialClose && rMultiple >= 1.5)
        {
         // Parse comment to check if already partially closed
         string comment = g_position.Comment();
         if(StringFind(comment, "PC") < 0 && volume > g_symbol.LotsMin())
           {
            double closeVol = MathFloor((volume * 0.5) / g_symbol.LotsStep()) * g_symbol.LotsStep();
            if(closeVol >= g_symbol.LotsMin())
              {
               g_trade.PositionClosePartial(ticket, closeVol);
               DebugLog("Partial close 50% for #" + IntegerToString((int)ticket) +
                        " | Closed: " + DoubleToString(closeVol, 2));
               // Note: We can't modify comment on partial, but the volume change tracks it
              }
           }
        }

      // ── Trailing Stop (ATR-based) ──
      if(InpUseTrailingStop && rMultiple >= 1.0 && g_currentATR > 0)
        {
         double trailDist = g_currentATR * InpTrailingATRMult;
         double newSL;

         if(g_position.PositionType() == POSITION_TYPE_BUY)
           {
            newSL = NormalizeDouble(currentPrice - trailDist, (int)g_symbol.Digits());
            if(newSL > currentSL && newSL < currentPrice)
              {
               g_trade.PositionModify(ticket, newSL, currentTP);
              }
           }
         else
           {
            newSL = NormalizeDouble(currentPrice + trailDist, (int)g_symbol.Digits());
            if((newSL < currentSL || currentSL == 0) && newSL > currentPrice)
              {
               g_trade.PositionModify(ticket, newSL, currentTP);
              }
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| ADAPTIVE LEARNING                                                |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
   // Only process deal additions (closed trades)
   if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
      return;

   // Check if it's our trade closing
   if(trans.deal_type != DEAL_TYPE_BUY && trans.deal_type != DEAL_TYPE_SELL)
      return;

   // Get deal info
   if(!HistoryDealSelect(trans.deal))
      return;

   long dealMagic = HistoryDealGetInteger(trans.deal, DEAL_MAGIC);
   if(dealMagic != InpMagicNumber)
      return;

   long entry = HistoryDealGetInteger(trans.deal, DEAL_ENTRY);
   if(entry != DEAL_ENTRY_OUT && entry != DEAL_ENTRY_OUT_BY)
      return;

   double dealProfit = HistoryDealGetDouble(trans.deal, DEAL_PROFIT);
   double dealSwap   = HistoryDealGetDouble(trans.deal, DEAL_SWAP);
   double dealComm   = HistoryDealGetDouble(trans.deal, DEAL_COMMISSION);
   double netPnl     = dealProfit + dealSwap + dealComm;

   // Parse score from comment
   string comment = HistoryDealGetString(trans.deal, DEAL_COMMENT);
   double tradeScore = ParseScoreFromComment(comment);

   // Record trade
   TradeRecord rec;
   rec.score = tradeScore;
   rec.pnl   = netPnl;
   rec.isWin = (netPnl > 0);
   rec.mode  = g_activeMode;

   // Add to history (keep last 50)
   int size = ArraySize(g_tradeHistory);
   if(size >= 50)
     {
      // Shift left
      for(int i = 0; i < size - 1; i++)
         g_tradeHistory[i] = g_tradeHistory[i + 1];
      g_tradeHistory[size - 1] = rec;
     }
   else
     {
      ArrayResize(g_tradeHistory, size + 1);
      g_tradeHistory[size] = rec;
     }
   g_historyCount = ArraySize(g_tradeHistory);

   // Update consecutive wins
   if(netPnl > 0)
      g_consecutiveWins++;
   else
      g_consecutiveWins = 0;

   // ── Adaptive Logic ──
   AdaptiveUpdate();

   DebugLog("TRADE CLOSED: PnL=" + DoubleToString(netPnl, 2) +
            " | Score=" + DoubleToString(tradeScore, 3) +
            " | ConsecWins=" + IntegerToString(g_consecutiveWins) +
            " | AdaptiveThresh=" + DoubleToString(g_adaptiveThreshold, 3) +
            " | AdaptiveLotMult=" + DoubleToString(g_adaptiveLotMult, 3));
  }

double ParseScoreFromComment(string comment)
  {
   // Format: "GMSE|MODE_XXX|S0.85"
   int pos = StringFind(comment, "|S");
   if(pos < 0)
      return 0.7; // default
   string scoreStr = StringSubstr(comment, pos + 2);
   return StringToDouble(scoreStr);
  }

void AdaptiveUpdate()
  {
   if(g_historyCount < 10)
      return;

   // Count wins/losses for low-score and high-score trades
   int lowScoreLoss = 0, lowScoreTotal = 0;
   int highScoreWin = 0, highScoreTotal = 0;

   double medianScore = InpMinScore;

   for(int i = 0; i < g_historyCount; i++)
     {
      if(g_tradeHistory[i].score < medianScore + 0.1)
        {
         lowScoreTotal++;
         if(!g_tradeHistory[i].isWin)
            lowScoreLoss++;
        }
      else
        {
         highScoreTotal++;
         if(g_tradeHistory[i].isWin)
            highScoreWin++;
        }
     }

   // If low-score trades losing → increase threshold
   if(lowScoreTotal > 3)
     {
      double lossRate = (double)lowScoreLoss / (double)lowScoreTotal;
      if(lossRate > 0.6)
         g_adaptiveThreshold = MathMin(g_adaptiveThreshold + 0.02, 0.15);
      else if(lossRate < 0.4)
         g_adaptiveThreshold = MathMax(g_adaptiveThreshold - 0.01, 0.0);
     }

   // If high-score trades winning → slightly increase lot
   if(highScoreTotal > 3)
     {
      double winRate = (double)highScoreWin / (double)highScoreTotal;
      if(winRate > 0.65)
         g_adaptiveLotMult = MathMin(g_adaptiveLotMult + 0.05, 1.3);
      else if(winRate < 0.4)
         g_adaptiveLotMult = MathMax(g_adaptiveLotMult - 0.05, 0.7);
     }

   // ── Reduce weight of weak signals ──
   // Calculate win rate per factor (approximate via score correlation)
   UpdateSignalWeights();
  }

void UpdateSignalWeights()
  {
   // Simple approach: if recent trades are mostly losers, slightly shift weights
   // toward structure and liquidity (most reliable) and away from volume/volatility
   if(g_historyCount < 20)
      return;

   int recentWins = 0;
   int recentCount = MathMin(20, g_historyCount);
   for(int i = g_historyCount - recentCount; i < g_historyCount; i++)
     {
      if(g_tradeHistory[i].isWin)
         recentWins++;
     }

   double winRate = (double)recentWins / (double)recentCount;

   if(winRate < 0.4)
     {
      // Shift toward structure/liquidity (more conservative)
      g_wStructure  = MathMin(0.35, g_wStructure + 0.01);
      g_wLiquidity  = MathMin(0.30, g_wLiquidity + 0.01);
      g_wVolume     = MathMax(0.10, g_wVolume - 0.01);
      g_wVolatility = MathMax(0.05, g_wVolatility - 0.005);
      g_wEntry      = MathMax(0.10, g_wEntry - 0.005);
     }
   else if(winRate > 0.6)
     {
      // Revert toward default
      g_wStructure  = g_wStructure  + (InpWeightStructure  - g_wStructure) * 0.1;
      g_wLiquidity  = g_wLiquidity  + (InpWeightLiquidity  - g_wLiquidity) * 0.1;
      g_wVolume     = g_wVolume     + (InpWeightVolume     - g_wVolume) * 0.1;
      g_wVolatility = g_wVolatility + (InpWeightVolatility - g_wVolatility) * 0.1;
      g_wEntry      = g_wEntry      + (InpWeightEntry      - g_wEntry) * 0.1;
     }

   // Normalize weights to sum to 1
   double total = g_wStructure + g_wLiquidity + g_wVolume + g_wVolatility + g_wEntry;
   if(total > 0)
     {
      g_wStructure  /= total;
      g_wLiquidity  /= total;
      g_wVolume     /= total;
      g_wVolatility /= total;
      g_wEntry      /= total;
     }
  }

//+------------------------------------------------------------------+
//| OnTimer - for periodic operations                                |
//+------------------------------------------------------------------+
void OnTimer()
  {
   // Reserved for future use (e.g., periodic state logging)
  }
//+------------------------------------------------------------------+

 

============== End

 

posted @ 2026-04-04 21:07  lsgxeva  阅读(56)  评论(0)    收藏  举报