//+------------------------------------------------------------------+
//|                                          SpreadManualTrader.mq5  |
//|                                             
//|                                       Версия 4.6 (ручная + риск) |
//+------------------------------------------------------------------+
#property copyright "Roman"
#property version   "4.6"
#property strict

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\AccountInfo.mqh>

//+------------------------------------------------------------------+
//| Входные параметры                                                |
//+------------------------------------------------------------------+
input group "=========== ОСНОВНЫЕ НАСТРОЙКИ ==========="
input string  Inp_Comment           = "AUDJPY-NZDCHF"; // Комментарий к позициям
input int     Inp_Magic             = 0;               // Магический номер
input int     MaxRetryAttempts      = 33;              // Попыток на операцию
input int     RetryDelayMs          = 333;             // Задержка между попытками (мс)
input int     LogIntervalSec        = 60;              // Интервал вывода лога (сек), 0 – отключить

input group "=========== СИМВОЛЫ СПРЕДА ==========="
input string  Symbol1               = "AUDJPY";       // Первый символ
input string  Symbol2               = "NZDCHF";       // Второй символ
input string  Symbol3               = "";             // Третий (опционально)
input bool    Reverse2              = false;          // Инвертировать направление Symbol2
input bool    Reverse3              = false;          // Инвертировать направление Symbol3
input double  Lots1                 = 0.1;            // Лот для Symbol1
input double  Lots2                 = 0.1;            // Лот для Symbol2
input double  Lots3                 = 0.1;            // Лот для Symbol3

input group "=========== УПРАВЛЕНИЕ РИСКАМИ ==========="
input double  SL_Percent            = 0.0;            // SL % от баланса (0 – отключён)
input double  TP_Percent            = 0.0;            // TP % от баланса (0 – отключён)
input double  TrailingEquityPercent = 0.0;            // Трейлинг по эквити % (0 – отключён)

input group "=========== 📊 ТИП СЧЁТА ==========="
input bool   netting_hedge = false;        // true – неттинг, false – хэдж (информационно)

//+------------------------------------------------------------------+
//| Глобальные переменные                                            |
//+------------------------------------------------------------------+
CTrade         trade;
CPositionInfo  position;
CSymbolInfo    symInfo;
CAccountInfo   account;

string         g_symbols[3];
double         g_lots[3];
bool           g_reverse[3];
int            g_magic;
string         g_comment;
int            g_maxRetry;
int            g_retryDelay;
int            g_logInterval;
bool           g_nettingHedge;
datetime       g_lastLogTime = 0;

int            g_buyCount      = 0;
int            g_sellCount     = 0;
double         g_lastBuyPrice  = 0;
double         g_lastSellPrice = 0;

double         g_peakEquity = 0.0;
bool           g_equityTrailingActive = false;

datetime       g_lastOpenTime = 0;
double         g_peakEquitySinceLastOpen = 0.0;
bool           g_hasPosition = false;

double         g_table[10][10];
int            g_tableIndex = 0;

