﻿//+------------------------------------------------------------------+
//|                                                ChartLauncher.mqh |
//|                                  Copyright 2026, MetaQuotes Ltd. |
//|                          https://www.mql5.com/en/users/lynnchris |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "3.0"

#ifndef _CHART_LAUNCHER_MQH
#define _CHART_LAUNCHER_MQH

#include <Indicators\Indicators.mqh>

//+------------------------------------------------------------------+
//| Returns the number of available bars for a symbol/timeframe      |
//+------------------------------------------------------------------+
int BarsAvailable(const string symbol, const ENUM_TIMEFRAMES tf)
  {
   MqlRates rates[];
//--- Copy up to 1000 bars; if the returned count is less, we have insufficient data
   return(CopyRates(symbol, tf, 0, 1000, rates));
  }

//+------------------------------------------------------------------+
//| Determines the target window: 0 = main chart, >0 = sub‑window    |
//+------------------------------------------------------------------+
int GetTargetWindow(const ENUM_INDICATOR type)
  {
//--- Overlay indicators are drawn directly on the price chart
   switch(type)
     {
      case IND_MA:
      case IND_DEMA:
      case IND_TEMA:
      case IND_AMA:
      case IND_VIDYA:
      case IND_BANDS:
      case IND_ENVELOPES:
      case IND_ICHIMOKU:
      case IND_SAR:
      case IND_ALLIGATOR:
      case IND_FRACTALS:
         return(0);   
     }
//--- Oscillators and other indicators belong in a separate sub‑window
   return((int)ChartGetInteger(0, CHART_WINDOWS_TOTAL));
  }

