//+------------------------------------------------------------------+
//|  AI_Capitalism_Advisor.mq5  v1.0                                 |
//|  Wild Capitalism — 10 competing AI traders                       |
//|  Everyone trades for themselves. Profit = reputation.            |
//|  Compatible with llm_capitalism_server.py  (port 8971)           |
//+------------------------------------------------------------------+
#property copyright "Wild Capitalism Engine"
#property version   "1.00"
#property description "10 independent AI traders with personal ratings"

#include <Trade\Trade.mqh>
#include <WinAPI\winhttp.mqh>

//── Inputs ──────────────────────────────────────────────
input group "=== SERVER ==="
input string   InpHost         = "127.0.0.1";
input int      InpPort         = 8971;
input int      InpReceiveMs    = 60000;   // advice from 10 traders ~15-20 sec

input group "=== TRADING ==="
input double   InpLotSize      = 0.1;     // each trader's lot
input int      InpSL_Points    = 800;     // SL for each (pp)
input int      InpTP_Points    = 2400;    // TP 1:3
input int      InpBaseMagic    = 80000;   // base magic; trader N → magic+N
input int      InpSlippage     = 10;

input group "=== AI ANALYSIS ==="
input int      InpPriceBars    = 100;     // bars for analysis
input int      InpAnalysisBars = 3;       // every N new bars
input bool     InpSendRewards  = true;    // send REWARD after closing

input group "=== COMPETITION ==="
input bool     InpAllowSameDir = true;    // allow multiple traders in the same direction
input bool     InpBankruptSkip = true;    // skip traders with wealth < -300 per 1 bar

input group "=== DISPLAY ==="
input bool     InpShowComment  = true;
input bool     InpShowLeaderboard = true;

//── Traders ─────────────────────────────────────────────────────
#define N_TRADERS 10

string g_traderNames[N_TRADERS] = {
   "GORDON","RICK","VERA","MAKS","DIANA",
   "LEO","NINA","ALEX","BORIS","KATE"
};

// The state of each trader
ulong  g_ticket[N_TRADERS];    // 0 = no position
string g_signal[N_TRADERS];    // last signal
double g_wealth[N_TRADERS];    // wealth (from the last server response)
int    g_rank[N_TRADERS];      // rank in the last answer
int    g_wins[N_TRADERS];
int    g_losses[N_TRADERS];
int    g_streak[N_TRADERS];
bool   g_rewardSent[N_TRADERS];// has a REWARD for the closed position already been sent?

// General statistics
int    g_barCount   = 0;
datetime g_lastBar  = 0;
int    g_totalTrades = 0;

// Trade objects for each trader (different magic)
CTrade g_trade[N_TRADERS];

// WebSocket
HINTERNET g_hSession   = NULL;
HINTERNET g_hConnect   = NULL;
HINTERNET g_hRequest   = NULL;
HINTERNET g_hWebSocket = NULL;
int       g_socket     = INVALID_HANDLE;
bool      g_useWS      = false;