//+------------------------------------------------------------------+
//| Инициализация                                                    |
//+------------------------------------------------------------------+
int OnInit()
{
   g_magic    = Inp_Magic;
   g_comment  = Inp_Comment;
   g_maxRetry = MaxRetryAttempts;
   g_retryDelay = RetryDelayMs;
   g_logInterval = LogIntervalSec;
   g_nettingHedge = netting_hedge;

   g_symbols[0] = Symbol1;
   g_symbols[1] = Symbol2;
   g_symbols[2] = Symbol3;
   g_lots[0]    = Lots1;
   g_lots[1]    = Lots2;
   g_lots[2]    = Lots3;
   g_reverse[0] = false;
   g_reverse[1] = Reverse2;
   g_reverse[2] = Reverse3;

   trade.SetExpertMagicNumber(g_magic);
   trade.SetDeviationInPoints(200);
   trade.SetTypeFilling(ORDER_FILLING_FOK);

   for(int i=0; i<3; i++)
   {
      if(g_symbols[i] == "") continue;
      if(!SymbolSelect(g_symbols[i], true))
      {
         Print("❌ Символ ", g_symbols[i], " не найден!");
         return INIT_FAILED;
      }
      MqlTick tick;
      if(!SymbolInfoTick(g_symbols[i], tick))
      {
         Print("❌ Нет котировок для ", g_symbols[i]);
         return INIT_FAILED;
      }
      Print("✅ Символ ", g_symbols[i], " доступен");
   }

   if(!MQLInfoInteger(MQL_TESTER))
      CreateButtons();

   if(HasPositionsWithMagic())
   {
      Print("🔄 Обнаружены позиции по магику, восстанавливаем состояние...");
      RestoreStateFromPositions();
   }
   else
      ResetAveragingVariables();

   g_peakEquity = AccountInfoDouble(ACCOUNT_EQUITY);
   UpdateAllInfo();
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Деинициализация                                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(!MQLInfoInteger(MQL_TESTER))
   {
      ObjectDelete(0, "btn_buy");
      ObjectDelete(0, "btn_sell");
      ObjectDelete(0, "btn_close");
   }
   Comment("");
   Print("✅ Советник выгружен");
}

//+------------------------------------------------------------------+
//| OnTick                                                           |
//+------------------------------------------------------------------+
void OnTick()
{
   UpdateCounters();
   UpdateDrawdown();
   if(!MQLInfoInteger(MQL_TESTER))
      RiskManagement();
   UpdateAllInfo();

   // Логирование состояния в журнал
   if(g_logInterval > 0)
   {
      datetime now = TimeCurrent();
      if(now - g_lastLogTime >= g_logInterval)
      {
         PrintState();
         g_lastLogTime = now;
      }
   }
}

//+------------------------------------------------------------------+
//| Обработчик событий графика                                       |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   if(MQLInfoInteger(MQL_TESTER)) return;
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      if(sparam == "btn_buy")      ManualBuy();
      else if(sparam == "btn_sell") ManualSell();
      else if(sparam == "btn_close") ManualCloseAll();
      UpdateButtonTexts();
   }
}

//+------------------------------------------------------------------+
//| Ручные команды                                                   |
//+------------------------------------------------------------------+
void ManualBuy()
{
   Print("=== РУЧНАЯ ПОКУПКА СПРЕДА ===");
   if(OpenSpread(POSITION_TYPE_BUY))
   {
      Print("✅ Спред успешно куплен");
      UpdateCounters();
      SaveEntry(POSITION_TYPE_BUY);
      UpdateButtonTexts();
      g_lastOpenTime = TimeCurrent();
      g_peakEquitySinceLastOpen = AccountInfoDouble(ACCOUNT_EQUITY);
      g_hasPosition = true;
   }
   else
      Print("❌ Ошибка при покупке спреда");
}

void ManualSell()
{
   Print("=== РУЧНАЯ ПРОДАЖА СПРЕДА ===");
   if(OpenSpread(POSITION_TYPE_SELL))
   {
      Print("✅ Спред успешно продан");
      UpdateCounters();
      SaveEntry(POSITION_TYPE_SELL);
      UpdateButtonTexts();
      g_lastOpenTime = TimeCurrent();
      g_peakEquitySinceLastOpen = AccountInfoDouble(ACCOUNT_EQUITY);
      g_hasPosition = true;
   }
   else
      Print("❌ Ошибка при продаже спреда");
}

void ManualCloseAll()
{
   Print("=== РУЧНОЕ ЗАКРЫТИЕ ВСЕХ ПОЗИЦИЙ ===");
   double profit = CalculateTotalProfit();
   if(CloseAllPositions())
   {
      Print("✅ Все позиции закрыты. Итоговый P&L: ", DoubleToString(profit, 2));
      RecordCloseResult(profit);
      ResetAveragingVariables();
      UpdateButtonTexts();
      g_hasPosition = false;
      g_lastOpenTime = 0;
      g_peakEquitySinceLastOpen = 0.0;
   }
   else
      Print("❌ Ошибка при закрытии позиций");
}