//+------------------------------------------------------------------+
//| Creates an indicator handle on the specified symbol              |
//+------------------------------------------------------------------+
int CreateIndicator(const ENUM_INDICATOR type, const string symbol)
  {
   MqlParam params[];
   int paramCount = 0;

//--- Build the parameter array for the requested indicator type.
//--- Each indicator has a specific number and order of parameters.
   switch(type)
     {
      //--- RSI: period (14) and applied price (close)
      case IND_RSI:
         ArrayResize(params, 2);
         params[0].type = TYPE_INT;
         params[0].integer_value = 14;
         params[1].type = TYPE_INT;
         params[1].integer_value = PRICE_CLOSE;
         paramCount = 2;
         break;

      //--- MACD: fast EMA (12), slow EMA (26), signal (9), applied price (close)
      case IND_MACD:
         ArrayResize(params, 4);
         params[0].type = TYPE_INT;
         params[0].integer_value = 12;
         params[1].type = TYPE_INT;
         params[1].integer_value = 26;
         params[2].type = TYPE_INT;
         params[2].integer_value = 9;
         params[3].type = TYPE_INT;
         params[3].integer_value = PRICE_CLOSE;
         paramCount = 4;
         break;

      //--- Stochastic: %K (5), %D (3), slowing (3), method (SMA), price (close)
      case IND_STOCHASTIC:
         ArrayResize(params, 5);
         params[0].type = TYPE_INT;
         params[0].integer_value = 5;
         params[1].type = TYPE_INT;
         params[1].integer_value = 3;
         params[2].type = TYPE_INT;
         params[2].integer_value = 3;
         params[3].type = TYPE_INT;
         params[3].integer_value = MODE_SMA;
         params[4].type = TYPE_INT;
         params[4].integer_value = PRICE_CLOSE;
         paramCount = 5;
         break;

      //--- MFI: period (14), volume (tick)
      case IND_MFI:
         ArrayResize(params, 2);
         params[0].type = TYPE_INT;
         params[0].integer_value = 14;
         params[1].type = TYPE_INT;
         params[1].integer_value = VOLUME_TICK;
         paramCount = 2;
         break;

      //--- CCI: period (14), applied price (close)
      case IND_CCI:
         ArrayResize(params, 2);
         params[0].type = TYPE_INT;
         params[0].integer_value = 14;
         params[1].type = TYPE_INT;
         params[1].integer_value = PRICE_CLOSE;
         paramCount = 2;
         break;

      //--- Momentum & TRIX: period (14), applied price (close)
      case IND_MOMENTUM:
      case IND_TRIX:
         ArrayResize(params, 2);
         params[0].type = TYPE_INT;
         params[0].integer_value = 14;
         params[1].type = TYPE_INT;
         params[1].integer_value = PRICE_CLOSE;
         paramCount = 2;
         break;

      //--- WPR & DeMarker: period (14) only
      case IND_WPR:
      case IND_DEMARKER:
         ArrayResize(params, 1);
         params[0].type = TYPE_INT;
         params[0].integer_value = 14;
         paramCount = 1;
         break;

      //--- Indicators with no parameters
      case IND_AO:
      case IND_AC:
      case IND_AD:
      case IND_OBV:
      case IND_BWMFI:
      case IND_GATOR:
      case IND_VOLUMES:
      case IND_FRACTALS:
         paramCount = 0;
         break;

      //--- Moving Average: period (10), shift (0), method (SMA), price (close)
      case IND_MA:
         ArrayResize(params, 4);
         params[0].type = TYPE_INT;
         params[0].integer_value = 10;
         params[1].type = TYPE_INT;
         params[1].integer_value = 0;
         params[2].type = TYPE_INT;
         params[2].integer_value = MODE_SMA;
         params[3].type = TYPE_INT;
         params[3].integer_value = PRICE_CLOSE;
         paramCount = 4;
         break;

      //--- DEMA & TEMA: period (14), shift (0), method (SMA), price (close)
      case IND_DEMA:
      case IND_TEMA:
         ArrayResize(params, 4);
         params[0].type = TYPE_INT;
         params[0].integer_value = 14;
         params[1].type = TYPE_INT;
         params[1].integer_value = 0;
         params[2].type = TYPE_INT;
         params[2].integer_value = MODE_SMA;
         params[3].type = TYPE_INT;
         params[3].integer_value = PRICE_CLOSE;
         paramCount = 4;
         break;

      //--- AMA: period (9), fast (30), slow (2), efficiency (30), price (close)
      case IND_AMA:
         ArrayResize(params, 5);
         params[0].type = TYPE_INT;
         params[0].integer_value = 9;
         params[1].type = TYPE_INT;
         params[1].integer_value = 30;
         params[2].type = TYPE_INT;
         params[2].integer_value = 2;
         params[3].type = TYPE_INT;
         params[3].integer_value = 30;
         params[4].type = TYPE_INT;
         params[4].integer_value = PRICE_CLOSE;
         paramCount = 5;
         break;

      //--- VIDYA: period (14), volatility (0.2), price (close)
      case IND_VIDYA:
         ArrayResize(params, 3);
         params[0].type = TYPE_INT;
         params[0].integer_value = 14;
         params[1].type = TYPE_DOUBLE;
         params[1].double_value = 0.2;
         params[2].type = TYPE_INT;
         params[2].integer_value = PRICE_CLOSE;
         paramCount = 3;
         break;

      //--- Bollinger Bands: period (20), deviation (2.0), shift (0), price (close)
      case IND_BANDS:
         ArrayResize(params, 4);
         params[0].type = TYPE_INT;
         params[0].integer_value = 20;
         params[1].type = TYPE_DOUBLE;
         params[1].double_value = 2.0;
         params[2].type = TYPE_INT;
         params[2].integer_value = 0;
         params[3].type = TYPE_INT;
         params[3].integer_value = PRICE_CLOSE;
         paramCount = 4;
         break;

      //--- Envelopes: period (10), shift (0), method (SMA), price (close), deviation (0.1)
      case IND_ENVELOPES:
         ArrayResize(params, 5);
         params[0].type = TYPE_INT;
         params[0].integer_value = 10;
         params[1].type = TYPE_INT;
         params[1].integer_value = 0;
         params[2].type = TYPE_INT;
         params[2].integer_value = MODE_SMA;
         params[3].type = TYPE_INT;
         params[3].integer_value = PRICE_CLOSE;
         params[4].type = TYPE_DOUBLE;
         params[4].double_value = 0.1;
         paramCount = 5;
         break;

      //--- Ichimoku: tenkan (9), kijun (26), senkou B (52), chikou (26), offset (26)
      case IND_ICHIMOKU:
         ArrayResize(params, 5);
         params[0].type = TYPE_INT;
         params[0].integer_value = 9;
         params[1].type = TYPE_INT;
         params[1].integer_value = 26;
         params[2].type = TYPE_INT;
         params[2].integer_value = 52;
         params[3].type = TYPE_INT;
         params[3].integer_value = 26;
         params[4].type = TYPE_INT;
         params[4].integer_value = 26;
         paramCount = 5;
         break;

      //--- SAR: step (0.02), max step (0.2)
      case IND_SAR:
         ArrayResize(params, 2);
         params[0].type = TYPE_DOUBLE;
         params[0].double_value = 0.02;
         params[1].type = TYPE_DOUBLE;
         params[1].double_value = 0.2;
         paramCount = 2;
         break;

      //--- Alligator: jaw (13,8), teeth (8,5), lips (5,3)
      case IND_ALLIGATOR:
         ArrayResize(params, 6);
         params[0].type = TYPE_INT;
         params[0].integer_value = 13;
         params[1].type = TYPE_INT;
         params[1].integer_value = 8;
         params[2].type = TYPE_INT;
         params[2].integer_value = 5;
         params[3].type = TYPE_INT;
         params[3].integer_value = 8;
         params[4].type = TYPE_INT;
         params[4].integer_value = 5;
         params[5].type = TYPE_INT;
         params[5].integer_value = 3;
         paramCount = 6;
         break;

      //--- ADX, ADXW, ATR: period (14)
      case IND_ADX:
      case IND_ADXW:
      case IND_ATR:
         ArrayResize(params, 1);
         params[0].type = TYPE_INT;
         params[0].integer_value = 14;
         paramCount = 1;
         break;

      //--- Force Index: period (13), method (SMA), volume (tick)
      case IND_FORCE:
         ArrayResize(params, 3);
         params[0].type = TYPE_INT;
         params[0].integer_value = 13;
         params[1].type = TYPE_INT;
         params[1].integer_value = MODE_SMA;
         params[2].type = TYPE_INT;
         params[2].integer_value = VOLUME_TICK;
         paramCount = 3;
         break;

      //--- RVI: period (10), method (SMA)
      case IND_RVI:
         ArrayResize(params, 2);
         params[0].type = TYPE_INT;
         params[0].integer_value = 10;
         params[1].type = TYPE_INT;
         params[1].integer_value = MODE_SMA;
         paramCount = 2;
         break;

      //--- OSMA: fast (12), slow (26), signal (9), price (close)
      case IND_OSMA:
         ArrayResize(params, 4);
         params[0].type = TYPE_INT;
         params[0].integer_value = 12;
         params[1].type = TYPE_INT;
         params[1].integer_value = 26;
         params[2].type = TYPE_INT;
         params[2].integer_value = 9;
         params[3].type = TYPE_INT;
         params[3].integer_value = PRICE_CLOSE;
         paramCount = 4;
         break;

      //--- Chaikin: fast (3), slow (10), method (SMA), volume (0.0)
      case IND_CHAIKIN:
         ArrayResize(params, 4);
         params[0].type = TYPE_INT;
         params[0].integer_value = 3;
         params[1].type = TYPE_INT;
         params[1].integer_value = 10;
         params[2].type = TYPE_INT;
         params[2].integer_value = MODE_SMA;
         params[3].type = TYPE_DOUBLE;
         params[3].double_value = 0.0;
         paramCount = 4;
         break;

      //--- Standard Deviation: period (10), shift (0), price (close)
      case IND_STDDEV:
         ArrayResize(params, 3);
         params[0].type = TYPE_INT;
         params[0].integer_value = 10;
         params[1].type = TYPE_INT;
         params[1].integer_value = 0;
         params[2].type = TYPE_INT;
         params[2].integer_value = PRICE_CLOSE;
         paramCount = 3;
         break;

      //--- Bears & Bulls Power: period (13), applied price (close)
      case IND_BEARS:
      case IND_BULLS:
         ArrayResize(params, 2);
         params[0].type = TYPE_INT;
         params[0].integer_value = 13;
         params[1].type = TYPE_INT;
         params[1].integer_value = PRICE_CLOSE;
         paramCount = 2;
         break;

      default:
         Print("Unsupported indicator type: ", EnumToString(type));
         return(INVALID_HANDLE);
     }

//--- Create the indicator using the specified symbol and parameters
   int handle = IndicatorCreate(symbol, PERIOD_CURRENT, type, paramCount, params);
   if(handle == INVALID_HANDLE)
      Print("Failed to create ", EnumToString(type), " on ", symbol, ", error: ", GetLastError());
   return(handle);
  }

