NVLT/NVLTD (по мотивам NATR)
Добавлено: 12 июн 2019, 17:15
				
				В индикаторе NATR пороги рассчитываются по волатильности ряда за период. Это здраво, т.к. пороги статистически обоснованы.
Однако, в среднюю волатильность по периоду на равных правах входят:
1) гэпы (утренние чаще всего) и просто отдельные большие скачки. На самом деле до и после гэпов/скачков волатильность обычно более-менее одинаковая, отдельные же броски необоснованно "раскачивают" среднюю волатильность и, соответственно, пороги.
2) движения вверх и движения вниз. После нескольких больших среднего монотонных изменений ряда (например, несколько раз вверх), т.е. после фронта, средняя волатильность возрастает, и начало обратного движения отлавливается плохо.
NVLT - попытка починить эти проблемы.
1) Волатильность рассчитывается не по среднему, а по ~40% медианных значений (т.е. значения за период сортируются по возрастанию, а затем берется среднее по ~40% значений в середине). Т.о. в вычислении среднего не участвуют экстремально большие/малые значения за период, т.е. гэпы/скачки и "штиль" не учитываются, а учитывается "стационарная" часть ряда, на фоне которой как раз и хочется выделять эти самые скачки.
2) Отдельно вычисляется волатильность движений вверх и вниз. За счет этого разворот после фронтов определяется четче, т.к. "противоположная" волатильность на монотонном фронте не меняется (просто нет новых точек для счета), а остается такой же, как и до фронта.
Период индикатора на выбранном тайм-фрейме следует брать примерно равным длительности "волночек ряби" на графике ряда после скачков; одной-двух волн обычно вполне достаточно, слишком большие периоды захватывают "давно прошедшие" времена, затемняя текущие события.
Индикатор сделан для "точечных" входных рядов (не для свечек); модифицировать его в "свечной" вариант не представляет труда.
NVLTD - логическое продолжение NVLT. В NATR и NVLT значение зазора до порога регулируется просто постоянным параметром N (зазор=N*волатильность). Однако, свойства ряда могут меняться со временем, константа N на достаточно большом промежутке может стать неоптимальной. Для реакции на реалии ряда в NVLTD порог вычисляется как ("средняя медианная" волатильность)+N*(СКО тех самых 40% точек из ряда). Для нормального распределения (нормальность распределения цен я, конечно, не проверял, оставляю это энтузиастам ) N=~2 покрывает ~95% текущего распределения, т.е. зазор становится адаптивным.
 ) N=~2 покрывает ~95% текущего распределения, т.е. зазор становится адаптивным.
Для полноты счастья добавлены
- серия Direction (+1/-1), которая указывает направление тренда, для использования в качестве сигнала в стратегиях и
- серии VLTup/VLTdn - результаты расчета порогов волатильности при движениях ряда вверх и вниз, которые можно использовать, например, для счета как-то статистически обоснованных процентов проскоков/trailing'ов.
NVLT
NVLTD
AS IS WITHOUT WARRANTY
--
Upd: название серии "Direction" заменено на "zDirection", т.к. в противном случае индикатор перестает нормально отрисовываться для графиков с PriceStudy=false. (Другие опробованные названия типа Dir, Direction_ тоже с такой же придурью. Бред...)
			Однако, в среднюю волатильность по периоду на равных правах входят:
