Пользовательские индикаторы > NVLT/NVLTD (по мотивам NATR)

Дополнительные индикаторы от пользователей Альфа-Директ 4. Готовые решения от пользователей.
BugsDigger
Сообщения: 533
Зарегистрирован: 11 ноя 2018, 17:11
Благодарил (а): 21 раз
Поблагодарили: 91 раз

NVLT/NVLTD (по мотивам NATR)

Непрочитанное сообщение BugsDigger » 12 июн 2019, 17:15

В индикаторе NATR пороги рассчитываются по волатильности ряда за период. Это здраво, т.к. пороги статистически обоснованы.

Однако, в среднюю волатильность по периоду на равных правах входят:
1) гэпы (утренние чаще всего) и просто отдельные большие скачки. На самом деле до и после гэпов/скачков волатильность обычно более-менее одинаковая, отдельные же броски необоснованно "раскачивают" среднюю волатильность и, соответственно, пороги.
2) движения вверх и движения вниз. После нескольких больших среднего монотонных изменений ряда (например, несколько раз вверх), т.е. после фронта, средняя волатильность возрастает, и начало обратного движения отлавливается плохо.

NVLT - попытка починить эти проблемы.
1) Волатильность рассчитывается не по среднему, а по ~40% медианных значений (т.е. значения за период сортируются по возрастанию, а затем берется среднее по ~40% значений в середине). Т.о. в вычислении среднего не участвуют экстремально большие/малые значения за период, т.е. гэпы/скачки и "штиль" не учитываются, а учитывается "стационарная" часть ряда, на фоне которой как раз и хочется выделять эти самые скачки.
2) Отдельно вычисляется волатильность движений вверх и вниз. За счет этого разворот после фронтов определяется четче, т.к. "противоположная" волатильность на монотонном фронте не меняется (просто нет новых точек для счета), а остается такой же, как и до фронта.

Период индикатора на выбранном тайм-фрейме следует брать примерно равным длительности "волночек ряби" на графике ряда после скачков; одной-двух волн обычно вполне достаточно, слишком большие периоды захватывают "давно прошедшие" времена, затемняя текущие события.

Индикатор сделан для "точечных" входных рядов (не для свечек); модифицировать его в "свечной" вариант не представляет труда.

NVLTD - логическое продолжение NVLT. В NATR и NVLT значение зазора до порога регулируется просто постоянным параметром N (зазор=N*волатильность). Однако, свойства ряда могут меняться со временем, константа N на достаточно большом промежутке может стать неоптимальной. Для реакции на реалии ряда в NVLTD порог вычисляется как ("средняя медианная" волатильность)+N*(СКО тех самых 40% точек из ряда). Для нормального распределения (нормальность распределения цен я, конечно, не проверял, оставляю это энтузиастам :D ) 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 :mrgreen:

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

BugsDigger
Сообщения: 533
Зарегистрирован: 11 ноя 2018, 17:11
Благодарил (а): 21 раз
Поблагодарили: 91 раз

Re: NVLT/NVLTD (по мотивам NATR)

Непрочитанное сообщение BugsDigger » 23 июн 2019, 18:52

"Свечной" вариант.

Формула счета порогов по волатильности баров сделана универсальной:
VLT=Nv*VLTm + Nd*VLTd,
где VLTm - средняя волатильность за период, VLTd - среднеквадратичное отклонение, Nv и Nd - их веса в конечном значении (можно и 0 задать при желании).
Знак изменения на очередном баре отсчитывается либо от цены закрытия предыдущего бара, либо от средней цены предыдущего бара (задается параметром UseAvgCandle= 0 или 1 соответственно).

Процентные значения VLTup_pc и VLTdn_pc рассчитываются относительно средней цены бара.
Серия VLTdiff добавлена "для красоты"; даже не знаю, несет ли она какой-то смысл. :)

Код: Выделить всё

function Initialize()
{
 IndicatorName = "cNVLTD";
 PriceStudy = true;                                            // в области цен
 
 AddInput("Input", Inputs.Candle);
 AddParameter("Period", 30, "Период расчета", 1);              // период расчета VLT             
 AddParameter("UseAvgCandle", 0, "Считать по средней цене свечи");               
 AddParameter("Nv", 1.0, "Порог волатильности");               // зазор в амплитудах текущей волатильности
 AddParameter("Nd", 4.0, "Добавка по СКО");                    // зазор в амплитудах текущей дисперсии волатильности

 AddSeries("cNVLTD", DrawAs.Custom, Color.Blue);               // выходная серия
 AddSeries("VLTup", DrawAs.Custom, Color.Green, false);        // уровень волатильности в + (невидимая серия)
 AddSeries("VLTdn", DrawAs.Custom, Color.Red, false);          // уровень волатильности в - (невидимая серия)
 AddSeries("VLTup_pc", DrawAs.Custom, Color.Green, AxisType.ZeroBased, true, Axes.New);  // уровень волатильности в +, %
 AddSeries("VLTdn_pc", DrawAs.Custom, Color.Red, AxisType.ZeroBased, true, Axes.New);    // уровень волатильности в -, %
 AddSeries("VLTdiff", DrawAs.Line, Color.Black, AxisType.ZeroBased, true, Axes.New);     // разность % уровеней волатильности в + и -
 AddSeries("zDirection", DrawAs.Custom, Color.Black, false);   // собственно сигнал (невидимая серия)
       
 AddGlobalVariable("gHigh", Types.Double, 0.0);
 AddGlobalVariable("gLow", Types.Double, 1e+8);
 
 AddGlobalVariable("bufU", Types.DoubleList);
 AddGlobalVariable("bufD", Types.DoubleList);
}