//+------------------------------------------------------------------+
//| Открытие спреда (с учётом реверсов)                              |
//+------------------------------------------------------------------+
bool OpenSpread(ENUM_POSITION_TYPE type)
{
   bool success = true;
   for(int i=0; i<3; i++)
   {
      if(g_symbols[i] == "") continue;
      double lot = NormalizeLotBySymbol(g_lots[i], g_symbols[i]);
      if(lot <= 0) continue;

      ENUM_ORDER_TYPE orderType;
      if(i==0)
         orderType = (type == POSITION_TYPE_BUY) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
      else
      {
         if(g_reverse[i])
            orderType = (type == POSITION_TYPE_BUY) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
         else
            orderType = (type == POSITION_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
      }

      double price = (orderType == ORDER_TYPE_BUY) ? SymbolInfoDouble(g_symbols[i], SYMBOL_ASK)
                                                   : SymbolInfoDouble(g_symbols[i], SYMBOL_BID);

      if(!SafePositionOpen(g_symbols[i], orderType, lot, price, g_comment))
      {
         success = false;
         Print("❌ Не удалось открыть позицию по ", g_symbols[i]);
      }
      else
         Print("✅ Открыта позиция ", g_symbols[i], " ", EnumToString(orderType), " лот ", DoubleToString(lot,2));
   }
   return success;
}

//+------------------------------------------------------------------+
//| Безопасное открытие с повторами                                  |
//+------------------------------------------------------------------+
bool SafePositionOpen(string symbol, ENUM_ORDER_TYPE type, double volume, double price, string comment)
{
   int attempts = 0;
   while(attempts < g_maxRetry)
   {
      if(trade.PositionOpen(symbol, type, volume, price, 0, 0, comment))
         return true;

      attempts++;
      uint err = trade.ResultRetcode();
      Print("❌ Попытка ", attempts, "/", g_maxRetry, " открыть ", symbol, " ошибка ", err);
      if(IsCriticalError(err))
      {
         Print("❌ Критическая ошибка, попытки прекращены");
         break;
      }
      if(attempts < g_maxRetry)
      {
         Sleep(g_retryDelay);
         price = (type == ORDER_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_ASK)
                                          : SymbolInfoDouble(symbol, SYMBOL_BID);
      }
   }
   return false;
}

//+------------------------------------------------------------------+
//| Вспомогательные функции                                          |
//+------------------------------------------------------------------+
double NormalizeLotBySymbol(double lot, string symbol)
{
   CSymbolInfo sym;
   if(!sym.Name(symbol)) return lot;
   double minLot = sym.LotsMin(), maxLot = sym.LotsMax(), step = sym.LotsStep();
   if(step > 0) lot = MathRound(lot / step) * step;
   if(lot < minLot) lot = minLot;
   if(lot > maxLot) lot = maxLot;
   int digits = (minLot >= 0.1) ? 1 : (minLot >= 0.01 ? 2 : 3);
   return NormalizeDouble(lot, digits);
}

bool CloseAllPositions()
{
   bool allClosed = true;
   int total = PositionsTotal();
   for(int i=total-1; i>=0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket>0 && PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == g_magic)
         {
            if(!trade.PositionClose(ticket))
            {
               allClosed = false;
               Print("❌ Ошибка закрытия тикета ", ticket, " код ", trade.ResultRetcode());
            }
            else
               Print("✅ Закрыта позиция ", PositionGetString(POSITION_SYMBOL));
         }
      }
   }
   return allClosed;
}

int CountPositionsBySymbolAndType(string symbol, ENUM_POSITION_TYPE type)
{
   int cnt = 0;
   for(int i=0; i<PositionsTotal(); i++)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket>0 && PositionSelectByTicket(ticket))
         if(PositionGetInteger(POSITION_MAGIC) == g_magic &&
            PositionGetString(POSITION_SYMBOL) == symbol &&
            PositionGetInteger(POSITION_TYPE) == type)
            cnt++;
   }
   return cnt;
}

