//+------------------------------------------------------------------+
//| BLACK BOX JOURNEY - READ-ONLY MT5 JOURNAL EXPORTER                |
//| Version 1.00 beta. NO order sends, changes, WebRequest or login. |
//| Exports already executed trade DEALS to local MQL5/Files CSV.    |
//+------------------------------------------------------------------+
#property strict
#property version "1.00"
#property description "Read-only local deal history exporter for Black Box Journey beta journal."

input int    InpRefreshSeconds = 60;                 // Export every N seconds (minimum 30)
input int    InpHistoryDays = 30;                     // Complete history window (1-365)
input int    InpServerUtcOffsetMinutes = 0;           // Server clock minus UTC; VERIFY manually
input string InpFileName = "BBJ_MT5_Journal.csv";     // File saved inside MQL5/Files

// MT5 account history uses broker-server time. A chosen fixed offset can be
// wrong across daylight saving changes; re-export affected periods separately.
string IsoUtcFromServerTime(const datetime broker_time)
{
   datetime utc_time = broker_time - (datetime)(InpServerUtcOffsetMinutes * 60);
   MqlDateTime parts;
   TimeToStruct(utc_time, parts);
   return StringFormat("%04d-%02d-%02dT%02d:%02d:%02dZ",
      parts.year, parts.mon, parts.day, parts.hour, parts.min, parts.sec);
}

string EntryName(const long entry)
{
   if(entry == DEAL_ENTRY_IN) return "in";
   if(entry == DEAL_ENTRY_OUT) return "out";
   if(entry == DEAL_ENTRY_INOUT) return "inout";
   if(entry == DEAL_ENTRY_OUT_BY) return "out_by";
   return "";
}

bool IsEligibleDeal(const ulong ticket)
{
   if(ticket == 0) return false;
   ENUM_DEAL_TYPE kind = (ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE);
   if(kind != DEAL_TYPE_BUY && kind != DEAL_TYPE_SELL) return false;
   if((long)HistoryDealGetInteger(ticket, DEAL_POSITION_ID) <= 0) return false;
   if(EntryName(HistoryDealGetInteger(ticket, DEAL_ENTRY)) == "") return false;
   if(StringLen(HistoryDealGetString(ticket, DEAL_SYMBOL)) < 1) return false;
   return true;
}

void ExportTradeHistory()
{
   datetime server_end = TimeCurrent() + (datetime)86400;
   datetime server_from = server_end - (datetime)(InpHistoryDays * 86400);
   ResetLastError();
   if(!HistorySelect(server_from, server_end))
   {
      Print("BBJ exporter: HistorySelect failed, error ", GetLastError());
      return;
   }

   int total = HistoryDealsTotal();
   int eligible = 0;
   for(int i = 0; i < total; ++i)
   {
      ulong ticket = HistoryDealGetTicket(i);
      if(IsEligibleDeal(ticket)) ++eligible;
   }
   if(eligible < 1)
   {
      Print("BBJ exporter: no trade deals in selected window. Nothing exported.");
      return;
   }
   // Refuse partial export. An incomplete position history must not be shown
   // as a valid full performance report, even when a chart can reconcile it.
   if(eligible > 500)
   {
      PrintFormat("BBJ exporter: %d trade deals exceed 500. Shorten HistoryDays. No CSV overwritten.", eligible);
      return;
   }

   ResetLastError();
   int out = FileOpen(InpFileName, FILE_WRITE | FILE_CSV | FILE_ANSI | FILE_SHARE_READ, ',');
   if(out == INVALID_HANDLE)
   {
      Print("BBJ exporter: cannot write CSV. Error ", GetLastError());
      return;
   }
   FileWrite(out, "ticket", "position_id", "symbol", "time", "side", "entry",
                  "volume", "price", "profit", "commission", "swap", "fee");
   int written = 0;
   for(int i = 0; i < total; ++i)
   {
      ulong ticket = HistoryDealGetTicket(i);
      if(!IsEligibleDeal(ticket)) continue;

      string entry = EntryName(HistoryDealGetInteger(ticket, DEAL_ENTRY));
      ENUM_DEAL_TYPE kind = (ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE);
      string side = (kind == DEAL_TYPE_BUY ? "buy" : "sell");
      ulong position = (ulong)HistoryDealGetInteger(ticket, DEAL_POSITION_ID);
      string symbol = HistoryDealGetString(ticket, DEAL_SYMBOL);
      datetime stamp = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
      double volume = HistoryDealGetDouble(ticket, DEAL_VOLUME);
      double price = HistoryDealGetDouble(ticket, DEAL_PRICE);
      double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);
      double commission = HistoryDealGetDouble(ticket, DEAL_COMMISSION);
      double swap = HistoryDealGetDouble(ticket, DEAL_SWAP);
      double fee = HistoryDealGetDouble(ticket, DEAL_FEE);

      FileWrite(out,
         IntegerToString((long)ticket),
         IntegerToString((long)position),
         symbol,
         IsoUtcFromServerTime(stamp),
         side, entry,
         DoubleToString(volume, 8),
         DoubleToString(price, 8),
         DoubleToString(profit, 8),
         DoubleToString(commission, 8),
         DoubleToString(swap, 8),
         DoubleToString(fee, 8));
      ++written;
   }
   FileFlush(out);
   FileClose(out);
   PrintFormat("BBJ exporter: wrote %d read-only trade deals to MQL5/Files/%s. Server UTC offset input=%d minutes.",
               written, InpFileName, InpServerUtcOffsetMinutes);
}

int OnInit()
{
   if(InpHistoryDays < 1 || InpHistoryDays > 365)
   {
      Print("BBJ exporter: HistoryDays must be 1 to 365.");
      return INIT_PARAMETERS_INCORRECT;
   }
   if(InpServerUtcOffsetMinutes < -840 || InpServerUtcOffsetMinutes > 840)
   {
      Print("BBJ exporter: server UTC offset is outside +/-14 hours.");
      return INIT_PARAMETERS_INCORRECT;
   }
   if(InpFileName != "BBJ_MT5_Journal.csv")
   {
      Print("BBJ exporter: only the standard output filename is supported in this beta.");
      return INIT_PARAMETERS_INCORRECT;
   }
   EventSetTimer((int)MathMax(30, InpRefreshSeconds));
   ExportTradeHistory();
   return INIT_SUCCEEDED;
}

void OnTimer()
{
   ExportTradeHistory();
}

void OnDeinit(const int reason)
{
   EventKillTimer();
}
//+------------------------------------------------------------------+
