//+------------------------------------------------------------------+
//| INKG_Bridge.mq5 — Puente soberano INKG ⇄ MetaTrader 5            |
//| Crea los mercados INKG como símbolos propios (gráficas reales)    |
//| y permite operar contra los motores del banco (futuros / DEX).    |
//| Cero dependencia de brokers: los datos y órdenes van directo a    |
//| https://futurebdetbank.com (Ley#16 / Ley#31).                     |
//|                                                                    |
//| ANTES DE USAR: Herramientas → Opciones → Asesores Expertos →       |
//|   ✔ Permitir WebRequest para: https://futurebdetbank.com           |
//+------------------------------------------------------------------+
#property copyright "Nación INKG · Future BDET Bank"
#property link      "https://trading.futurebdetbank.com"
#property version   "1.00"
#property description "Mercados INKG dentro de MT5 + operar con un clic (futuros y DEX soberano)."

input string InpToken      = "";   // Token API (trading.futurebdetbank.com → pestaña MetaTrader)
input string InpPerps      = "BTC-PERP,ETH-PERP,SOL-PERP,BNB-PERP,XRP-PERP"; // Futuros (precio real)
input string InpPools      = "WMP,WAMPUM,IGCB,IGCC,IGCS";                     // Tokens soberanos (DEX vs WUSD)
input int    InpRefreshSec = 15;   // Refresco de precios (segundos)
input double InpCollateral = 50;   // Futuros: colateral por orden (WUSD)
input int    InpLeverage   = 3;    // Futuros: apalancamiento (2-5)
input double InpBuyWUSD    = 50;   // DEX: WUSD por compra
input double InpSellTokens = 10;   // DEX: tokens por venta

const string API    = "https://futurebdetbank.com";
const string PREFIX = "INKG.";
string g_perps[];
string g_pools[];

//--- nombres de símbolos ----------------------------------------------
string PerpName(string p){ string s=p; StringReplace(s,"-",""); return PREFIX+s; }   // BTC-PERP → INKG.BTCPERP
string PoolName(string b){ return PREFIX+b; }                                         // WMP → INKG.WMP

// devuelve "perp"/"pool"/"" y el id del motor (BTC-PERP o WMP)
string KindOf(string sym,string &id)
  {
   for(int i=0;i<ArraySize(g_perps);i++) if(PerpName(g_perps[i])==sym){ id=g_perps[i]; return "perp"; }
   for(int i=0;i<ArraySize(g_pools);i++) if(PoolName(g_pools[i])==sym){ id=g_pools[i]; return "pool"; }
   id=""; return "";
  }

//--- HTTP --------------------------------------------------------------
string Http(string method,string path,string body,int &code)
  {
   string headers="Content-Type: application/json\r\n";
   if(StringLen(InpToken)>0) headers+="Authorization: Bearer "+InpToken+"\r\n";
   char data[]; char res[]; string rh;
   if(StringLen(body)>0){ int n=StringToCharArray(body,data,0,WHOLE_ARRAY,CP_UTF8); if(n>0) ArrayResize(data,n-1); }
   ResetLastError();
   code=WebRequest(method,API+path,headers,10000,data,res,rh);
   if(code==-1)
     {
      int e=GetLastError();
      if(e==4014) Print("INKG: agrega https://futurebdetbank.com en Herramientas → Opciones → Asesores Expertos → WebRequest");
      else Print("INKG: error de red ",e);
      return "";
     }
   return CharArrayToString(res,0,WHOLE_ARRAY,CP_UTF8);
  }

//--- JSON mínimo ---------------------------------------------------------
double JNum(const string s,const string key,int from=0)
  {
   int p=StringFind(s,"\""+key+"\":",from); if(p<0) return EMPTY_VALUE;
   p+=StringLen(key)+3;
   while(p<StringLen(s) && (StringGetCharacter(s,p)==' '||StringGetCharacter(s,p)=='"')) p++;
   int q=p;
   while(q<StringLen(s)){ ushort c=StringGetCharacter(s,q); if((c>='0'&&c<='9')||c=='.'||c=='-'||c=='e'||c=='E'||c=='+') q++; else break; }
   if(q==p) return EMPTY_VALUE;
   return StringToDouble(StringSubstr(s,p,q-p));
  }
string JStr(const string s,const string key,int from=0)
  {
   int p=StringFind(s,"\""+key+"\":\"",from); if(p<0) return "";
   p+=StringLen(key)+4; int q=StringFind(s,"\"",p); if(q<0) return "";
   return StringSubstr(s,p,q-p);
  }