bool HasPositionsWithMagic()
{
   for(int i=0; i<PositionsTotal(); i++)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket>0 && PositionSelectByTicket(ticket))
         if(PositionGetInteger(POSITION_MAGIC) == g_magic)
            return true;
   }
   return false;
}

void UpdateCounters()
{
   g_buyCount  = CountPositionsBySymbolAndType(Symbol1, POSITION_TYPE_BUY);
   g_sellCount = CountPositionsBySymbolAndType(Symbol1, POSITION_TYPE_SELL);
   g_hasPosition = (g_buyCount + g_sellCount > 0);
   if(!g_hasPosition)
   {
      g_peakEquitySinceLastOpen = 0.0;
      g_lastOpenTime = 0;
   }
}

void SaveEntry(ENUM_POSITION_TYPE type)
{
   double curPrice = SymbolInfoDouble(Symbol1, SYMBOL_BID);
   if(type == POSITION_TYPE_BUY) g_lastBuyPrice = curPrice;
   else g_lastSellPrice = curPrice;
}

void ResetAveragingVariables()
{
   g_lastBuyPrice = 0; g_lastSellPrice = 0;
   g_buyCount = 0; g_sellCount = 0;
   g_equityTrailingActive = false;
   g_peakEquity = AccountInfoDouble(ACCOUNT_EQUITY);
   g_hasPosition = false;
   g_peakEquitySinceLastOpen = 0.0;
   g_lastOpenTime = 0;
}

void RestoreStateFromPositions()
{
   UpdateCounters();
   datetime latestBuy=0, latestSell=0;
   for(int i=0; i<PositionsTotal(); i++)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket>0 && PositionSelectByTicket(ticket))
      {
         if(PositionGetInteger(POSITION_MAGIC) == g_magic && PositionGetString(POSITION_SYMBOL) == Symbol1)
         {
            datetime t = (datetime)PositionGetInteger(POSITION_TIME);
            ENUM_POSITION_TYPE pt = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
            if(pt == POSITION_TYPE_BUY && t > latestBuy) { latestBuy=t; g_lastBuyPrice = PositionGetDouble(POSITION_PRICE_OPEN); }
            else if(pt == POSITION_TYPE_SELL && t > latestSell) { latestSell=t; g_lastSellPrice = PositionGetDouble(POSITION_PRICE_OPEN); }
         }
      }
   }
   if(g_hasPosition)
   {
      g_peakEquitySinceLastOpen = AccountInfoDouble(ACCOUNT_EQUITY);
      g_lastOpenTime = TimeCurrent();
   }
}

double CalculateTotalProfit()
{
   double total = 0;
   for(int i=0; i<PositionsTotal(); i++)
   {
      ulong ticket = PositionGetTicket(i);
      if(ticket>0 && PositionSelectByTicket(ticket))
         if(PositionGetInteger(POSITION_MAGIC) == g_magic)
            total += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
   }
   return total;
}

void UpdateDrawdown()
{
   if(!g_hasPosition)
   {
      g_peakEquitySinceLastOpen = 0.0;
      return;
   }
   double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
   if(g_peakEquitySinceLastOpen == 0.0)
      g_peakEquitySinceLastOpen = currentEquity;
   if(currentEquity > g_peakEquitySinceLastOpen)
      g_peakEquitySinceLastOpen = currentEquity;
}