//+------------------------------------------------------------------+
//| OnInit                                                           |
//+------------------------------------------------------------------+
int OnInit()
  {
   // Initialize trading objects
   for(int i = 0; i < N_TRADERS; i++)
     {
      int magic = InpBaseMagic + i;
      g_trade[i].SetExpertMagicNumber(magic);
      g_trade[i].SetDeviationInPoints(InpSlippage);
      g_trade[i].SetTypeFilling(ORDER_FILLING_IOC);

      g_ticket[i]    = 0;
      g_signal[i]    = "hold";
      g_wealth[i]    = 1000.0;
      g_rank[i]      = i + 1;
      g_wins[i]      = 0;
      g_losses[i]    = 0;
      g_streak[i]    = 0;
      g_rewardSent[i]= false;
     }

   Print("╔══════════════════════════════════════════════╗");
   Print("║   WILD CAPITALISM ENGINE  v1.0               ║");
   Print("║   10 competing AI traders                    ║");
   Print("╚══════════════════════════════════════════════╝");
   Print("Magic numbers: ", InpBaseMagic, " .. ", InpBaseMagic + N_TRADERS - 1);

   if(InitWebSocket())
     {
      g_useWS = true;
      Print("✓ WinHTTP WebSocket connected to ", InpHost, ":", InpPort);
     }
   else
     {
      g_socket = SocketCreate();
      if(g_socket != INVALID_HANDLE &&
         SocketConnect(g_socket, InpHost, InpPort, 3000))
         Print("✓ Socket connected");
      else
        {
         if(g_socket != INVALID_HANDLE)
           { SocketClose(g_socket); g_socket = INVALID_HANDLE; }
         Print("✗ No connection to the server! Launch llm_capitalism_server.py");
         return INIT_FAILED;
        }
     }

   return INIT_SUCCEEDED;
  }


//+------------------------------------------------------------------+
//| OnDeinit                                                         |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   string s = "stop";
   SendRaw(s);
   Sleep(300);
   if(g_useWS) CloseWebSocket();
   if(g_socket != INVALID_HANDLE)
     { SocketClose(g_socket); g_socket = INVALID_HANDLE; }
   if(InpShowComment) Comment("");
   Print("═══════════════════════════════════════");
   Print("Total trades: ", g_totalTrades);
   PrintLeaderboardConsole();
   Print("Wild Capitalism disabled.");
  }


//+------------------------------------------------------------------+
//| OnTick                                                           |
//+------------------------------------------------------------------+
void OnTick()
  {
   // Check closed positions on every tick (for REWARD)
   if(InpSendRewards)
      CheckClosedPositions();

   // New bar?
   datetime barTime = iTime(_Symbol, PERIOD_CURRENT, 0);
   if(barTime == g_lastBar) return;
   g_lastBar = barTime;
   g_barCount++;

   if(g_barCount % InpAnalysisBars != 0) return;

   // Collect prices
   double prices[];
   ArraySetAsSeries(prices, true);
   int copied = CopyClose(_Symbol, PERIOD_CURRENT, 0, InpPriceBars, prices);
   if(copied < 20) { Print("Few bars: ", copied); return; }

   string csv = "";
   for(int i = copied - 1; i >= 0; i--)
     {
      csv += DoubleToString(prices[i], _Digits);
      if(i > 0) csv += ",";
     }

   // Server request
   string cmd      = "COMPETE:" + _Symbol + ":" + csv;
   Print("▶ Send COMPETE (", copied, " bars)...");
   string response = SendCommand(cmd);

   if(StringLen(response) == 0)
     {
      Print("⚠ No server response");
      return;
     }

   // Parsing signals and rating
   ParseCompeteResponse(response);

   // Trade for each trader independently
   for(int i = 0; i < N_TRADERS; i++)
      ExecuteTraderSignal(i);

   // Update the comment
   if(InpShowComment)
      UpdateComment(barTime);
  }


