//+------------------------------------------------------------------+
//| JournalTrader.mq4  (version MetaTrader 4)                        |
//| Envoie tes trades FERMES vers ton Journal d'un trader.           |
//| Ce programme ne passe AUCUN ordre : il lit seulement l'historique|
//| et envoie, pour chaque trade ferme : instrument, sens, lots,     |
//| date, heure, gain brut, frais et resultat net.                   |
//| Il n'envoie ni mot de passe ni solde.                            |
//+------------------------------------------------------------------+
#property copyright "journalduntrader.com"
#property link      "https://journalduntrader.com"
#property version   "1.00"
#property strict
#property description "Envoie tes trades fermes vers journalduntrader.com (aucun ordre n'est passe)."

input string JournalCode = "";     // Ton code personnel (cree sur le site, page « Connexion MetaTrader »)
input int    SyncSeconds = 60;     // Verification toutes les X secondes
input int    DaysBack    = 30;     // Au premier envoi : combien de jours d'historique envoyer

const string API_URL = "https://wjkishuodorhzkowvvwq.supabase.co/rest/v1/rpc/v2_mt_ingest";
const string API_KEY = "sb_publishable_iEK1VNkX1s_cvgxMwG3PRA_nnJKRXyQ";

int      g_lastCount = -1;
datetime g_lastOk = 0;

//+------------------------------------------------------------------+
string StatusKey() { return "JT4_last_" + IntegerToString(AccountNumber()); }

void SetStatus(const string s) { Comment("JournalTrader (MT4)\n", s); }

//+------------------------------------------------------------------+
int OnInit()
{
   if(StringLen(JournalCode) < 10)
   {
      SetStatus("Ecris ton code personnel dans les parametres (onglet Entrees).");
      Alert("JournalTrader : ecris ton code personnel dans les parametres du programme (onglet Entrees).");
      return INIT_PARAMETERS_INCORRECT;
   }
   EventSetTimer(MathMax(15, SyncSeconds));
   SetStatus("Demarrage...");
   OnTimer();
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   EventKillTimer();
   Comment("");
}

void OnTick() {}

void OnTimer()
{
   // on envoie quand l'historique change (nouveau trade ferme), sinon on reverifie toutes les 10 minutes
   int cnt = OrdersHistoryTotal();
   if(cnt == g_lastCount && TimeCurrent() - g_lastOk < 600) return;
   if(Sync()) g_lastCount = cnt;
}

//+------------------------------------------------------------------+
string DateOf(const datetime t)
{
   string s = TimeToString(t, TIME_DATE);       // 2026.10.27
   StringReplace(s, ".", "-");
   return s;
}

//+------------------------------------------------------------------+
bool Post(const string body, string &answer, int &code)
{
   char data[], res[];
   string resHeaders;
   StringToCharArray(body, data, 0, StringLen(body), CP_UTF8);
   string headers = "Content-Type: application/json\r\napikey: " + API_KEY + "\r\nAuthorization: Bearer " + API_KEY + "\r\n";
   ResetLastError();
   code = WebRequest("POST", API_URL, headers, 15000, data, res, resHeaders);
   if(code == -1)
   {
      int err = GetLastError();
      answer = "WebRequest impossible (erreur " + IntegerToString(err) + "). Ajoute https://wjkishuodorhzkowvvwq.supabase.co dans Outils > Options > Expert Advisors > Autoriser WebRequest.";
      return false;
   }
   answer = CharArrayToString(res, 0, ArraySize(res), CP_UTF8);
   return (code >= 200 && code < 300);
}

//+------------------------------------------------------------------+
bool Sync()
{
   if(!IsConnected()) { SetStatus("En attente de connexion du terminal..."); return false; }
   datetime now = TimeCurrent();
   datetime last = 0;
   if(GlobalVariableCheck(StatusKey())) last = (datetime)(long)GlobalVariableGet(StatusKey());
   datetime from = (last > 0) ? last - 2 * 86400 : now - (datetime)DaysBack * 86400;

   string items[];
   datetime newest = last;
   int total = OrdersHistoryTotal();
   for(int i = 0; i < total; i++)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) continue;
      int type = OrderType();
      if(type != OP_BUY && type != OP_SELL) continue;       // on ignore depots, retraits et ordres en attente annules
      datetime tc = OrderCloseTime();
      if(tc <= 0 || tc < from) continue;
      string symbol = OrderSymbol();
      StringReplace(symbol, "\"", "");
      double gross = OrderProfit(), cost = OrderCommission() + OrderSwap();
      string js = "{\"login\":" + IntegerToString(AccountNumber()) +
                  ",\"pos\":" + IntegerToString(OrderTicket()) +
                  ",\"symbol\":\"" + symbol + "\"" +
                  ",\"side\":\"" + (type == OP_BUY ? "buy" : "sell") + "\"" +
                  ",\"lots\":" + DoubleToString(OrderLots(), 3) +
                  ",\"date\":\"" + DateOf(tc) + "\"" +
                  ",\"time\":\"" + StringSubstr(TimeToString(OrderOpenTime(), TIME_MINUTES), 0, 5) + "\"" +
                  ",\"gross\":" + DoubleToString(gross, 2) +
                  ",\"cost\":" + DoubleToString(cost, 2) +
                  ",\"net\":" + DoubleToString(gross + cost, 2) + "}";
      int sz = ArraySize(items); ArrayResize(items, sz + 1); items[sz] = js;
      if(tc > newest) newest = tc;
   }
   if(ArraySize(items) == 0)
   {
      g_lastOk = TimeCurrent();
      SetStatus("Connecte. Rien de nouveau a envoyer.\nDerniere verification : " + TimeToString(TimeLocal(), TIME_DATE | TIME_MINUTES));
      return true;
   }
   int sent = 0;
   for(int b = 0; b < ArraySize(items); b += 100)
   {
      string body = "{\"p_token\":\"" + JournalCode + "\",\"p_trades\":[";
      for(int x = b; x < MathMin(b + 100, ArraySize(items)); x++) body += (x > b ? "," : "") + items[x];
      body += "]}";
      string ans; int code;
      if(!Post(body, ans, code))
      {
         SetStatus("Envoi echoue (" + IntegerToString(code) + ") : " + ans);
         Print("JournalTrader : ", ans);
         return false;
      }
      sent += MathMin(100, ArraySize(items) - b);
   }
   GlobalVariableSet(StatusKey(), (double)(long)newest);
   g_lastOk = TimeCurrent();
   SetStatus("Connecte. " + IntegerToString(sent) + " trade(s) envoye(s).\nDerniere verification : " + TimeToString(TimeLocal(), TIME_DATE | TIME_MINUTES));
   return true;
}
//+------------------------------------------------------------------+