function Evaluate()
{
 //------------------------------------------------------------------------------------------------------
 Action<double, int> AddVal = (double V, int MaxCnt) =>
 {
  if(V>0) { bufU.Add( V); if(bufU.Count>Period) bufU.RemoveAt(0); } else
  if(V<0) { bufD.Add(-V); if(bufD.Count>Period) bufD.RemoveAt(0); }
  else
  {
   bufU.Add(0.0); if(bufU.Count>Period) bufU.RemoveAt(0);
   bufD.Add(0.0); if(bufD.Count>Period) bufD.RemoveAt(0);
  }
 };
 //------------------------------------------------------------------------------------------------------
 // среднее и СКО по ~40% центральных значений
 Func< List<double>, Tuple<double, double> > Median1 = (List<double> L) =>
 {
  List<double> tmp=new List<double>(L);
  tmp.Sort();
 
  int n=tmp.Count;                         
  int n1=n/2-n/5;              // [n1..n2] - 40% центральных значений
  int n2=n/2+n/5;
 
  double m, d=0.0;
  if(n1==n2) m=tmp[n1];       // всего одно значение
  else                        // несколько значений
  {
   m=0.0;
   for(int i=n1; i<=n2; i++) m+=tmp[i];
   m/=(n2-n1+1);
   for(int i=n1; i<=n2; i++) { double v=m-tmp[i]; d+=v*v; }
   d=Math.Sqrt(d/(n2-n1));
  }
  return new Tuple<double, double>(m, d);
 };
 //------------------------------------------------------------------------------------------------------
 int Period_=(int)Period;
 double Nv_=(double)Nv;
 double Nd_=(double)Nd;
 double gHigh_=gHigh;
 double gLow_=gLow;
 
 int gDirection;
 double NVLTD1;                      // нужно только для случая, если для уровня запрещен ход назад
 double Ref, O0, L0, H0, C0, M0;
 
 O0=Input.Open[0];
 L0=Input.Low[0];
 H0=Input.High[0];
 C0=Input.Close[0];
 M0=(L0+H0)/2.0;
 
 if(CurrentIndex==0)
 {
  Ref=(UseAvgCandle==0 ? O0 : M0);
  gDirection=(C0>=Ref ? 1 : -1);
  NVLTD1=0.0;
 }
 else
 {
  Ref=(UseAvgCandle==0 ? Input.Close[-1] : (Input.High[-1]+Input.Low[-1])/2.0);
  gDirection=(int)zDirection[-1];
  NVLTD1=cNVLTD[-1];                 
 }
 
 AddVal(H0-Ref, Period_);             // волатильность в + относительно предыдущего закрытия/среднего значения
 AddVal(L0-Ref, Period_);             // волатильность в - относительно предыдущего закрытия/среднего значения
 
 Tuple<double, double> md;
 md=Median1(bufU);                   
 double vU=Nv_*md.Item1+Nd_*md.Item2; // уровень волатильности в +
 md=Median1(bufD);                   
 double vD=Nv_*md.Item1+Nd_*md.Item2; // уровень волатильности в -
 
 double Result;
 if(gDirection>0)
 { // был рост
  // if(L0<gHigh_-vD)  // по Low кажется грубовато
  if(C0<gHigh_-vD &&   // по цене закрытия и
     C0<O0)            // бар отрицательный
  { // переворот
   gDirection=-1;
   gLow_=L0;
   Result=gLow_+vU;
  }
  else
  { // продолжение
   Result=Math.Max(NVLTD1, gHigh_-vD);         // для уровня нет хода назад
   //Result=gHigh_-vD;                         // ход назад возможен
  }
 }
 else
 { // было снижение
  // if(H0>gLow_+vU)
  if(C0>gLow_+vU &&
     C0>O0)
  { // переворот
   gDirection=1;
   gHigh_=H0;
   Result=gHigh_-vD;
  }
  else
  { // продолжение                               
   Result=Math.Min(NVLTD1, gLow_+vU);
   // Result=gLow_+vU;
  }
 }

 if(H0>gHigh_) gHigh_=H0;
 if(L0<gLow_)  gLow_ =L0;

 cNVLTD[0]=Result;
 VLTup[0]=vU; vU=vU/M0*100.0; VLTup_pc[0]= vU;
 VLTdn[0]=vD; vD=vD/M0*100.0; VLTdn_pc[0]=-vD;
 VLTdiff[0]=vU-vD;
 zDirection[0]=gDirection;

// cNVLT.DrawLine((gDirection>0 ? Color.Green : Color.Red), Line.Solid, 2);
 if(gDirection>0) cNVLTD.DrawFigure(Figure.Up,   Color.Green, Line.Solid, 3, Color.Green, 50);
 else             cNVLTD.DrawFigure(Figure.Down, Color.Red,   Line.Solid, 3, Color.Red,   50);
 
 VLTup_pc.DrawHistogram(Color.Green);
 VLTdn_pc.DrawHistogram(Color.Red);
 
 gHigh=gHigh_;
 gLow=gLow_;
}


Безымянный.png


AS IS WITHOUT WARRANTY :mrgreen:


Вернуться в «Пользовательские индикаторы»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 13 гостей