//+------------------------------------------------------------------+
//| TransactionCostCollector.mq5                                     |
//|                                                                  |
//| Collects transaction cost data for a given symbol: spread        |
//| distribution, swap rates, and commission diagnostics.            |
//|                                                                  |
//| Run as a Script (not EA) on any chart of the target symbol.      |
//| Output: <terminal_data_path>/MQL5/Files/<symbol>_costs.csv       |
//|                                                                  |
//| The CSV file is structured in sections that can be parsed by     |
//| Python for further analysis.                                     |
//+------------------------------------------------------------------+
#property script_show_inputs

//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input int    InpBars        = 50000;  // Number of bars of spread history to sample
input string InpOutputFile  = "";     // Override output filename (blank = <symbol>_costs.csv)

//+------------------------------------------------------------------+
//| Internal constants                                               |
//+------------------------------------------------------------------+
#define CSV_SEP ","                   // CSV field separator

//+------------------------------------------------------------------+
//| Forward declarations of helper functions                         |
//+------------------------------------------------------------------+
void WriteCsvRow(int    handle,
                 string section,
                 string key,
                 string value,
                 string unit,
                 string note);

void CollectSymbolProperties(int    fh,
                             string symbol,
                             int    digits,
                             double point,
                             double pip_factor,
                             double tick_size,
                             double tick_value,
                             double contract_sz,
                             double min_lot,
                             string cur_base,
                             string cur_profit,
                             string cur_margin);

void CollectSwapInfo(int    fh,
                     string symbol,
                     double swap_long,
                     double swap_short,
                     int    swap_mode,
                     int    swap_3day,
                     string swap_mode_str);

void CollectCommissionInfo(int fh, double commission_blocked);

void CollectSpreadDistribution(int    fh,
                               string symbol,
                               int    digits,
                               double point,
                               double pip_factor,
                               const int &spread_arr[],
                               int    n,
                               int    bars_sampled);

void CollectSessionSpread(int    fh,
                          string symbol,
                          int    digits,
                          double point,
                          double pip_factor,
                          const int &spread_arr[],
                          int    n);

//+------------------------------------------------------------------+
//| Script entry point                                               |
//+------------------------------------------------------------------+
void OnStart()
  {
   string symbol = Symbol();
   int    digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);

   //--- Core symbol properties
   double point       = SymbolInfoDouble(symbol, SYMBOL_POINT);
   double tick_size   = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
   double tick_value  = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
   double contract_sz = SymbolInfoDouble(symbol, SYMBOL_TRADE_CONTRACT_SIZE);
   double min_lot     = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
   string cur_base    = SymbolInfoString(symbol, SYMBOL_CURRENCY_BASE);
   string cur_profit  = SymbolInfoString(symbol, SYMBOL_CURRENCY_PROFIT);
   string cur_margin  = SymbolInfoString(symbol, SYMBOL_CURRENCY_MARGIN);

   //--- Pip factor: 10 points per pip for 5‑digit / 3‑digit symbols
   double pip_factor = (digits == 3 || digits == 5) ? 10.0 : 1.0;

   //--- Swap rates
   double swap_long  = SymbolInfoDouble(symbol, SYMBOL_SWAP_LONG);
   double swap_short = SymbolInfoDouble(symbol, SYMBOL_SWAP_SHORT);
   int    swap_mode  = (int)SymbolInfoInteger(symbol, SYMBOL_SWAP_MODE);
   int    swap_3day  = (int)SymbolInfoInteger(symbol, SYMBOL_SWAP_ROLLOVER3DAYS);

   string swap_mode_str = "";
   switch(swap_mode)
     {
      case SYMBOL_SWAP_MODE_POINTS:           swap_mode_str = "points";        break;
      case SYMBOL_SWAP_MODE_CURRENCY_SYMBOL:  swap_mode_str = "currency";      break;
      case SYMBOL_SWAP_MODE_INTEREST_OPEN:    swap_mode_str = "interest_open"; break;
      case SYMBOL_SWAP_MODE_INTEREST_CURRENT: swap_mode_str = "interest_curr"; break;
      case SYMBOL_SWAP_MODE_CURRENCY_MARGIN:  swap_mode_str = "currency_mrgn"; break;
      case SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT: swap_mode_str = "currency_dep";  break;
      default:                                swap_mode_str = "unknown";        break;
     }

   //--- Commission diagnostic (not directly the per‑lot rate)
   double commission_blocked = AccountInfoDouble(ACCOUNT_COMMISSION_BLOCKED);

   //--- Prepare output file
   string fname = InpOutputFile != "" ? InpOutputFile
                                      : symbol + "_costs.csv";
   int fh = FileOpen(fname, FILE_WRITE | FILE_CSV | FILE_ANSI, ',');
   if(fh == INVALID_HANDLE)
     {
      Print("ERROR: Cannot open output file: ", fname);
      return;
     }

   //--- Write header
   FileWrite(fh, "section" + CSV_SEP + "key" + CSV_SEP + "value" +
                 CSV_SEP + "unit" + CSV_SEP + "note");

   //--- Collect static data sections
   CollectSymbolProperties(fh, symbol, digits, point, pip_factor,
                           tick_size, tick_value, contract_sz, min_lot,
                           cur_base, cur_profit, cur_margin);
   CollectSwapInfo(fh, symbol, swap_long, swap_short, swap_mode,
                   swap_3day, swap_mode_str);
   CollectCommissionInfo(fh, commission_blocked);

   //--- Historical spread distribution
   int spread_arr[];
   int bars_copied = CopySpread(symbol, PERIOD_CURRENT, 0, InpBars, spread_arr);
   if(bars_copied <= 0)
     {
      Print("ERROR: CopySpread failed. Bars available: ", bars_copied);
      FileClose(fh);
      return;
     }

   // Compute distribution statistics and write them
   CollectSpreadDistribution(fh, symbol, digits, point, pip_factor,
                             spread_arr, bars_copied, bars_copied);
   CollectSessionSpread(fh, symbol, digits, point, pip_factor,
                        spread_arr, bars_copied);

   FileClose(fh);

   //--- Console summary (to be updated with key stats later if desired)
   Print("TransactionCostCollector: wrote ", fname);
   Print("  Bars sampled : ", bars_copied);
  }

