//+------------------------------------------------------------------+
//|                                                     RSTRade EA   |
//|                          RSTRade_Journal - Trade Journal Webhook |
//|                                      (c) RS Trade - MQL5 Expert  |
//+------------------------------------------------------------------+
#property strict
#property copyright   "RS Trade"
#property link        "https://yourrs.example.com"
#property version     "1.0"
#property description "Journals trades to RS backend via HTTP WebRequest()"

input string  WebhookURL    = "https://yourrs.example.com/api/webhook/trades"; // Base webhook URL
input string  WebhookToken  = "";                                             // Token appended as ?token=...
input int     RequestTimeout = 5000;                                           // Timeout ms

// Helper: format datetime as yyyy-mm-dd HH:MM:SS
string FormatDateTime(datetime t)
{
   if(t == 0) return "";
   MqlDateTime st;
   TimeToStruct(t, st);
   return StringFormat("%04d-%02d-%02d %02d:%02d:%02d", st.year, st.mon, st.day, st.hour, st.min, st.sec);
}

// Helper: safely add query token
string BuildUrlWithToken(const string baseUrl, const string token)
{
   if(StringLen(token) == 0) return baseUrl;
   int qpos = StringFind(baseUrl, "?");
   if(qpos < 0)
      return baseUrl + "?token=" + token;
   return baseUrl + "&token=" + token;
}

// Helper: convert long to string (portable across builds)
string ToStringLong(const long v)
{
   return StringFormat("%I64d", v);
}

// HTTP POST JSON
bool PostJSON(const string url, const string json, const int timeoutMs)
{
   char data[];
   StringToCharArray(json, data, 0, WHOLE_ARRAY, CP_UTF8);
   char result[];
   string headers = "Content-Type: application/json\r\n";
   string result_headers = "";
   ResetLastError();
   int res = WebRequest("POST", url, headers, timeoutMs, data, result, result_headers);
   if(res == -1)
   {
      PrintFormat("[RSTRade_Journal] WebRequest failed. Error=%d. Ensure URL is allowed in Tools→Options→Expert Advisors.", GetLastError());
      return false;
   }
   string response = CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8);
   if(res >= 200 && res < 300)
   {
      PrintFormat("[RSTRade_Journal] POST success: %s", response);
      return true;
   }
   PrintFormat("[RSTRade_Journal] POST HTTP %d: %s", res, response);
   return false;
}

int OnInit()
{
   // Reminder to allow WebRequest
   Print("[RSTRade_Journal] Add domain to Tools→Options→Expert Advisors→Allow WebRequest for listed URL: ", WebhookURL);
   if(StringLen(WebhookURL) == 0)
   {
      Print("[RSTRade_Journal] WebhookURL is empty. Please set an endpoint.");
      return(INIT_PARAMETERS_INCORRECT);
   }
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
}

void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest      &request,
                        const MqlTradeResult       &result)
{
   // Gather account and symbol context
   long accountLogin = AccountInfoInteger(ACCOUNT_LOGIN);
   string broker     = AccountInfoString(ACCOUNT_COMPANY);
   string symbol     = trans.symbol;

   // Determine side and direction from transaction
   string side = "";
   if(trans.type == TRADE_TRANSACTION_DEAL_ADD || trans.type == TRADE_TRANSACTION_DEAL_UPDATE)
   {
      if(trans.deal_type == DEAL_TYPE_BUY)  side = "buy";
      if(trans.deal_type == DEAL_TYPE_SELL) side = "sell";
   }

   string direction = "";
   // Heuristic: if a new deal is opened -> entry; if order closed or position decreased -> exit
   if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
      direction = "entry";
   else if(trans.type == TRADE_TRANSACTION_ORDER_DELETE || trans.type == TRADE_TRANSACTION_DEAL_UPDATE)
      direction = "exit";

   double volume = trans.volume;
   double price  = trans.price;
   // Some builds don't provide profit in transaction; default to 0
   double profit = 0.0;

   // Basic timing and prices from transaction (portable across builds)
   double open_price = 0.0, close_price = 0.0;
   datetime open_time = 0, close_time = 0, event_time = TimeCurrent();
   // Use trans.price as execution price; we don't reliably have separate open/close here
   close_price = trans.price;

   // Build JSON (manual to avoid external libs)
   string json = "{"
      + "\"broker\":\"" + StringReplace(broker, "\"", "\\\"") + "\"," 
      + "\"account_id\":" + IntegerToString((int)accountLogin) + ","
      + "\"symbol\":\"" + StringReplace(symbol, "\"", "\\\"") + "\"," 
      + "\"deal_ticket\":" + ToStringLong(trans.deal) + ","
      + "\"order_ticket\":" + ToStringLong(trans.order) + ","
      + "\"side\":\"" + side + "\"," 
      + "\"direction\":\"" + direction + "\"," 
      + "\"volume\":" + DoubleToString(volume, 2) + ","
      + "\"price\":" + DoubleToString(price, _Digits) + ","
      + "\"profit\":" + DoubleToString(profit, 2) + ","
      + "\"open_price\":" + DoubleToString(open_price, _Digits) + ","
      + "\"close_price\":" + DoubleToString(close_price, _Digits) + ","
      + "\"open_time\":\"" + FormatDateTime(open_time) + "\"," 
      + "\"close_time\":\"" + FormatDateTime(close_time) + "\"," 
      + "\"event_time\":\"" + FormatDateTime(event_time) + "\"," 
      + "\"raw\":{"
          + "\"type\":" + IntegerToString((int)trans.type) + ","
          + "\"reason\":" + IntegerToString(0) + ","
          + "\"order\":" + ToStringLong(trans.order) + ","
          + "\"deal\":" + ToStringLong(trans.deal)
      + "}"
   + "}";

   string url = BuildUrlWithToken(WebhookURL, WebhookToken);
   PostJSON(url, json, RequestTimeout);
}