//+------------------------------------------------------------------+
//| Риск-менеджмент                                                  |
//+------------------------------------------------------------------+
void RiskManagement()
{
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   double profit = CalculateTotalProfit();

   double slAmount = balance * SL_Percent / 100.0;
   if(SL_Percent > 0 && profit < -slAmount)
   {
      Print("🛑 СТОП-ЛОСС по проценту! P&L=", DoubleToString(profit, 2), " (лимит -", DoubleToString(slAmount, 2), " ", AccountInfoString(ACCOUNT_CURRENCY), ")");
      ManualCloseAll(); return;
   }

   double tpAmount = balance * TP_Percent / 100.0;
   if(TP_Percent > 0 && profit > tpAmount)
   {
      Print("🎯 ТЕЙК-ПРОФИТ по проценту! P&L=", DoubleToString(profit, 2), " (цель +", DoubleToString(tpAmount, 2), " ", AccountInfoString(ACCOUNT_CURRENCY), ")");
      ManualCloseAll(); return;
   }

   if(TrailingEquityPercent > 0 && profit > 0)
   {
      double equity = AccountInfoDouble(ACCOUNT_EQUITY);
      if(!g_equityTrailingActive)
      {
         if(profit > 0 && equity > g_peakEquity)
         {
            g_peakEquity = equity;
            g_equityTrailingActive = true;
            Print("🚀 Активирован трейлинг по эквити, пик = ", DoubleToString(g_peakEquity, 2));
         }
      }
      else
      {
         if(equity > g_peakEquity)
         {
            g_peakEquity = equity;
            Print("📈 Обновлён пик эквити: ", DoubleToString(g_peakEquity, 2));
         }
         double stopLevel = g_peakEquity * (1.0 - TrailingEquityPercent / 100.0);
         if(equity < stopLevel)
         {
            Print("📉 Сработал трейлинг по эквити! P&L=", DoubleToString(profit, 2));
            ManualCloseAll(); return;
         }
      }
   }
   else
   {
      if(g_equityTrailingActive)
      {
         g_equityTrailingActive = false;
         g_peakEquity = AccountInfoDouble(ACCOUNT_EQUITY);
      }
   }
}

//+------------------------------------------------------------------+
//| Логирование состояния в журнал                                   |
//+------------------------------------------------------------------+
void PrintState()
{
   string side = GetCurrentSide();
   int totalPos = 0;
   for(int i=0; i<3; i++)
      if(g_symbols[i] != "")
         totalPos += CountPositionsBySymbolAndType(g_symbols[i], POSITION_TYPE_BUY) +
                     CountPositionsBySymbolAndType(g_symbols[i], POSITION_TYPE_SELL);

   double profit = CalculateTotalProfit();
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
   string dd = "—";
   if(g_hasPosition && g_peakEquitySinceLastOpen > 0)
   {
      double ddPercent = (g_peakEquitySinceLastOpen - currentEquity) / g_peakEquitySinceLastOpen * 100.0;
      dd = DoubleToString(ddPercent, 2) + "%";
   }

   string logMsg = StringFormat("[%s] Спред: %s, Поз:%d, P&L:%.2f %s, Баланс:%.2f, Просадка:%s",
                                TimeToString(TimeCurrent(), TIME_SECONDS),
                                side,
                                totalPos,
                                profit,
                                AccountInfoString(ACCOUNT_CURRENCY),
                                balance,
                                dd);
   Print(logMsg);
}