//+------------------------------------------------------------------+
//| Write a single CSV row in the standard 5‑column format           |
//+------------------------------------------------------------------+
void WriteCsvRow(int    handle,
                 string section,
                 string key,
                 string value,
                 string unit,
                 string note)
  {
   FileWrite(handle,
             section + CSV_SEP +
             key     + CSV_SEP +
             value   + CSV_SEP +
             unit    + CSV_SEP +
             note);
  }

//+------------------------------------------------------------------+
//| Write symbol properties                                          |
//+------------------------------------------------------------------+
void CollectSymbolProperties(int    fh,
                             string symbol,
                             int    digits,
                             double point,
                             double pip_factor,
                             double tick_size,
                             double tick_value,
                             double contract_sz,
                             double min_lot,
                             string cur_base,
                             string cur_profit,
                             string cur_margin)
  {
   string sec = "symbol_properties";
   WriteCsvRow(fh, sec, "symbol",        symbol,                          "—",     "");
   WriteCsvRow(fh, sec, "digits",        (string)digits,                  "—",     "");
   WriteCsvRow(fh, sec, "point",         DoubleToString(point, 10),       "price", "");
   WriteCsvRow(fh, sec, "pip_factor",    DoubleToString(pip_factor, 1),   "points_per_pip",
               "10 for 5‑digit; 1 for 4‑digit");
   WriteCsvRow(fh, sec, "tick_size",     DoubleToString(tick_size, 10),   "price", "");
   WriteCsvRow(fh, sec, "tick_value",    DoubleToString(tick_value, 6),   "account_currency_per_lot", "");
   WriteCsvRow(fh, sec, "contract_size", DoubleToString(contract_sz, 2),  "units", "");
   WriteCsvRow(fh, sec, "min_lot",       DoubleToString(min_lot, 2),      "lots",  "");
   WriteCsvRow(fh, sec, "currency_base",   cur_base,                      "—",     "");
   WriteCsvRow(fh, sec, "currency_profit", cur_profit,                    "—",     "");
   WriteCsvRow(fh, sec, "currency_margin", cur_margin,                    "—",     "");
  }

//+------------------------------------------------------------------+
//| Write swap rate information                                      |
//+------------------------------------------------------------------+
void CollectSwapInfo(int    fh,
                     string symbol,
                     double swap_long,
                     double swap_short,
                     int    swap_mode,
                     int    swap_3day,
                     string swap_mode_str)
  {
   string sec = "swap";
   WriteCsvRow(fh, sec, "swap_long",  DoubleToString(swap_long, 6),
               swap_mode_str, "per night");
   WriteCsvRow(fh, sec, "swap_short", DoubleToString(swap_short, 6),
               swap_mode_str, "per night");
   WriteCsvRow(fh, sec, "swap_mode",  swap_mode_str, "—",
               "see SYMBOL_SWAP_MODE enum");
   WriteCsvRow(fh, sec, "swap_3day",  (string)swap_3day, "weekday",
               "0=Sun … 6=Sat; triple swap charged on this weekday");
  }

//+------------------------------------------------------------------+
//| Write commission diagnostic (not the direct per‑lot rate)        |
//+------------------------------------------------------------------+
void CollectCommissionInfo(int fh, double commission_blocked)
  {
   string sec = "commission";
   WriteCsvRow(fh, sec, "commission_blocked_now",
               DoubleToString(commission_blocked, 4), "account_currency",
               "ACCOUNT_COMMISSION_BLOCKED; see derivation note");
   WriteCsvRow(fh, sec, "derivation_note",
               "Open a reference trade of 1.0 lot on this symbol; "
               "read ACCOUNT_COMMISSION_BLOCKED; divide by 1.0 to get "
               "per‑lot rate", "—", "");
  }

