#property copyright "Scriptong"
#property link      "http://advancetools.net"
#property description   "English: Dynamic channel, which is calculated based on the maximum distance between the price and MA.\nRussian: ������������ �����, ������������ �� ��������� ������������� ���������� ����� ����� � MA."
#property strict

#property indicator_chart_window
#property indicator_buffers 8
#property indicator_color1 clrRed
#property indicator_color2 clrDarkOrange
#property indicator_color3 clrYellow
#property indicator_color4 clrAquamarine
#property indicator_color5 clrMagenta
#property indicator_color6 clrYellow
#property indicator_color7 clrDarkOrange
#property indicator_color8 clrRed

#property indicator_width4 2
#property indicator_width5 2

#property indicator_style1 STYLE_DOT
#property indicator_style2 STYLE_DOT
#property indicator_style3 STYLE_DOT
#property indicator_style6 STYLE_DOT
#property indicator_style7 STYLE_DOT
#property indicator_style8 STYLE_DOT


input    uint                 i_extremumDepth            = 78;                                     // Depth extremum search / ������� ������ ����������
input    uint                 i_maPeriod                 = 100;                                    // MA period / ������ MA
input    ENUM_MA_METHOD       i_maMethod                 = MODE_SMA;                               // MA method / ����� ������� ��
input    ENUM_APPLIED_PRICE   i_maPrice                  = PRICE_CLOSE;                            // MA price / ���� ������� ��
input    double               i_fiboLevel1               = 23.6;                                   // Fibo level 1 / ������� ���� 1
input    double               i_fiboLevel2               = 38.2;                                   // Fibo level 2 / ������� ���� 2
input    double               i_fiboLevel3               = 50.0;                                   // Fibo level 3 / ������� ���� 3
input    double               i_fiboLevel4               = 61.8;                                   // Fibo level 4 / ������� ���� 4
input    int                  i_indBarsCount             = 10000;                                  // Number of bars to display / ���-�� ����� �����������

#define PREFIX                                        "CHAFIB_"
#define FONT_NAME                                     "Tahoma"
#define FONT_SIZE                                     8

bool g_activate;

int g_priceShiftMaxIndex;                                                                          // Index of element with maximum value in array g_priceShift

double   g_fiboLevel[4],
         g_priceShifts[],
         g_point,                                                                                  
         g_delta;                                                                                  

enum ENUM_MESSAGE_CODE
{
   MESSAGE_CODE_WRONG_DEPTH,
   MESSAGE_CODE_WRONG_MA_PERIOD,
   MESSAGE_CODE_TERMINAL_FATAL_ERROR1,
   MESSAGE_CODE_ENOUGH_MEMORY,
   MESSAGE_CODE_FIBO_EQUALS,
   MESSAGE_CODE_BIND_ERROR
};


// The indicator's buffers
double g_buffHigh1[];                                                                              
double g_buffHigh2[];                                                                              
double g_buffHigh3[];                                                                              
double g_buffHigh4[];                                                                              
double g_buffLow1[];                                                                              
double g_buffLow2[];                                                                              
double g_buffLow3[];                                                                              
double g_buffLow4[];                                                                              