1) гэпы (утренние чаще всего) и просто отдельные большие скачки. На самом деле до и после гэпов/скачков волатильность обычно более-менее одинаковая, отдельные же броски необоснованно "раскачивают" среднюю волатильность и, соответственно, пороги.
2) движения вверх и движения вниз. После нескольких больших среднего монотонных изменений ряда (например, несколько раз вверх), т.е. после фронта, средняя волатильность возрастает, и начало обратного движения отлавливается плохо.
NVLT - попытка починить эти проблемы.
1) Волатильность рассчитывается не по среднему, а по ~40% медианных значений (т.е. значения за период сортируются по возрастанию, а затем берется среднее по ~40% значений в середине). Т.о. в вычислении среднего не участвуют экстремально большие/малые значения за период, т.е. гэпы/скачки и "штиль" не учитываются, а учитывается "стационарная" часть ряда, на фоне которой как раз и хочется выделять эти самые скачки.
2) Отдельно вычисляется волатильность движений вверх и вниз. За счет этого разворот после фронтов определяется четче, т.к. "противоположная" волатильность на монотонном фронте не меняется (просто нет новых точек для счета), а остается такой же, как и до фронта.
Период индикатора на выбранном тайм-фрейме следует брать примерно равным длительности "волночек ряби" на графике ряда после скачков; одной-двух волн обычно вполне достаточно, слишком большие периоды захватывают "давно прошедшие" времена, затемняя текущие события.
Индикатор сделан для "точечных" входных рядов (не для свечек); модифицировать его в "свечной" вариант не представляет труда.
NVLTD - логическое продолжение NVLT. В NATR и NVLT значение зазора до порога регулируется просто постоянным параметром N (зазор=N*волатильность). Однако, свойства ряда могут меняться со временем, константа N на достаточно большом промежутке может стать неоптимальной. Для реакции на реалии ряда в NVLTD порог вычисляется как ("средняя медианная" волатильность)+N*(СКО тех самых 40% точек из ряда). Для нормального распределения (нормальность распределения цен я, конечно, не проверял, оставляю это энтузиастам
 ) N=~2 покрывает ~95% текущего распределения, т.е. зазор становится адаптивным.
 ) N=~2 покрывает ~95% текущего распределения, т.е. зазор становится адаптивным.Для полноты счастья добавлены