//+------------------------------------------------------------------+
//| Parse COMPETE response                                           |
//+------------------------------------------------------------------+
void ParseCompeteResponse(string resp)
  {
   // 1) Signals from "signals_flat":"buy,sell,hold,..."
   string flat = ParseJson(resp, "signals_flat");
   if(StringLen(flat) == 0)
     {
      Print("⚠ signals_flat not found in response");
      return;
     }

   string parts[];
   int n = StringSplit(flat, ',', parts);
   for(int i = 0; i < N_TRADERS && i < n; i++)
     {
      StringTrimLeft(parts[i]);
      StringTrimRight(parts[i]);
      g_signal[i] = parts[i];
     }

   // 2) Rating - search by name in JSON
   //    Format: "name":"GORDON",...,"wealth":1250.0,...,"rank":1
   for(int i = 0; i < N_TRADERS; i++)
     {
      string nameSearch = "\"" + g_traderNames[i] + "\"";
      int pos = StringFind(resp, nameSearch);
      if(pos < 0) continue;

      // wealth
      double w = ParseDoubleAfterKey(resp, "wealth", pos, 200);
      if(w != 0.0) g_wealth[i] = w;

      // rank
      int r = (int)ParseDoubleAfterKey(resp, "rank", pos, 200);
      if(r > 0) g_rank[i] = r;

      // wins / losses / streak
      int wi = (int)ParseDoubleAfterKey(resp, "wins",   pos, 200);
      int lo = (int)ParseDoubleAfterKey(resp, "losses", pos, 200);
      int st = (int)ParseDoubleAfterKey(resp, "streak", pos, 400);
      if(wi >= 0) g_wins[i]   = wi;
      if(lo >= 0) g_losses[i] = lo;
      g_streak[i] = st;
     }

   // Print to log
   string line = "Signals: ";
   for(int i = 0; i < N_TRADERS; i++)
      line += g_traderNames[i] + "=" + g_signal[i] + " ";
   Print(line);
  }


//+------------------------------------------------------------------+
//| Trading logic of one trader                                      |
//+------------------------------------------------------------------+
void ExecuteTraderSignal(int idx)
  {
   string sig   = g_signal[idx];
   string name  = g_traderNames[idx];
   int    magic = InpBaseMagic + idx;

   // Trader on the verge of bankruptcy - skip the bar (if option enabled)
   if(InpBankruptSkip && g_wealth[idx] < -300.0)
     {
      Print("💀 [", name, "] BANKRUPT (", DoubleToString(g_wealth[idx], 0), ") — skip bar");
      return;
     }

   bool hasBuy  = PositionExistsByMagic(magic, POSITION_TYPE_BUY);
   bool hasSell = PositionExistsByMagic(magic, POSITION_TYPE_SELL);

   if(sig == "buy")
     {
      if(hasSell)
        {
         CloseByMagic(magic, POSITION_TYPE_SELL);
         Sleep(200);
        }
      if(!hasBuy)
         OpenPosition(idx, ORDER_TYPE_BUY);
     }
   else if(sig == "sell")
     {
      if(hasBuy)
        {
         CloseByMagic(magic, POSITION_TYPE_BUY);
         Sleep(200);
        }
      if(!hasSell)
         OpenPosition(idx, ORDER_TYPE_SELL);
     }
   // hold — do nothing, the position is held
  }


//+------------------------------------------------------------------+
//| Open a position for idx trader                                   |
//+------------------------------------------------------------------+
void OpenPosition(int idx, ENUM_ORDER_TYPE type)
  {
   string name  = g_traderNames[idx];
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   double ask   = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid   = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double sl, tp, price;
   string direction;

   if(type == ORDER_TYPE_BUY)
     {
      price     = ask;
      sl        = (InpSL_Points > 0) ? NormalizeDouble(ask - InpSL_Points * point, _Digits) : 0;
      tp        = (InpTP_Points > 0) ? NormalizeDouble(ask + InpTP_Points * point, _Digits) : 0;
      direction = "BUY";
      if(g_trade[idx].Buy(InpLotSize, _Symbol, price, sl, tp,
                           "💰" + name))
        {
         ulong ticket = g_trade[idx].ResultOrder();
         g_ticket[idx]     = ticket;
         g_rewardSent[idx] = false;
         g_totalTrades++;
         PrintFormat("✅ [%s] BUY @ %.5f  SL=%.5f  TP=%.5f  magic=%d",
                     name, price, sl, tp, InpBaseMagic + idx);
        }
      else
         PrintFormat("❌ [%s] BUY error: %d %s",
                     name, g_trade[idx].ResultRetcode(),
                     g_trade[idx].ResultRetcodeDescription());
     }
   else
     {
      price     = bid;
      sl        = (InpSL_Points > 0) ? NormalizeDouble(bid + InpSL_Points * point, _Digits) : 0;
      tp        = (InpTP_Points > 0) ? NormalizeDouble(bid - InpTP_Points * point, _Digits) : 0;
      direction = "SELL";
      if(g_trade[idx].Sell(InpLotSize, _Symbol, price, sl, tp,
                            "💰" + name))
        {
         ulong ticket = g_trade[idx].ResultOrder();
         g_ticket[idx]     = ticket;
         g_rewardSent[idx] = false;
         g_totalTrades++;
         PrintFormat("✅ [%s] SELL @ %.5f  SL=%.5f  TP=%.5f  magic=%d",
                     name, price, sl, tp, InpBaseMagic + idx);
        }
      else
         PrintFormat("❌ [%s] SELL error: %d %s",
                     name, g_trade[idx].ResultRetcode(),
                     g_trade[idx].ResultRetcodeDescription());
     }
  }