//+------------------------------------------------------------------+
//| Finds an existing chart for a symbol, or opens a new one         |
//+------------------------------------------------------------------+
long FindOrOpenChart(const string symbol)
  {
//--- Loop through all open charts
   for(long ch = ChartFirst(); ch != -1; ch = ChartNext(ch))
     {
      if(ChartSymbol(ch) == symbol)
         return(ch);   
     }
//--- No chart found – open a new one
   return(ChartOpen(symbol, PERIOD_CURRENT));
  }

//+------------------------------------------------------------------+
//| Attaches an indicator with default parameters                    |
//+------------------------------------------------------------------+
bool AttachIndicator(const ENUM_INDICATOR type, const string symbol = NULL)
  {
   string targetSymbol = (symbol == NULL) ? Symbol() : symbol;

//--- Ensure the symbol is available in Market Watch
   if(!SymbolSelect(targetSymbol, true))
      return(false);

//--- Check that we have enough historical data (at least 10 bars)
   int bars = BarsAvailable(targetSymbol, PERIOD_CURRENT);
   if(bars < 10)
      return(false);

//--- Find or open a chart for the target symbol
   long chartId = FindOrOpenChart(targetSymbol);
   if(chartId == -1)
      return(false);

//--- Create the indicator handle
   int handle = CreateIndicator(type, targetSymbol);
   if(handle == INVALID_HANDLE)
      return(false);

//--- Determine the correct window (main chart or sub‑window)
   int window = GetTargetWindow(type);

//--- Attach to the target chart; if it fails, fall back to the main chart (0)
   if(!ChartIndicatorAdd(chartId, window, handle))
     {
      IndicatorRelease(handle);
      return(false);
     }

   ChartRedraw(chartId);
   Print("Indicator ", EnumToString(type), " attached to ", targetSymbol);
   return(true);
  }

