//+------------------------------------------------------------------+
//| FXP_FisherTransform.mq5                                          |
//| FXPIPS ONLINE - https://fxpips.online/mt5-indicators/fisher-transform/
//|                                                                  |
//| License: MIT. Copyright (c) 2026 FXPIPS ONLINE.                  |
//| Permission is granted to use, copy, modify and distribute this   |
//| software provided this notice is kept. The software is provided  |
//| "as is", without warranty of any kind.                           |
//|                                                                  |
//| Educational tool - not financial advice. Trading involves        |
//| substantial risk of loss. Test on a demo account first.          |
//+------------------------------------------------------------------+
#property copyright "FXPIPS ONLINE"
#property link      "https://fxpips.online/mt5-indicators/fisher-transform/"
#property version   "1.00"
#property description "Ehlers Fisher Transform of the median price with a trigger line"
#property description "(the Fisher value one bar earlier). Coloured by its position"
#property description "relative to the trigger."

#property indicator_separate_window
#property indicator_buffers 4
#property indicator_plots   2

#property indicator_label1  "Fisher"
#property indicator_type1   DRAW_COLOR_LINE
#property indicator_color1  clrLimeGreen,clrTomato
#property indicator_width1  2

#property indicator_label2  "Trigger"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrSilver
#property indicator_style2  STYLE_DOT
#property indicator_width2  1

input group "Fisher Transform"
input int    InpPeriod     = 10;   // Lookback for highest/lowest median price
input double InpUpperLevel = 1.5;  // Upper reference level
input double InpLowerLevel = -1.5; // Lower reference level
input group "Alerts"
input bool   InpAlerts     = false; // Popup alert on a Fisher/trigger cross
input bool   InpPush       = false; // Push notification on a Fisher/trigger cross

double gFish[], gClr[], gTrig[], gVal[];
int      gPeriod = 10;
datetime gLastAlertBar = 0;

string TfName() { return StringSubstr(EnumToString(Period()), 7); }

void Notify(const string text, const datetime barTime)
{
   if(!InpAlerts && !InpPush) return;
   if(barTime == gLastAlertBar) return;
   gLastAlertBar = barTime;
   string msg = "FXP Fisher " + _Symbol + " " + TfName() + ": " + text;
   if(InpAlerts) Alert(msg);
   if(InpPush)   SendNotification(msg);
}

int OnInit()
{
   gPeriod = MathMax(2, InpPeriod);
   SetIndexBuffer(0, gFish, INDICATOR_DATA);
   SetIndexBuffer(1, gClr,  INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2, gTrig, INDICATOR_DATA);
   SetIndexBuffer(3, gVal,  INDICATOR_CALCULATIONS);
   ArraySetAsSeries(gFish, false); ArraySetAsSeries(gClr, false);
   ArraySetAsSeries(gTrig, false); ArraySetAsSeries(gVal, false);
   for(int p = 0; p < 2; p++)
   {
      PlotIndexSetDouble(p, PLOT_EMPTY_VALUE, EMPTY_VALUE);
      PlotIndexSetInteger(p, PLOT_DRAW_BEGIN, gPeriod * 2);
   }
   IndicatorSetInteger(INDICATOR_LEVELS, 3);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, InpUpperLevel);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, 0.0);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 2, InpLowerLevel);
   IndicatorSetInteger(INDICATOR_LEVELSTYLE, STYLE_DOT);
   IndicatorSetString(INDICATOR_SHORTNAME, StringFormat("FXP Fisher Transform (%d)", gPeriod));
   IndicatorSetInteger(INDICATOR_DIGITS, 3);
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   // No indicator handles or chart objects are created by this indicator.
}

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 < gPeriod * 2 + 2) return(0);
   ArraySetAsSeries(time, false);
   ArraySetAsSeries(high, false);
   ArraySetAsSeries(low, false);

   int start;
   if(prev_calculated <= 0 || prev_calculated > rates_total)
   {
      ArrayInitialize(gFish, 0.0);
      ArrayInitialize(gTrig, 0.0);
      ArrayInitialize(gVal, 0.0);
      ArrayInitialize(gClr, 0.0);
      start = gPeriod;
      gLastAlertBar = time[rates_total - 1];
   }
   else
      start = prev_calculated - 1;

   for(int i = start; i < rates_total && !IsStopped(); i++)
   {
      double mid = (high[i] + low[i]) / 2.0;
      double hh = mid, ll = mid;
      for(int k = 1; k < gPeriod; k++)
      {
         double m = (high[i - k] + low[i - k]) / 2.0;
         if(m > hh) hh = m;
         if(m < ll) ll = m;
      }
      double norm = (hh - ll > 0.0) ? (mid - ll) / (hh - ll) - 0.5 : 0.0;
      double v = 0.66 * norm + 0.67 * gVal[i - 1];
      if(v > 0.999)  v = 0.999;
      if(v < -0.999) v = -0.999;
      gVal[i]  = v;
      gFish[i] = 0.5 * MathLog((1.0 + v) / (1.0 - v)) + 0.5 * gFish[i - 1];
      gTrig[i] = gFish[i - 1];
      gClr[i]  = (gFish[i] >= gTrig[i]) ? 0.0 : 1.0;
   }

   if(prev_calculated > 0)
   {
      int b = rates_total - 2;
      if(gFish[b] > gTrig[b] && gFish[b - 1] <= gTrig[b - 1]) Notify("Fisher crossed above its trigger", time[rates_total - 1]);
      if(gFish[b] < gTrig[b] && gFish[b - 1] >= gTrig[b - 1]) Notify("Fisher crossed below its trigger", time[rates_total - 1]);
   }
   return(rates_total);
}
//+------------------------------------------------------------------+