//+------------------------------------------------------------------+
//| Check if the positions have been closed. If yes, send a REWARD   |
//+------------------------------------------------------------------+
void CheckClosedPositions()
  {
   if(!InpSendRewards) return;

   for(int i = 0; i < N_TRADERS; i++)
     {
      if(g_ticket[i] == 0)   continue;
      if(g_rewardSent[i])     continue;

      // Is the position still open?
      if(PositionSelectByTicket(g_ticket[i]))
         continue;  // yes, it is open

      // Position closed - search history
      double profit = GetDealProfitByPosition(g_ticket[i]);
      if(profit != EMPTY_VALUE)
        {
         string cmd = "REWARD:" + g_traderNames[i] + ":" +
                      DoubleToString(profit, 2);
         string resp = SendCommand(cmd);
         if(StringLen(resp) > 0)
           {
            g_rewardSent[i] = true;
            string dir = (profit > 0) ? "WIN 🏆" : "LOSS 💸";
            PrintFormat("[REWARD] %s: %s %.2f → wealth=%.0f",
                        g_traderNames[i], dir, profit, g_wealth[i] + profit);
            g_wealth[i] += profit;
           }
         g_ticket[i] = 0;
        }
     }
  }


//+------------------------------------------------------------------+
//| Get the profit of a closed position from history                 |
//+------------------------------------------------------------------+
double GetDealProfitByPosition(ulong posTicket)
  {
   HistorySelect(0, TimeCurrent() + 86400);
   int total = HistoryDealsTotal();
   double totalProfit = 0.0;
   bool   found = false;

   for(int i = total - 1; i >= 0 && i >= total - 50; i--)
     {
      ulong  dTicket = HistoryDealGetTicket(i);
      ulong  dPos    = (ulong)HistoryDealGetInteger(dTicket, DEAL_POSITION_ID);
      if(dPos != posTicket) continue;

      ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dTicket, DEAL_ENTRY);
      if(entry == DEAL_ENTRY_OUT || entry == DEAL_ENTRY_INOUT)
        {
         totalProfit += HistoryDealGetDouble(dTicket, DEAL_PROFIT)
                      + HistoryDealGetDouble(dTicket, DEAL_SWAP)
                      + HistoryDealGetDouble(dTicket, DEAL_COMMISSION);
         found = true;
        }
     }
   return found ? totalProfit : EMPTY_VALUE;
  }