// recorre los objetos {...} de un arreglo "key":[ ... ]
int JObjects(const string s,const string key,string &objs[])
  {
   ArrayResize(objs,0);
   int p=StringFind(s,"\""+key+"\":["); if(p<0) return 0;
   p=StringFind(s,"[",p)+1; int depth=0,start=-1,n=0;
   for(int i=p;i<StringLen(s);i++)
     {
      ushort c=StringGetCharacter(s,i);
      if(c=='{'){ if(depth==0) start=i; depth++; }
      else if(c=='}'){ depth--; if(depth==0&&start>=0){ ArrayResize(objs,n+1); objs[n++]=StringSubstr(s,start,i-start+1); start=-1; } }
      else if(c==']' && depth==0) break;
     }
   return n;
  }

//--- símbolos propios ------------------------------------------------------
int DigitsFor(double px){ if(px>=1000) return 2; if(px>=1) return 4; if(px>=0.01) return 6; return 8; }

bool EnsureSymbol(string name,string path,string desc,double px)
  {
   if(!SymbolInfoInteger(name,SYMBOL_CUSTOM))
     {
      ResetLastError();
      if(!CustomSymbolCreate(name,path) && GetLastError()!=ERR_CUSTOM_SYMBOL_EXIST){ Print("INKG: no pude crear ",name," err ",GetLastError()); return false; }
     }
   int d=DigitsFor(px);
   CustomSymbolSetInteger(name,SYMBOL_DIGITS,d);
   CustomSymbolSetDouble(name,SYMBOL_POINT,MathPow(10,-d));
   CustomSymbolSetString(name,SYMBOL_DESCRIPTION,desc);
   CustomSymbolSetString(name,SYMBOL_CURRENCY_PROFIT,"USD");
   CustomSymbolSetInteger(name,SYMBOL_CHART_MODE,SYMBOL_CHART_MODE_LAST);
   SymbolSelect(name,true);
   return true;
  }

int CandlesToRates(const string json,MqlRates &rates[])
  {
   string objs[]; int n=JObjects(json,"candles",objs);
   ArrayResize(rates,n); int k=0;
   for(int i=0;i<n;i++)
     {
      double t=JNum(objs[i],"t"),o=JNum(objs[i],"o"),h=JNum(objs[i],"h"),l=JNum(objs[i],"l"),c=JNum(objs[i],"c"),v=JNum(objs[i],"n");
      if(t==EMPTY_VALUE||o==EMPTY_VALUE||c==EMPTY_VALUE) continue;
      rates[k].time=(datetime)(long)t; rates[k].open=o; rates[k].high=h; rates[k].low=l; rates[k].close=c;
      rates[k].tick_volume=(v==EMPTY_VALUE?1:(long)v); rates[k].spread=0; rates[k].real_volume=0; k++;
     }
   ArrayResize(rates,k);
   return k;
  }

void LoadHistory(string name,string url)
  {
   int code; string js=Http("GET",url,"",code);
   if(code!=200){ Print("INKG: historial ",name," HTTP ",code); return; }
   MqlRates r[]; int n=CandlesToRates(js,r);
   if(n>0){ int u=CustomRatesUpdate(name,r); Print("INKG: ",name," historial ",n," velas (",u,")"); }
  }

void PushTick(string name,double bid,double ask,double last)
  {
   if(bid<=0||ask<=0) return;
   MqlTick t[1];
   t[0].time=TimeGMT(); t[0].time_msc=(long)t[0].time*1000;
   t[0].bid=bid; t[0].ask=ask; t[0].last=last; t[0].volume=1;
   t[0].flags=TICK_FLAG_BID|TICK_FLAG_ASK|TICK_FLAG_LAST;
   CustomTicksAdd(name,t);
  }

//--- precios en vivo ----------------------------------------------------------
void RefreshPrices()
  {
   // tokens soberanos: un solo llamado a los pools del DEX
   int code; string js=Http("GET","/api/amm/pools","",code);
   if(code==200)
     {
      string objs[]; int n=JObjects(js,"pools",objs);
      for(int i=0;i<n;i++)
        {
         string base=JStr(objs[i],"base"), quote=JStr(objs[i],"quote");
         if(quote!="WUSD") continue;
         for(int j=0;j<ArraySize(g_pools);j++) if(g_pools[j]==base)
           {
            double px=JNum(objs[i],"price"), fee=JNum(objs[i],"fee_rate"); if(fee==EMPTY_VALUE) fee=0.003;
            if(px==EMPTY_VALUE||px<=0) continue;
            EnsureSymbol(PoolName(base),"INKG\\Soberanos",base+" / WUSD · DEX soberano INKG",px);
            PushTick(PoolName(base),px*(1-fee),px/(1-fee),px);   // venta / compra reales del pool (incluye fee)
           }
        }
     }
   // futuros: último cierre del oráculo
   for(int i=0;i<ArraySize(g_perps);i++)
     {
      string jp=Http("GET","/api/trading/candles?symbol="+g_perps[i]+"&tf=60&limit=10","",code);
      if(code!=200) continue;
      MqlRates r[]; int n=CandlesToRates(jp,r); if(n<=0) continue;
      CustomRatesUpdate(PerpName(g_perps[i]),r);
      double px=r[n-1].close; PushTick(PerpName(g_perps[i]),px,px,px);
     }
  }