//+------------------------------------------------------------------+
//| UI – кнопки и панель                                            |
//+------------------------------------------------------------------+
void CreateButtons()
{
   int w=80, h=25, gap=5;
   int chartW = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   int startX = (chartW - (w*3 + gap*2))/2;
   int y=20;

   ObjectCreate(0, "btn_buy", OBJ_BUTTON, 0,0,0);
   ObjectSetInteger(0, "btn_buy", OBJPROP_XDISTANCE, startX);
   ObjectSetInteger(0, "btn_buy", OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, "btn_buy", OBJPROP_XSIZE, w);
   ObjectSetInteger(0, "btn_buy", OBJPROP_YSIZE, h);
   ObjectSetInteger(0, "btn_buy", OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetString(0, "btn_buy", OBJPROP_TEXT, "КУПИТЬ");
   ObjectSetInteger(0, "btn_buy", OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(0, "btn_buy", OBJPROP_BGCOLOR, C'173,216,230');

   ObjectCreate(0, "btn_sell", OBJ_BUTTON, 0,0,0);
   ObjectSetInteger(0, "btn_sell", OBJPROP_XDISTANCE, startX + w + gap);
   ObjectSetInteger(0, "btn_sell", OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, "btn_sell", OBJPROP_XSIZE, w);
   ObjectSetInteger(0, "btn_sell", OBJPROP_YSIZE, h);
   ObjectSetInteger(0, "btn_sell", OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetString(0, "btn_sell", OBJPROP_TEXT, "ПРОДАТЬ");
   ObjectSetInteger(0, "btn_sell", OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(0, "btn_sell", OBJPROP_BGCOLOR, C'255,218,185');

   ObjectCreate(0, "btn_close", OBJ_BUTTON, 0,0,0);
   ObjectSetInteger(0, "btn_close", OBJPROP_XDISTANCE, startX + 2*(w+gap));
   ObjectSetInteger(0, "btn_close", OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, "btn_close", OBJPROP_XSIZE, w);
   ObjectSetInteger(0, "btn_close", OBJPROP_YSIZE, h);
   ObjectSetInteger(0, "btn_close", OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetString(0, "btn_close", OBJPROP_TEXT, "ЗАКРЫТЬ");
   ObjectSetInteger(0, "btn_close", OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(0, "btn_close", OBJPROP_BGCOLOR, C'220,230,240');

   UpdateButtonTexts();
}

void UpdateButtonTexts()
{
   if(MQLInfoInteger(MQL_TESTER)) return;
   int buy = CountPositionsBySymbolAndType(Symbol1, POSITION_TYPE_BUY);
   int sell = CountPositionsBySymbolAndType(Symbol1, POSITION_TYPE_SELL);
   int total = 0;
   for(int i=0; i<3; i++)
      if(g_symbols[i] != "")
         total += CountPositionsBySymbolAndType(g_symbols[i], POSITION_TYPE_BUY) +
                  CountPositionsBySymbolAndType(g_symbols[i], POSITION_TYPE_SELL);

   ObjectSetString(0, "btn_buy", OBJPROP_TEXT, (buy>0 ? "КУПИТЬ ("+IntegerToString(buy)+")" : "КУПИТЬ"));
   ObjectSetString(0, "btn_sell", OBJPROP_TEXT, (sell>0 ? "ПРОДАТЬ ("+IntegerToString(sell)+")" : "ПРОДАТЬ"));
   ObjectSetString(0, "btn_close", OBJPROP_TEXT, (total>0 ? "ЗАКРЫТЬ ("+IntegerToString(total)+")" : "ЗАКРЫТЬ"));
}

void RecordCloseResult(double profit)
{
   for(int row=9; row>=0; row--)
      for(int col=9; col>=0; col--)
      {
         if(row==0 && col==0) g_table[0][0] = profit;
         else if(col>0) g_table[row][col] = g_table[row][col-1];
         else g_table[row][0] = g_table[row-1][9];
      }
   if(g_tableIndex < 100) g_tableIndex++;
}

void UpdateAllInfo()
{
   string txt = "";
   txt += "═══════════ РУЧНОЙ СПРЕД-ТРЕЙДЕР v4.6 ═══════════\n";
   txt += "Магик: " + IntegerToString(g_magic) + "  |  " + g_comment + "\n";

   string realType = (AccountInfoInteger(ACCOUNT_MARGIN_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) ? "Хэдж" : "Неттинг";
   string userType = g_nettingHedge ? "Неттинг" : "Хэдж";
   txt += "Тип счёта: " + realType + " (пользователь: " + userType + ")\n";

   txt += "Символы: " + Symbol1;
   if(Symbol2!="") txt += " / " + Symbol2;
   if(Symbol3!="") txt += " / " + Symbol3;
   txt += "\n";
   txt += "Лоты: " + DoubleToString(Lots1,2);
   if(Symbol2!="") txt += " / " + DoubleToString(Lots2,2);
   if(Symbol3!="") txt += " / " + DoubleToString(Lots3,2);
   txt += "\n";

   txt += "─────────────── ПОЗИЦИИ ──────────────────────\n";
   string side = GetCurrentSide();
   txt += "Направление: " + side + "\n";

   int totalPos = 0;
   for(int i=0; i<3; i++)
      if(g_symbols[i] != "")
         totalPos += CountPositionsBySymbolAndType(g_symbols[i], POSITION_TYPE_BUY) +
                     CountPositionsBySymbolAndType(g_symbols[i], POSITION_TYPE_SELL);
   txt += "Позиций всего: " + IntegerToString(totalPos) + " (BUY:" + IntegerToString(g_buyCount) + ", SELL:" + IntegerToString(g_sellCount) + ")\n";

   double profit = CalculateTotalProfit();
   txt += "Общий P&L: " + DoubleToString(profit,2) + " " + AccountInfoString(ACCOUNT_CURRENCY) + "\n";

   txt += "─────────────── РИСК-МЕНЕДЖМЕНТ ──────────────\n";
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   txt += "Баланс: " + DoubleToString(balance,2) + " " + AccountInfoString(ACCOUNT_CURRENCY) + "\n";

   if(SL_Percent > 0)
   {
      double slAmount = balance * SL_Percent / 100.0;
      txt += "SL: " + DoubleToString(SL_Percent,1) + "%  =  -" + DoubleToString(slAmount,2) + " " + AccountInfoString(ACCOUNT_CURRENCY) + "\n";
   }
   else
      txt += "SL: отключён\n";

   if(TP_Percent > 0)
   {
      double tpAmount = balance * TP_Percent / 100.0;
      txt += "TP: " + DoubleToString(TP_Percent,1) + "%  =  +" + DoubleToString(tpAmount,2) + " " + AccountInfoString(ACCOUNT_CURRENCY) + "\n";
   }
   else
      txt += "TP: отключён\n";

   if(TrailingEquityPercent > 0)
   {
      txt += "Трейлинг экв.: " + DoubleToString(TrailingEquityPercent,1) + "%\n";
      if(g_equityTrailingActive)
         txt += "▶ Активен (пик " + DoubleToString(g_peakEquity,2) + " " + AccountInfoString(ACCOUNT_CURRENCY) + ")\n";
   }
   else
      txt += "Трейлинг экв.: отключён\n";

   if(g_hasPosition && g_peakEquitySinceLastOpen > 0)
   {
      double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
      double drawdownPercent = (g_peakEquitySinceLastOpen - currentEquity) / g_peakEquitySinceLastOpen * 100.0;
      txt += "Просадка (Equity): " + DoubleToString(drawdownPercent, 2) + "%\n";
   }
   else
      txt += "Просадка (Equity): —\n";

   txt += "─────────────── ИСТОРИЯ (последние 10) ────────\n";
   int maxShow = (g_tableIndex<10) ? g_tableIndex : 10;
   for(int i=0; i<maxShow; i++)
   {
      double val = g_table[i][0];
      if(val!=0)
         txt += (val>0 ? "+" : "") + DoubleToString(val,0) + " ";
   }
   txt += "\n";

   txt += "─────────────── НАСТРОЙКИ ОТКРЫТИЯ ───────────\n";
   txt += "Попыток: " + IntegerToString(g_maxRetry) + "  Задержка: " + IntegerToString(g_retryDelay) + " мс\n";
   txt += "Лог-интервал: " + IntegerToString(g_logInterval) + " сек\n";

   txt += "════════════════════════════════════════════════\n";
   Comment(txt);
}

string GetCurrentSide()
{
   if(g_buyCount > 0 && g_sellCount == 0) return "BUY";
   if(g_sellCount > 0 && g_buyCount == 0) return "SELL";
   if(g_buyCount > 0 && g_sellCount > 0) return "MIXED";
   return "FLAT";
}

bool IsCriticalError(uint err)
{
   if(err >= 10004 && err <= 10018) return true;
   if(err == 10027 || err == 10028) return true;
   return false;
}
//+------------------------------------------------------------------+