//+------------------------------------------------------------------+
//| Close all positions with the given 'magic' and type              |
//+------------------------------------------------------------------+
void CloseByMagic(int magic, ENUM_POSITION_TYPE type)
  {
   int idx = magic - InpBaseMagic;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(!PositionSelectByTicket(ticket)) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      if((int)PositionGetInteger(POSITION_MAGIC) != magic) continue;
      if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) != type) continue;

      // Remember the profit before closing for REWARD
      double profit = PositionGetDouble(POSITION_PROFIT)
                    + PositionGetDouble(POSITION_SWAP);

      if(g_trade[idx].PositionClose(ticket))
        {
         if(InpSendRewards && idx >= 0 && idx < N_TRADERS && !g_rewardSent[idx])
           {
            string cmd  = "REWARD:" + g_traderNames[idx] + ":" +
                          DoubleToString(profit, 2);
            string resp = SendCommand(cmd);
            if(StringLen(resp) > 0)
              {
               g_rewardSent[idx] = true;
               g_wealth[idx]    += profit;
               g_ticket[idx]     = 0;
               PrintFormat("[REWARD/FLIP] %s: %.2f", g_traderNames[idx], profit);
              }
           }
        }
     }
  }


//+------------------------------------------------------------------+
//| Check if there is a position with the given 'magic'              |
//+------------------------------------------------------------------+
bool PositionExistsByMagic(int magic, ENUM_POSITION_TYPE type)
  {
   for(int i = 0; i < PositionsTotal(); i++)
     {
      ulong ticket = PositionGetTicket(i);
      if(!PositionSelectByTicket(ticket)) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      if((int)PositionGetInteger(POSITION_MAGIC) != magic) continue;
      if((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == type)
         return true;
     }
   return false;
  }


//+------------------------------------------------------------------+
//| Chart comment                                                    |
//+------------------------------------------------------------------+
void UpdateComment(datetime barTime)
  {
   if(!InpShowComment) return;

   // Sort by wealth to display rating
   int order[N_TRADERS];
   for(int i = 0; i < N_TRADERS; i++) order[i] = i;
   // Simple bubble sort
   for(int i = 0; i < N_TRADERS - 1; i++)
      for(int j = i + 1; j < N_TRADERS; j++)
         if(g_wealth[order[j]] > g_wealth[order[i]])
           { int tmp = order[i]; order[i] = order[j]; order[j] = tmp; }

   string txt = StringFormat(
      "══ WILD CAPITALISM  v1.0 ══\n"
      "%s  |  Bar #%d\n"
      "Positions: %d  Trades: %d  Equity: %.2f\n"
      "─────────────────────────────────\n",
      TimeToString(barTime, TIME_DATE|TIME_MINUTES),
      g_barCount,
      PositionsTotal(), g_totalTrades,
      AccountInfoDouble(ACCOUNT_EQUITY)
   );

   if(InpShowLeaderboard)
     {
      txt += "  # TRADER    SIGNAL  $WEALTH  W/L   STR\n";
      txt += "  ─────────────────────────────────────\n";
      for(int k = 0; k < N_TRADERS; k++)
        {
         int i = order[k];
         // Position icon
         string posIcon = " ";
         if(PositionExistsByMagic(InpBaseMagic + i, POSITION_TYPE_BUY))
            posIcon = "↑";
         else if(PositionExistsByMagic(InpBaseMagic + i, POSITION_TYPE_SELL))
            posIcon = "↓";

         // Signal icon
         string sigIcon = "—";
         if(g_signal[i] == "buy")  sigIcon = "BUY";
         if(g_signal[i] == "sell") sigIcon = "SEL";

         // Winning streak
         string strStr = "";
         if(g_streak[i] > 0)       strStr = "+" + IntegerToString(g_streak[i]);
         else if(g_streak[i] < 0)  strStr = IntegerToString(g_streak[i]);
         else                       strStr = "0";

         txt += StringFormat("  %d %s%s%-8s %3s  %7.0f  %d/%d  %s\n",
                              k + 1,
                              posIcon, " ",
                              g_traderNames[i],
                              sigIcon,
                              g_wealth[i],
                              g_wins[i], g_losses[i],
                              strStr);
        }
     }

   Comment(txt);
  }


//+------------------------------------------------------------------+
//| Console rating when exiting                                      |
//+------------------------------------------------------------------+
void PrintLeaderboardConsole()
  {
   Print("════════ FINAL RATING ════════");
   // Sorting
   int order[N_TRADERS];
   for(int i = 0; i < N_TRADERS; i++) order[i] = i;
   for(int i = 0; i < N_TRADERS - 1; i++)
      for(int j = i + 1; j < N_TRADERS; j++)
         if(g_wealth[order[j]] > g_wealth[order[i]])
           { int tmp = order[i]; order[i] = order[j]; order[j] = tmp; }

   for(int k = 0; k < N_TRADERS; k++)
     {
      int i = order[k];
      PrintFormat("#%2d  %-8s  $%.0f  %dW/%dL  streak:%+d",
                  k + 1, g_traderNames[i],
                  g_wealth[i], g_wins[i], g_losses[i], g_streak[i]);
     }
   Print("══════════════════════════════════");
  }


//══════════════════════════════════════════════════════════════════
//  JSON helpers
//══════════════════════════════════════════════════════════════════

string ParseJson(string json, string key)
  {
   string search = "\"" + key + "\"";
   int pos = StringFind(json, search);
   if(pos < 0) return "";
   pos = StringFind(json, ":", pos);
   if(pos < 0) return "";
   pos++;
   while(pos < StringLen(json) && StringSubstr(json, pos, 1) == " ") pos++;
   if(StringSubstr(json, pos, 1) == "\"")
     {
      pos++;
      string val = "";
      while(pos < StringLen(json))
        {
         string ch = StringSubstr(json, pos, 1);
         if(ch == "\"") break;
         if(ch == "\\" && pos + 1 < StringLen(json)) { pos++; ch = StringSubstr(json, pos, 1); }
         val += ch; pos++;
        }
      return val;
     }
   else
     {
      string val = "";
      while(pos < StringLen(json))
        {
         string ch = StringSubstr(json, pos, 1);
         if(ch == "," || ch == "}" || ch == "]") break;
         val += ch; pos++;
        }
      StringTrimLeft(val); StringTrimRight(val);
      return val;
     }
  }

// Search for the 'key' numeric value, starting from startPos, in the symbols' windowSize window
double ParseDoubleAfterKey(string json, string key, int startPos, int windowSize)
  {
   string sub = StringSubstr(json, startPos, windowSize);
   string search = "\"" + key + "\"";
   int pos = StringFind(sub, search);
   if(pos < 0) return 0.0;
   pos = StringFind(sub, ":", pos);
   if(pos < 0) return 0.0;
   pos++;
   while(pos < StringLen(sub) && StringSubstr(sub, pos, 1) == " ") pos++;
   string val = "";
   bool hasMinus = false;
   string firstCh = StringSubstr(sub, pos, 1);
   if(firstCh == "-") { hasMinus = true; pos++; }
   while(pos < StringLen(sub))
     {
      string ch = StringSubstr(sub, pos, 1);
      if(ch == "," || ch == "}" || ch == "]" || ch == " " || ch == "\n") break;
      val += ch; pos++;
     }
   StringTrimLeft(val); StringTrimRight(val);
   if(StringLen(val) == 0) return 0.0;
   double result = StringToDouble(val);
   return hasMinus ? -result : result;
  }


//══════════════════════════════════════════════════════════════════
//  WebSocket / Socket (from the original EA)
//══════════════════════════════════════════════════════════════════

bool InitWebSocket()
  {
   uchar nullbuf[] = {};
   g_hSession = WinHttpOpen("MT5/Capitalism", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                             NULL, NULL, 0);
   if(!g_hSession) return false;
   g_hConnect = WinHttpConnect(g_hSession, InpHost, InpPort, 0);
   if(!g_hConnect) { CloseWebSocket(); return false; }
   g_hRequest = WinHttpOpenRequest(g_hConnect, "GET", NULL, NULL, NULL, NULL, 0);
   if(!g_hRequest) { CloseWebSocket(); return false; }
   if(!WinHttpSetOption(g_hRequest, WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET,
                        nullbuf, 0)) { CloseWebSocket(); return false; }
   if(!WinHttpSendRequest(g_hRequest, NULL, 0, nullbuf, 0, 0, 0))
     { CloseWebSocket(); return false; }
   if(!WinHttpReceiveResponse(g_hRequest, nullbuf))
     { CloseWebSocket(); return false; }
   ulong nv = 0;
   g_hWebSocket = WinHttpWebSocketCompleteUpgrade(g_hRequest, nv);
   if(!g_hWebSocket) { CloseWebSocket(); return false; }
   WinHttpCloseHandle(g_hRequest);
   g_hRequest = NULL;
   return true;
  }

void CloseWebSocket()
  {
   if(g_hWebSocket)
     {
      uchar r[];
      WinHttpWebSocketClose(g_hWebSocket,
                            WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, r, 0);
      g_hWebSocket = NULL;
     }
   if(g_hRequest)  { WinHttpCloseHandle(g_hRequest);  g_hRequest  = NULL; }
   if(g_hConnect)  { WinHttpCloseHandle(g_hConnect);  g_hConnect  = NULL; }
   if(g_hSession)  { WinHttpCloseHandle(g_hSession);  g_hSession  = NULL; }
  }

bool WsSend(string msg)
  {
   if(!g_hWebSocket) return false;
   uchar buf[];
   int n = StringToCharArray(msg, buf, 0, StringLen(msg), CP_UTF8);
   return (WinHttpWebSocketSend(g_hWebSocket,
           WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE, buf, n) == 0);
  }

string WsReceive(int ms = 60000)
  {
   if(!g_hWebSocket) return "";
   uchar buf[131072];   // 128KB — 10 traders' big response

   ZeroMemory(buf);
   ulong bytesRead = 0;
   WINHTTP_WEB_SOCKET_BUFFER_TYPE bufType =
      (WINHTTP_WEB_SOCKET_BUFFER_TYPE)(-1);
   string result = "";
   uint t0 = GetTickCount();
   while(GetTickCount() - t0 < (uint)ms)
     {
      ulong ret = WinHttpWebSocketReceive(g_hWebSocket, buf,
                                          ArraySize(buf), bytesRead, bufType);
      if(ret == 0 && bytesRead > 0)
        {
         result += CharArrayToString(buf, 0, (int)bytesRead, CP_UTF8);
         if(bufType == WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE ||
            bufType == WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE)
           { t0 = GetTickCount(); continue; }
         return result;
        }
      Sleep(50);
     }
   return result;
  }

bool SocketSendStr(string msg)
  {
   if(g_socket == INVALID_HANDLE) return false;
   uchar buf[];
   int n = StringToCharArray(msg, buf, 0, StringLen(msg), CP_UTF8);
   return (SocketSend(g_socket, buf, n) == n);
  }

string SocketReceiveStr(int ms = 60000)
  {
   if(g_socket == INVALID_HANDLE) return "";
   string res = "";
   uint t0 = GetTickCount();
   while(GetTickCount() - t0 < (uint)ms)
     {
      uint avail = SocketIsReadable(g_socket);
      if(avail > 0)
        {
         uchar buf[];
         int n = SocketRead(g_socket, buf, avail, 2000);
         if(n > 0) res += CharArrayToString(buf, 0, n, CP_UTF8);
         if(StringLen(res) > 0) break;
        }
      Sleep(50);
     }
   return res;
  }

bool SendRaw(string msg)
  { return g_useWS ? WsSend(msg) : SocketSendStr(msg); }

string ReceiveRaw()
  { return g_useWS ? WsReceive(InpReceiveMs) : SocketReceiveStr(InpReceiveMs); }

string SendCommand(string cmd)
  {
   if(!SendRaw(cmd)) return "";
   return ReceiveRaw();
  }
//+------------------------------------------------------------------+