- серия Direction (+1/-1), которая указывает направление тренда, для использования в качестве сигнала в стратегиях и
- серии VLTup/VLTdn - результаты расчета порогов волатильности при движениях ряда вверх и вниз, которые можно использовать, например, для счета как-то статистически обоснованных процентов проскоков/trailing'ов.
NVLT
Код: Выделить всё
function Initialize()
{
 // для работы с "точечными" индикаторами (не свечками)
 IndicatorName = "pNVLT";
 PriceStudy = true;                                            // в области цен
 
 AddInput("Input", Inputs.Price);                              // точки          
 AddParameter("Period", 30, "Период расчета", 1);              // период расчета VLT              
 AddParameter("N", 4.0, "Порог волатильности");                // зазор в амплитудах текущей волатильности
 AddSeries("pNVLT", DrawAs.Line, Color.Blue);                  // выходная серия
 AddSeries("VLTup", DrawAs.Custom, Color.Green, false);        // уровень волатильности в + (невидимая серия)
 AddSeries("VLTdn", DrawAs.Custom, Color.Red, false);          // уровень волатильности в - (невидимая серия) 
 AddSeries("zDirection", DrawAs.Custom, Color.Black, false);     // собственно сигнал (невидимая серия)
        
 AddGlobalVariable("gHigh", Types.Double, 0.0);
 AddGlobalVariable("gLow", Types.Double, 1e+9);
 
 AddGlobalVariable("bufU", Types.DoubleList);
 AddGlobalVariable("bufD", Types.DoubleList);
}
function Evaluate()
{
 //------------------------------------------------------------------------------------------------------
 // среднее по ~40% центральных значений
 Func<List<double>, double, int, double> AddValAndGetMedian = (List<double> L, double V, int MaxCnt) =>
 {
  L.Add(V);
  if(L.Count>MaxCnt) L.RemoveAt(0);
  List<double> tmp=new List<double>(L);
  tmp.Sort();
 
  int n=tmp.Count;                          
  int n1=Math.Max(0, n/2-n/5);          // [n1..n2] - 40% центральных значений
  int n2=Math.Min(n-1, n/2+n/5);
  double m=0.0;
  for(int i=n1; i<=n2; i++) m+=tmp[i];
  m/=(n2-n1+1);
  return m;
 };
 //------------------------------------------------------------------------------------------------------
 double gHigh_=gHigh;                   // минимизация обращений к глобальным переменным,
 double gLow_=gLow;                     // которые (обращения) идут через вызов методов
  
 double I0=Input[0];
 double I1=(CurrentIndex==0 ? I0 : Input[-1]);
 double InputLo, InputHi;               // имитация свечки
 if(I0<I1) { InputLo=I0; InputHi=I1; }
 else      { InputLo=I1; InputHi=I0; }
 
 double Result;
 double vU, vD;                         // уровень волатильности в + и в -
 int gDirection;
 if(CurrentIndex==0)
 {                                      // затравка
  bufU.Add(0.0);
  bufD.Add(0.0);
  vU=vD=0.0;
  gDirection=1;
  Result=I0;
 }
 else
 {                                      // очередной бар
  double v=I0-I1;
  if(v>0) { vU=(double)N*AddValAndGetMedian(bufU,  v, (int)Period); vD=VLTdn[-1]; } else
  if(v<0) { vD=(double)N*AddValAndGetMedian(bufD, -v, (int)Period); vU=VLTup[-1]; }
  else
  {
   vU=(double)N*AddValAndGetMedian(bufU, 0.0, (int)Period);
   vD=(double)N*AddValAndGetMedian(bufD, 0.0, (int)Period);
  }
  
  gDirection=(int)zDirection[-1];
  if(gDirection>0)
  { // был рост
   if(InputLo<gHigh_-vD)
   { // переворот
    gDirection=-1;
    gLow_=InputLo;
    Result=gLow_+vU;
   }
   else
   { // продолжение
    // Result=Math.Max(pNVLT[-1], gHigh_-vD); // для уровня нет хода назад
    Result=gHigh_-vD;                         // ход назад возможен
   } 
  }
  else 
  { // было снижение
   if(InputHi>gLow_+vU)
   { // переворот
    gDirection=1;
    gHigh_=InputHi;
    Result=gHigh_-vD;
   }
   else
   { // продолжение                                
    // Result=Math.Min(pNVLT[-1], gLow_+vU);
    Result=gLow_+vU;
   } 
  }
 }
 if(InputHi>gHigh_) gHigh_=InputHi;
 if(InputLo<gLow_)  gLow_ =InputLo;
 pNVLT[0]=Result;
 VLTup[0]=vU;
 VLTdn[0]=vD;
 zDirection[0]=gDirection;
 gHigh=gHigh_;
 gLow=gLow_;
}NVLTD
Код: Выделить всё
function Initialize()
{
 // для работы с "точечными" данными (не свечками)
 IndicatorName = "pNVLTD";
 PriceStudy = true;                                            // в области цен
 
 AddInput("Input", Inputs.Price);                              // точки          
 AddParameter("Period", 30, "Период расчета", 1);              // период расчета VLT              
 AddParameter("N", 4.0, "Порог волатильности");                // добавка к зазору в амплитудах СКО текущей волатильности
 AddSeries("pNVLTD", DrawAs.Line, Color.Blue);                 // выходная серия
 AddSeries("VLTup", DrawAs.Custom, Color.Green, false);        // уровень волатильности в + (невидимая серия)
 AddSeries("VLTdn", DrawAs.Custom, Color.Red, false);          // уровень волатильности в - (невидимая серия) 
 AddSeries("zDirection", DrawAs.Custom, Color.Black, false);          // собственно сигнал (невидимая серия)
        
 AddGlobalVariable("gHigh", Types.Double, 0.0);
 AddGlobalVariable("gLow", Types.Double, 1e+9);
 
 AddGlobalVariable("bufU", Types.DoubleList);
 AddGlobalVariable("bufD", Types.DoubleList);
}
function Evaluate()
{
 //------------------------------------------------------------------------------------------------------
 // среднее и СКО по ~40% центральных значений
 Func< List<double>, double, int, Tuple<double, double> > AddValAndGetMedian1 = (List<double> L, double V, int MaxCnt) =>
 {
  L.Add(V);
  if(L.Count>MaxCnt) L.RemoveAt(0);
  List<double> tmp=new List<double>(L);
  tmp.Sort();
 
  int n=tmp.Count;                          
  int n1=Math.Max(0, n/2-n/5);              // [n1..n2] - 40% центральных значений
  int n2=Math.Min(n-1, n/2+n/5);
  
  double m=0.0;
  for(int i=n1; i<=n2; i++) m+=tmp[i];
  m/=(n2-n1+1);
  
  double d=0.0;
  for(int i=n1; i<=n2; i++) { double v=m-tmp[i]; d+=v*v; }
  d=Math.Sqrt(d/(n2-n1));
  Tuple<double, double> result=new Tuple<double, double>(m, d);
  return result;
 };
 //------------------------------------------------------------------------------------------------------
 double gHigh_=gHigh;                   // минимизация обращений к глобальным переменным,
 double gLow_=gLow;                     // которые (обращения) идут через вызов методов
 
 double I0=Input[0];
 double I1=(CurrentIndex==0 ? I0 : Input[-1]);
 double InputLo, InputHi;               // имитация свечки
 if(I0<I1) { InputLo=I0; InputHi=I1; }
 else      { InputLo=I1; InputHi=I0; }
 
 double Result;
 double vU, vD;                         // уровень волатильности в + и в -
 int gDirection;
 if(CurrentIndex==0)
 {                                      // затравка
  bufU.Add(0.0);
  bufD.Add(0.0);
  vU=vD=0.0;
  gDirection=1;
  Result=I0;
 }
 else
 {                                      // очередной бар
  Tuple<double, double> md; 
  double v=I0-I1;
  if(v>0)
  {
   md=AddValAndGetMedian1(bufU,  v, (int)Period);
   vU=md.Item1+md.Item2*(double)N;      // среднее+N*СКО
   vD=VLTdn[-1];
  } else
  if(v<0)
  {
   md=AddValAndGetMedian1(bufD, -v, (int)Period);
   vD=md.Item1+md.Item2*(double)N;
   vU=VLTup[-1];
  } 
  else
  {
   md=AddValAndGetMedian1(bufU, 0.0, (int)Period);
   vU=md.Item1+md.Item2*(double)N;
   
   md=AddValAndGetMedian1(bufD, 0.0, (int)Period);
   vD=md.Item1+md.Item2*(double)N;
  }
  gDirection=(int)zDirection[-1];
  if(gDirection>0)
  { // был рост
   if(InputLo<gHigh_-vD)
   { // переворот
    gDirection=-1;
    gLow_=InputLo;
    Result=gLow_+vU;
   }
   else
   { // продолжение
    // Result=Math.Max(pNVLTD[-1], gHigh_-vD); // для уровня нет хода назад
    Result=gHigh_-vD;                          // ход назад возможен
   }
  }
  else 
  { // было снижение
   if(InputHi>gLow_+vU)
   { // переворот
    gDirection=1;
    gHigh_=InputHi;
    Result=gHigh_-vD;
   }
   else
   { // продолжение
    // Result=Math.Min(pNVLTD[-1], gLow_+vU);
    Result=gLow_+vU;
   } 
  }
 }
 if(InputHi>gHigh) gHigh_=InputHi;
 if(InputLo<gLow)  gLow_ =InputLo;
 pNVLTD[0]=Result;
 VLTup[0]=vU;
 VLTdn[0]=vD;
 zDirection[0]=gDirection;
 gHigh=gHigh_;
 gLow=gLow_;
}
AS IS WITHOUT WARRANTY

--
Upd: название серии "Direction" заменено на "zDirection", т.к. в противном случае индикатор перестает нормально отрисовываться для графиков с PriceStudy=false. (Другие опробованные названия типа Dir, Direction_ тоже с такой же придурью. Бред...)