//--- panel de operación ---------------------------------------------------------
void Btn(string id,string text,int x,int y,int w,color bg)
  {
   if(ObjectFind(0,id)<0) ObjectCreate(0,id,OBJ_BUTTON,0,0,0);
   ObjectSetInteger(0,id,OBJPROP_CORNER,CORNER_RIGHT_UPPER);
   ObjectSetInteger(0,id,OBJPROP_XDISTANCE,x); ObjectSetInteger(0,id,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(0,id,OBJPROP_XSIZE,w);     ObjectSetInteger(0,id,OBJPROP_YSIZE,28);
   ObjectSetInteger(0,id,OBJPROP_BGCOLOR,bg);  ObjectSetInteger(0,id,OBJPROP_COLOR,clrBlack);
   ObjectSetInteger(0,id,OBJPROP_FONTSIZE,9);  ObjectSetString(0,id,OBJPROP_TEXT,text);
   ObjectSetInteger(0,id,OBJPROP_STATE,false);
  }
void BuildPanel()
  {
   string id; string k=KindOf(_Symbol,id);
   bool perp=(k=="perp");
   Btn("INKG_BUY", perp?"LONG ▲":"COMPRAR", 230,30,105,C'63,240,138');
   Btn("INKG_SELL",perp?"SHORT ▼":"VENDER",  120,30,105,C'255,91,110');
   Btn("INKG_CLOSE","CERRAR POSICIONES",     230,62,215,C'57,214,255');
   if(!perp) ObjectDelete(0,"INKG_CLOSE");
   ChartRedraw();
  }

string ShowPositions()
  {
   int code; string js=Http("GET","/api/margin/positions","",code);
   if(code==401) return "Sin sesión: revisa el token API";
   if(code!=200) return "";
   string objs[]; int n=JObjects(js,"positions",objs); string out=""; int open=0;
   for(int i=0;i<n;i++)
     {
      if(JStr(objs[i],"status")!="open") continue; open++;
      double pnl=JNum(objs[i],"unrealized_pnl");
      out+=StringFormat("#%d %s %s %dx · entrada %s · P&L %s\n",(int)JNum(objs[i],"id"),JStr(objs[i],"symbol"),JStr(objs[i],"side"),
                        (int)JNum(objs[i],"leverage"),DoubleToString(JNum(objs[i],"entry_price"),2),pnl==EMPTY_VALUE?"—":DoubleToString(pnl,2));
     }
   return open?("Posiciones abiertas:\n"+out):"Sin posiciones abiertas";
  }

void Status()
  {
   string id; string k=KindOf(_Symbol,id);
   string head="INKG Bridge · trading.futurebdetbank.com\n";
   if(k=="") head+="Abre una gráfica INKG.* (Observación de Mercado) y pon este EA ahí para operar.\n";
   else if(k=="perp") head+=StringFormat("%s · Futuros · colateral %.2f WUSD × %dx\n",id,InpCollateral,InpLeverage);
   else head+=StringFormat("%s/WUSD · DEX · compra %.2f WUSD · venta %.4f %s\n",id,InpBuyWUSD,InpSellTokens,id);
   Comment(head+(StringLen(InpToken)>0?ShowPositions():"Falta el token API"));
  }

void Trade(bool buy)
  {
   string id; string k=KindOf(_Symbol,id);
   if(k==""){ Alert("INKG: abre una gráfica INKG.* para operar."); return; }
   if(StringLen(InpToken)==0){ Alert("INKG: falta el token API (trading.futurebdetbank.com → MetaTrader)."); return; }
   string body,path,what;
   if(k=="perp")
     {
      int lev=MathMax(2,MathMin(5,InpLeverage));
      path="/api/margin/open";
      body=StringFormat("{\"symbol\":\"%s\",\"side\":\"%s\",\"leverage\":%d,\"collateral\":%.2f}",id,buy?"long":"short",lev,InpCollateral);
      what=StringFormat("Abrir %s %s · %.2f WUSD × %dx",buy?"LONG":"SHORT",id,InpCollateral,lev);
     }
   else
     {
      path="/api/amm/swap";
      if(buy){ body=StringFormat("{\"from\":\"WUSD\",\"to\":\"%s\",\"amount\":%.2f}",id,InpBuyWUSD); what=StringFormat("Comprar %s con %.2f WUSD",id,InpBuyWUSD); }
      else   { body=StringFormat("{\"from\":\"%s\",\"to\":\"WUSD\",\"amount\":%.8f}",id,InpSellTokens); what=StringFormat("Vender %.4f %s",InpSellTokens,id); }
     }
   if(MessageBox(what+"\n\nOperación REAL contra tu wallet INKG. ¿Confirmas?","INKG Bridge",MB_YESNO|MB_ICONQUESTION)!=IDYES) return;
   int code; string js=Http("POST",path,body,code);
   if(code==200 && StringFind(js,"\"ok\":true")>=0) Alert("INKG ✅ ",what," — hecho");
   else Alert("INKG ❌ ",what," — ",(JStr(js,"error")!=""?JStr(js,"error"):"HTTP "+IntegerToString(code)));
   Status();
  }

void CloseAll()
  {
   string id; if(KindOf(_Symbol,id)!="perp") return;
   if(MessageBox("Cerrar TODAS tus posiciones abiertas de "+id+" al precio actual?","INKG Bridge",MB_YESNO|MB_ICONQUESTION)!=IDYES) return;
   int code; string js=Http("GET","/api/margin/positions","",code); if(code!=200){ Alert("INKG: no pude leer posiciones (HTTP ",code,")"); return; }
   string objs[]; int n=JObjects(js,"positions",objs); int done=0;
   for(int i=0;i<n;i++)
     {
      if(JStr(objs[i],"status")!="open"||JStr(objs[i],"symbol")!=id) continue;
      int c2; Http("POST","/api/margin/close",StringFormat("{\"position_id\":%d}",(int)JNum(objs[i],"id")),c2);
      if(c2==200) done++;
     }
   Alert("INKG: ",done," posición(es) cerrada(s) en ",id);
   Status();
  }

//--- ciclo de vida -------------------------------------------------------------
int OnInit()
  {
   StringSplit(InpPerps,',',g_perps);
   StringSplit(InpPools,',',g_pools);
   for(int i=0;i<ArraySize(g_perps);i++){ StringTrimLeft(g_perps[i]); StringTrimRight(g_perps[i]); }
   for(int i=0;i<ArraySize(g_pools);i++){ StringTrimLeft(g_pools[i]); StringTrimRight(g_pools[i]); }
   // crear símbolos + historial
   for(int i=0;i<ArraySize(g_perps);i++)
     {
      if(EnsureSymbol(PerpName(g_perps[i]),"INKG\\Futuros",g_perps[i]+" · Futuro perpetuo INKG (oráculo de precio real)",100))
         LoadHistory(PerpName(g_perps[i]),"/api/trading/candles?symbol="+g_perps[i]+"&tf=60&limit=500");
     }
   RefreshPrices();
   for(int i=0;i<ArraySize(g_pools);i++)
      if(SymbolInfoInteger(PoolName(g_pools[i]),SYMBOL_CUSTOM))
         LoadHistory(PoolName(g_pools[i]),"/api/amm/candles?pair="+g_pools[i]+"/WUSD&bucket=300&limit=200");
   BuildPanel();
   Status();
   EventSetTimer(MathMax(5,InpRefreshSec));
   return(INIT_SUCCEEDED);
  }
void OnDeinit(const int reason){ EventKillTimer(); ObjectDelete(0,"INKG_BUY"); ObjectDelete(0,"INKG_SELL"); ObjectDelete(0,"INKG_CLOSE"); Comment(""); }
void OnTimer(){ RefreshPrices(); static int k=0; if(++k%4==0) Status(); }
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   if(id!=CHARTEVENT_OBJECT_CLICK) return;
   if(sparam=="INKG_BUY")  { ObjectSetInteger(0,sparam,OBJPROP_STATE,false); Trade(true); }
   if(sparam=="INKG_SELL") { ObjectSetInteger(0,sparam,OBJPROP_STATE,false); Trade(false); }
   if(sparam=="INKG_CLOSE"){ ObjectSetInteger(0,sparam,OBJPROP_STATE,false); CloseAll(); }
  }
//+------------------------------------------------------------------+
