//+------------------------------------------------------------------+
//|                                              DeltaFusionLite.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
// https://www.mql5.com/en/code/66864
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/francescosecchi"
#property version   "1.0"
#property description "DeltaFusion Lite - Cumulative Delta & Net Delta"
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_plots   3
#property indicator_level1  0.0
#define EPSILON 1e-12
#define DEFAULT_MIN_TICK 0.00001
#define DEFAULT_TF_SECONDS 60
#define COLOR_CUM_ASK clrGreen
#define COLOR_CUM_BID clrRed
#define COLOR_NET_DELTA clrBlack
double CumAsk[];
double CumBid[];
double NetDelta[];
input string ResetSession = "=== Session Reset ==="; // Section header label used to group session reset inputs
input int    ResetHourCustom = 1; // Hour of daily session reset (0-23)
input int    ResetMinuteCustom = 0; // Minute of daily session reset (0-59)
input string CumulativeDelta = "=== Cumulative Delta ==="; // Section header label used to group cumulative delta inputs
input int    SmoothPeriod = 10; // EMA smoothing period for delta (>=1)
datetime g_lastSession = 0;
bool g_firstRun = true;
double Eps() { return EPSILON; }
double SafeDiv(double numerator, double denominator)
{
   if(MathAbs(denominator) <= Eps())
   {
      return (numerator > Eps()) ? 1e9 : 0.0;
   }
   double result = numerator / denominator;
   if(!MathIsValidNumber(result)) return 0.0;
   return result;
}
double SafeNormalize(double price)
{
   return NormalizeDouble(price, _Digits);
}
int GetTFSeconds()
{
   int sec = PeriodSeconds();
   return (sec <= 0) ? DEFAULT_TF_SECONDS : sec;
}
double GetMinTick()
{
   double tick = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   return (tick <= 0.0) ? DEFAULT_MIN_TICK : tick;
}
datetime GetSessionStart()
{
   int mm = (ResetMinuteCustom < 0) ? 0 : ((ResetMinuteCustom > 59) ? 59 : ResetMinuteCustom);
   datetime now = TimeCurrent();
   MqlDateTime md;
   TimeToStruct(now, md);
   md.hour = ResetHourCustom;
   md.min = mm;
   md.sec = 0;
   datetime sessionToday = StructToTime(md);
   if(now < sessionToday)
   {
      sessionToday -= 24 * 60 * 60;
   }
   return sessionToday;
}
bool IsNewSession()
{
   datetime currentSession = GetSessionStart();
   if(currentSession != g_lastSession)
   {
      g_lastSession = currentSession;
      return true;
   }
   return false;
}
void SmoothEMA(const double &src[], int count, int period, double &emaOut[])
{
   if(ArraySize(emaOut) != count)
   {
      ArrayResize(emaOut, count);
      ArraySetAsSeries(emaOut, true);
   }
   if(count <= 0) return;
   if(period <= 1)
   {
      ArrayCopy(emaOut, src, 0, 0, WHOLE_ARRAY);
      return;
   }
   double alpha = 2.0 / (period + 1.0);
   double prev = src[count - 1];
   emaOut[count - 1] = prev;
   for(int i = count - 2; i >= 0; i--)
   {
      prev = alpha * src[i] + (1.0 - alpha) * prev;
      emaOut[i] = prev;
   }
}
void GetTickDeltaAskBid(int i,
                        const double &open[],
                        const double &high[],
                        const double &low[],
                        const double &close[],
                        const long &tick_volume[],
                        double &deltaAsk,
                        double &deltaBid)
{
   deltaAsk = 0.0;
   deltaBid = 0.0;
   if(i < 0) return;
   double o = open[i];
   double c = close[i];
   double h = high[i];
   double l = low[i];
   long tv = tick_volume[i];
   double vol = (tv > 0) ? (double)tv : 1.0;
   double spread = h - l;
   if(spread <= 0.0) return;
   double upper = MathMax(h - MathMax(o, c), 0.0);
   double lower = MathMax(MathMin(o, c) - l, 0.0);
   double body = spread - (upper + lower);
   double pu = upper / spread;
   double pl = lower / spread;
   double pb = body / spread;
   if(c > o)
   {
      deltaAsk = (pb + (pu + pl) / 2.0) * vol;
      deltaBid = ((pu + pl) / 2.0) * vol;
   }
   else if(c < o)
   {
      deltaBid = (pb + (pu + pl) / 2.0) * vol;
      deltaAsk = ((pu + pl) / 2.0) * vol;
   }
   else
   {
      deltaAsk = deltaBid = ((pu + pl) / 2.0) * vol;
   }
}
int OnInit()
{
   SetIndexBuffer(0, CumAsk, INDICATOR_DATA);
   SetIndexBuffer(1, CumBid, INDICATOR_DATA);
   SetIndexBuffer(2, NetDelta, INDICATOR_DATA);
   PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_HISTOGRAM);
   PlotIndexSetInteger(0, PLOT_LINE_STYLE, STYLE_SOLID);
   PlotIndexSetInteger(0, PLOT_LINE_WIDTH, 2);
   PlotIndexSetInteger(0, PLOT_LINE_COLOR, COLOR_CUM_ASK);
   PlotIndexSetString(0, PLOT_LABEL, "Ask Volume");
   PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_HISTOGRAM);
   PlotIndexSetInteger(1, PLOT_LINE_STYLE, STYLE_SOLID);
   PlotIndexSetInteger(1, PLOT_LINE_WIDTH, 2);
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, COLOR_CUM_BID);
   PlotIndexSetString(1, PLOT_LABEL, "Bid Volume");
   PlotIndexSetInteger(2, PLOT_DRAW_TYPE, DRAW_LINE);
   PlotIndexSetInteger(2, PLOT_LINE_STYLE, STYLE_SOLID);
   PlotIndexSetInteger(2, PLOT_LINE_WIDTH, 1);
   PlotIndexSetInteger(2, PLOT_LINE_COLOR, COLOR_NET_DELTA);
   PlotIndexSetString(2, PLOT_LABEL, "Net Delta");
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   ArraySetAsSeries(CumAsk, true);
   ArraySetAsSeries(CumBid, true);
   ArraySetAsSeries(NetDelta, true);
   IndicatorSetString(INDICATOR_SHORTNAME, "DeltaFusion Lite v1.0");
   IndicatorSetInteger(INDICATOR_LEVELS, 1);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, 0.0);
   IndicatorSetInteger(INDICATOR_LEVELSTYLE, 0, STYLE_DASH);
   IndicatorSetInteger(INDICATOR_LEVELCOLOR, 0, clrGray);
   IndicatorSetInteger(INDICATOR_LEVELWIDTH, 0, 1);
   Print("DeltaFusion Lite initialized successfully");
   return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
   string reason_text;
   switch(reason)
   {
      case REASON_PROGRAM:     reason_text = "Program terminated"; break;
      case REASON_REMOVE:      reason_text = "Indicator removed"; break;
      case REASON_RECOMPILE:   reason_text = "Recompiled"; break;
      case REASON_CHARTCHANGE: reason_text = "Chart changed"; break;
      case REASON_CHARTCLOSE:  reason_text = "Chart closed"; break;
      case REASON_PARAMETERS:  reason_text = "Parameters changed"; break;
      case REASON_ACCOUNT:     reason_text = "Account changed"; break;
      default: reason_text = "Unknown reason"; break;
   }
   Print("Indicator deinitialized: ", reason_text);
}
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   if(rates_total <= 0) return 0;
   ArraySetAsSeries(time, true);
   ArraySetAsSeries(open, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(tick_volume, true);
   int start;
   if(prev_calculated == 0 || g_firstRun)
   {
      start = rates_total - 1;
      g_firstRun = false;
   }
   else
   {
      start = rates_total - prev_calculated;
      if(start > 0) start--;
   }
   if(start < 0) start = 0;
   if(start >= rates_total) start = rates_total - 1;
   static double dA[];
   static double dB[];
   if(ArraySize(dA) != rates_total)
   {
      ArrayResize(dA, rates_total);
      ArraySetAsSeries(dA, true);
   }
   if(ArraySize(dB) != rates_total)
   {
      ArrayResize(dB, rates_total);
      ArraySetAsSeries(dB, true);
   }
   for(int i = start; i >= 0; i--)
   {
      GetTickDeltaAskBid(i, open, high, low, close, tick_volume, dA[i], dB[i]);
   }
   static double emaA[];
   static double emaB[];
   SmoothEMA(dA, rates_total, SmoothPeriod, emaA);
   SmoothEMA(dB, rates_total, SmoothPeriod, emaB);
   for(int i2 = start; i2 >= 0; i2--)
   {
      CumAsk[i2] = (emaA[i2] > 0.0) ? emaA[i2] : EMPTY_VALUE;
      CumBid[i2] = (emaB[i2] > 0.0) ? -emaB[i2] : EMPTY_VALUE;
      NetDelta[i2] = emaA[i2] - emaB[i2];
   }
   datetime sessionStart = GetSessionStart();
   double sumAsk = 0.0, sumBid = 0.0;
   int firstIdx = -1, lastIdx = -1;
   for(int i3 = 0; i3 < rates_total; i3++)
   {
      if(time[i3] < sessionStart) break;
      if(firstIdx < 0) firstIdx = i3;
      lastIdx = i3;
      if(CumAsk[i3] != EMPTY_VALUE) sumAsk += MathAbs(CumAsk[i3]);
      if(CumBid[i3] != EMPTY_VALUE) sumBid += MathAbs(CumBid[i3]);
   }
   return rates_total;
}