//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Custom indicator initialization function                                                                                                                                                          |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
int OnInit()
{
   g_activate = false;
   
   if (!TuningParameters())                        
      return INIT_FAILED;                          
       
   if (!BuffersBind())                             
      return INIT_FAILED;                          
                                                   
   g_activate = true;
   
   return INIT_SUCCEEDED;
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Checking the correctness of values of tuning parameters                                                                                                                                           |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
bool TuningParameters()
{
   string name = WindowExpertName();

   if (i_extremumDepth < 1)
   {
      Alert(name, GetStringByMessageCode(MESSAGE_CODE_WRONG_DEPTH));
      return false;
   } 

   if (ArrayResize(g_priceShifts, i_extremumDepth) < 0)
   {
      Alert(name, GetStringByMessageCode(MESSAGE_CODE_ENOUGH_MEMORY));
      return false;
   }

   if (i_maPeriod < 1)
   {
      Alert(name, GetStringByMessageCode(MESSAGE_CODE_WRONG_MA_PERIOD));
      return false;
   } 

   g_point = Point;
   g_delta = -g_point / 10;
   if (g_point == 0)
   {
      Alert(name, GetStringByMessageCode(MESSAGE_CODE_TERMINAL_FATAL_ERROR1));
      return false;
   } 
   
   g_fiboLevel[0] = i_fiboLevel1;
   g_fiboLevel[1] = i_fiboLevel2;
   g_fiboLevel[2] = i_fiboLevel3;
   g_fiboLevel[3] = i_fiboLevel4;
   if (!IsFiboLevelsUnique())
      return false;
      
   ArraySort(g_fiboLevel);
   
   return true;
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Are unique the specified fibo levels?                                                                                                                                                             |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
bool IsFiboLevelsUnique()
{
   for (int i = 0; i < 3; i++)
      for (int j = i + 1; j < 4; j++)
         if (MathAbs(g_fiboLevel[i] - g_fiboLevel[j]) <= DBL_EPSILON)
         {
            Alert(WindowExpertName(), GetStringByMessageCode(MESSAGE_CODE_FIBO_EQUALS));
            return false;
         }
         
   return true;
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Custom indicator deinitialization function                                                                                                                                                        |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   ObjectsDeleteAll(0, PREFIX);
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Binding of array and the indicator buffers                                                                                                                                                        |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
bool BuffersBind()
{
   string name = WindowExpertName();
   
   if (!SetIndexBuffer(0, g_buffHigh1) || 
       !SetIndexBuffer(1, g_buffHigh2) || 
       !SetIndexBuffer(2, g_buffHigh3) || 
       !SetIndexBuffer(3, g_buffHigh4) || 
       !SetIndexBuffer(4, g_buffLow1)  || 
       !SetIndexBuffer(5, g_buffLow2)  ||
       !SetIndexBuffer(6, g_buffLow3)  ||
       !SetIndexBuffer(7, g_buffLow4))
   {
      Alert(name, GetStringByMessageCode(MESSAGE_CODE_BIND_ERROR), GetLastError());
      return false;
   }

   for (int i = 0; i < 8; i++)
   {
      SetIndexStyle(i, DRAW_LINE);
      if (i < 4)
         SetIndexLabel(i, "Upper " + DoubleToString(g_fiboLevel[3 - i], 2) + "%");
      else
         SetIndexLabel(i, "Lower " + DoubleToString(g_fiboLevel[i - 4], 2) + "%");
   }
   
   return true;
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Initialize of all indicator buffers                                                                                                                                                               |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
void BuffersInitializeAll()
{
   ArrayInitialize(g_buffHigh4, EMPTY_VALUE);  
   ArrayInitialize(g_buffHigh3, EMPTY_VALUE);  
   ArrayInitialize(g_buffHigh2, EMPTY_VALUE);  
   ArrayInitialize(g_buffHigh1, EMPTY_VALUE);  
   ArrayInitialize(g_buffLow1, EMPTY_VALUE);  
   ArrayInitialize(g_buffLow2, EMPTY_VALUE);  
   ArrayInitialize(g_buffLow3, EMPTY_VALUE);  
   ArrayInitialize(g_buffLow4, EMPTY_VALUE);  
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Determination of bar index which needed to recalculate                                                                                                                                            |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
int GetRecalcIndex(int &total, const int ratesTotal, const int prevCalculated)
{
   total = ratesTotal - int(i_extremumDepth + i_maPeriod);                                                                         
                                                   
   if (i_indBarsCount > 0 && i_indBarsCount < total)
      total = MathMin(i_indBarsCount, total);                      
                                                   
   if (prevCalculated < ratesTotal - 1)                     
   {       
      BuffersInitializeAll();
      return total;
   }
   
   return (MathMin(ratesTotal - prevCalculated, total));                            
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Calculate of price shift at the specified bar                                                                                                                                                     |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
double GetPriceShift(int barIndex)
{
   double ma = iMA(NULL, 0, i_maPeriod, 0, i_maMethod, i_maPrice, barIndex);
   double highShift = MathAbs(ma - iHigh(NULL, 0, barIndex));
   double lowShift = MathAbs(ma - iLow(NULL, 0, barIndex));
   
   return MathMax(highShift, lowShift);
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Primary filling of array of price shifts                                                                                                                                                          |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
void InitializePriceShifts(int limit)
{
   for (int i = int(limit + i_extremumDepth - 1); i >= limit; i--)
      g_priceShifts[i - limit] = GetPriceShift(i);
      
   g_priceShiftMaxIndex = ArrayMaximum(g_priceShifts);
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Moving up the elements in array g_priceShifts                                                                                                                                                     |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
void MovePriceShifts()
{
   for (int i = int(i_extremumDepth) - 1; i > 0; i--)
      g_priceShifts[i] = g_priceShifts[i - 1];
      
   if (g_priceShiftMaxIndex == int(i_extremumDepth) - 1)
      g_priceShiftMaxIndex = ArrayMaximum(g_priceShifts);
   else
      g_priceShiftMaxIndex++;   
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| ����������� ������� "�����"                                                                                                                                                                       |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
void ShowText(string name, datetime time, double price, string text, color clr)
{
   if (ObjectFind(0, name) < 0)
   {
      ObjectCreate(0, name, OBJ_TEXT, 0, time, price);
      ObjectSetString(0, name, OBJPROP_FONT, FONT_NAME);
      ObjectSetInteger(0, name, OBJPROP_FONTSIZE, FONT_SIZE);
      ObjectSetString(0, name, OBJPROP_TEXT, text); 
      ObjectSetString(0, name, OBJPROP_TOOLTIP, "\n");
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      return;
   }
   
   ObjectMove(0, name, 0, time, price);
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Show the descriptions for each Fibo level                                                                                                                                                         |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
void ShowDescriptions()
{
   datetime time = iTime(NULL, 0, 0) + PeriodSeconds();
   ShowText(PREFIX + "HIGH1", time, g_buffHigh1[0], DoubleToString(g_fiboLevel[3], 2) + "%", indicator_color1);
   ShowText(PREFIX + "HIGH2", time, g_buffHigh2[0], DoubleToString(g_fiboLevel[2], 2) + "%", indicator_color2);
   ShowText(PREFIX + "HIGH3", time, g_buffHigh3[0], DoubleToString(g_fiboLevel[1], 2) + "%", indicator_color3);
   ShowText(PREFIX + "HIGH4", time, g_buffHigh4[0], DoubleToString(g_fiboLevel[0], 2) + "%", indicator_color4);
   ShowText(PREFIX + "LOW1", time, g_buffLow1[0], DoubleToString(g_fiboLevel[0], 2) + "%", indicator_color5);
   ShowText(PREFIX + "LOW2", time, g_buffLow2[0], DoubleToString(g_fiboLevel[1], 2) + "%", indicator_color6);
   ShowText(PREFIX + "LOW3", time, g_buffLow3[0], DoubleToString(g_fiboLevel[2], 2) + "%", indicator_color7);
   ShowText(PREFIX + "LOW4", time, g_buffLow4[0], DoubleToString(g_fiboLevel[3], 2) + "%", indicator_color8);
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Show the channels values at the specified bar                                                                                                                                                     |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
void ShowChannel(int barIndex)
{
   double ma = iMA(NULL, 0, i_maPeriod, 0, i_maMethod, i_maPrice, barIndex);
   double shift = g_priceShifts[g_priceShiftMaxIndex];
   g_buffHigh1[barIndex] = ma + shift * g_fiboLevel[3] / 100.0;
   g_buffHigh2[barIndex] = ma + shift * g_fiboLevel[2] / 100.0;
   g_buffHigh3[barIndex] = ma + shift * g_fiboLevel[1] / 100.0;
   g_buffHigh4[barIndex] = ma + shift * g_fiboLevel[0] / 100.0;
   g_buffLow1[barIndex] = ma - shift * g_fiboLevel[0] / 100.0;
   g_buffLow2[barIndex] = ma - shift * g_fiboLevel[1] / 100.0;
   g_buffLow3[barIndex] = ma - shift * g_fiboLevel[2] / 100.0;
   g_buffLow4[barIndex] = ma - shift * g_fiboLevel[3] / 100.0;
   
   if (barIndex == 0)
      ShowDescriptions();
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Calculation of indicators values                                                                                                                                                                  |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
void CalcIndicatorData(int limit, int total)
{
   if (limit > 1)
      InitializePriceShifts(limit);
      
   for (int i = limit; i >= 0; i--)
   {
      if (g_priceShiftMaxIndex < 0 || g_priceShiftMaxIndex >= int(i_extremumDepth))
         return;
         
      double curShift = GetPriceShift(i);
      if (curShift - g_priceShifts[g_priceShiftMaxIndex] > DBL_EPSILON)
         g_priceShiftMaxIndex = 0;
      g_priceShifts[0] = curShift;
      
      ShowChannel(i);
      
      if (i > 0)
         MovePriceShifts();
   }
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Custom indicator iteration function                                                                                                                                                               |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
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[])
{
   iTime(NULL, 0, 0);
   if (GetLastError() != ERR_NO_ERROR)
      return prev_calculated;
    
   int total;   
   int limit = GetRecalcIndex(total, rates_total, prev_calculated);                                

   CalcIndicatorData(limit, total);                                                                
   
   return rates_total;
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Getting string by code of message and terminal language                                                                                                                                           |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
string GetStringByMessageCode(ENUM_MESSAGE_CODE messageCode)
{
   string language = TerminalInfoString(TERMINAL_LANGUAGE);
   if (language == "Russian")
      return GetRussianMessage(messageCode);
      
   return GetEnglishMessage(messageCode);
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Getting string by code of message for russian language                                                                                                                                            |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
string GetRussianMessage(ENUM_MESSAGE_CODE messageCode)
{
   switch (messageCode)
   {
      case MESSAGE_CODE_WRONG_DEPTH:                       return ": ������ ������ ���������� ������ ���� ����� 0. ��������� ��������.";
      case MESSAGE_CODE_WRONG_MA_PERIOD:                   return ": ������ ������� �� ������ ���� ����� 0. ��������� ��������.";
      case MESSAGE_CODE_TERMINAL_FATAL_ERROR1:             return ": ��������� ������ ��������� - ����� ����� ����. ��������� ��������.";
      case MESSAGE_CODE_ENOUGH_MEMORY:                     return ": ������ ������������� ������ ��� �������. ��������� ��������.";
      case MESSAGE_CODE_FIBO_EQUALS:                       return ": ����� ��������� ����-������� ���������� ���������� ��������. ��������� ��������.";
      case MESSAGE_CODE_BIND_ERROR:                        return ": ������ ���������� �������� � �������� ����������. ������ �";
   }
   
   return "";
}
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//| Getting string by code of message for english language                                                                                                                                            |
//+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
string GetEnglishMessage(ENUM_MESSAGE_CODE messageCode)
{
   switch (messageCode)
   {
      case MESSAGE_CODE_WRONG_DEPTH:                       return ": the depth of extremum search must be greater than 0. The indicator is turned off.";
      case MESSAGE_CODE_WRONG_MA_PERIOD:                   return ": MA calculation period must be greater than 0. The indicator is turned off.";
      case MESSAGE_CODE_TERMINAL_FATAL_ERROR1:             return ": terminal fatal error - point equals to zero. The indicator is turned off.";
      case MESSAGE_CODE_ENOUGH_MEMORY:                     return ": not enough of memory for array. The indicator is turned off.";
      case MESSAGE_CODE_FIBO_EQUALS:                       return ": two or more Fibo levels are equals. The indicator is turned off.";
      case MESSAGE_CODE_BIND_ERROR:                        return ": error of binding of the arrays and the indicator buffers. Error N";
   }

   return "";
}