//+------------------------------------------------------------------+
//| Attaches an indicator with custom parameters                     |
//+------------------------------------------------------------------+
bool AttachIndicator(const ENUM_INDICATOR type, const string symbol, const MqlParam &params[])
  {
   string targetSymbol = (symbol == NULL) ? Symbol() : symbol;

   if(!SymbolSelect(targetSymbol, true))
      return(false);

   int bars = BarsAvailable(targetSymbol, PERIOD_CURRENT);
   if(bars < 10)
      return(false);

   long chartId = FindOrOpenChart(targetSymbol);
   if(chartId == -1)
      return(false);

//--- Use the provided parameter array instead of building defaults
   int paramCount = ArraySize(params);
   int handle = IndicatorCreate(targetSymbol, PERIOD_CURRENT, type, paramCount, params);
   if(handle == INVALID_HANDLE)
      return(false);

   int window = GetTargetWindow(type);
   if(!ChartIndicatorAdd(chartId, window, handle))
     {
      IndicatorRelease(handle);
      return(false);
     }

   ChartRedraw(chartId);
   Print("Indicator ", EnumToString(type), " attached to ", targetSymbol, " with custom parameters");
   return(true);
  }

#endif // _CHART_LAUNCHER_MQH
//+------------------------------------------------------------------+