//+------------------------------------------------------------------+
//| Compute and write spread distribution summary statistics         |
//+------------------------------------------------------------------+
void CollectSpreadDistribution(int       fh,
                                string   symbol,
                                int      digits,
                                double   point,
                                double   pip_factor,
                                const int &spread_arr[],
                                int      n,
                                int      bars_sampled)
  {
   // Compute basic moments
   double sum   = 0.0;
   double sum_sq = 0.0;
   int    min_sp = INT_MAX, max_sp = 0;
   for(int i = 0; i < n; i++)
     {
      int s = spread_arr[i];
      sum    += s;
      sum_sq += (double)s * s;
      if(s < min_sp) min_sp = s;
      if(s > max_sp) max_sp = s;
     }

   double mean_sp = sum / n;
   double var_sp  = (sum_sq / n) - (mean_sp * mean_sp);
   double std_sp  = MathSqrt(MathMax(var_sp, 0.0));

   // Sort a copy to get percentiles
   int sorted[];
   ArrayCopy(sorted, spread_arr, 0, 0, n);
   ArraySort(sorted);

   double p25 = sorted[(int)(n * 0.25)];
   double p50 = sorted[(int)(n * 0.50)];
   double p75 = sorted[(int)(n * 0.75)];
   double p90 = sorted[(int)(n * 0.90)];
   double p95 = sorted[(int)(n * 0.95)];
   double p99 = sorted[(int)(n * 0.99)];

   // Conversion helpers: pips = (points * point) / (pip_factor * point) = points / pip_factor
   double mean_pips = mean_sp / pip_factor;
   double std_pips  = std_sp  / pip_factor;
   double p50_pips  = p50     / pip_factor;
   double p95_pips  = p95     / pip_factor;
   double p99_pips  = p99     / pip_factor;

   string sec = "spread_summary";
   WriteCsvRow(fh, sec, "bars_sampled", (string)bars_sampled, "bars", "");
   WriteCsvRow(fh, sec, "mean_points",  DoubleToString(mean_sp, 4), "points", "");
   WriteCsvRow(fh, sec, "std_points",   DoubleToString(std_sp, 4),  "points", "");
   WriteCsvRow(fh, sec, "p25_points",   DoubleToString(p25, 2),     "points", "");
   WriteCsvRow(fh, sec, "p50_points",   DoubleToString(p50, 2),     "points", "");
   WriteCsvRow(fh, sec, "p75_points",   DoubleToString(p75, 2),     "points", "");
   WriteCsvRow(fh, sec, "p90_points",   DoubleToString(p90, 2),     "points", "");
   WriteCsvRow(fh, sec, "p95_points",   DoubleToString(p95, 2),     "points", "");
   WriteCsvRow(fh, sec, "p99_points",   DoubleToString(p99, 2),     "points", "");
   WriteCsvRow(fh, sec, "mean_pips",    DoubleToString(mean_pips, 4), "pips", "");
   WriteCsvRow(fh, sec, "p50_pips",     DoubleToString(p50_pips, 4),  "pips", "");
   WriteCsvRow(fh, sec, "p95_pips",     DoubleToString(p95_pips, 4),  "pips", "");
   WriteCsvRow(fh, sec, "p99_pips",     DoubleToString(p99_pips, 4),  "pips", "");
  }

//+------------------------------------------------------------------+
//| Write mean spread by hour of day (broker time)                   |
//+------------------------------------------------------------------+
void CollectSessionSpread(int       fh,
                          string   symbol,
                          int      digits,
                          double   point,
                          double   pip_factor,
                          const int &spread_arr[],
                          int      n)
  {
   datetime times[];
   int time_copied = CopyTime(symbol, PERIOD_CURRENT, 0, n, times);
   if(time_copied != n)
     {
      Print("WARNING: CopyTime returned ", time_copied, " bars, expected ", n,
            ". Session spread may be incomplete.");
     }

   double hour_sum[24];
   int    hour_cnt[24];
   ArrayInitialize(hour_sum, 0);
   ArrayInitialize(hour_cnt, 0);

   int limit = MathMin(n, time_copied);
   for(int i = 0; i < limit; i++)
     {
      MqlDateTime dt;
      TimeToStruct(times[i], dt);
      int h = dt.hour;
      hour_sum[h] += spread_arr[i];
      hour_cnt[h]++;
     }

   string sec = "spread_by_hour";
   for(int h = 0; h < 24; h++)
     {
      if(hour_cnt[h] > 0)
        {
         double hmean = hour_sum[h] / hour_cnt[h];
         double hmean_pips = hmean / pip_factor;   // points -> pips
         string hour_str = StringFormat("hour_%02d", h);
         WriteCsvRow(fh, sec, hour_str,
                     DoubleToString(hmean_pips, 4), "pips",
                     "broker_time; n=" + (string)hour_cnt[h]);
        }
     }
  }