Однако, в среднюю волатильность по периоду на равных правах входят:
1) гэпы (утренние чаще всего) и просто отдельные большие скачки. На самом деле до и после гэпов/скачков волатильность обычно более-менее одинаковая, отдельные же броски необоснованно "раскачивают" среднюю волатильность и, соответственно, пороги.
2) движения вверх и движения вниз. После нескольких больших среднего монотонных изменений ряда (например, несколько раз вверх), т.е. после фронта, средняя волатильность возрастает, и начало обратного движения отлавливается плохо.
NVLT - попытка починить эти проблемы.
1) Волатильность рассчитывается не по среднему, а по ~40% медианных значений (т.е. значения за период сортируются по возрастанию, а затем берется среднее по ~40% значений в середине). Т.о. в вычислении среднего не участвуют экстремально большие/малые значения за период, т.е. гэпы/скачки и "штиль" не учитываются, а учитывается "стационарная" часть ряда, на фоне которой как раз и хочется выделять эти самые скачки.
2) Отдельно вычисляется волатильность движений вверх и вниз. За счет этого разворот после фронтов определяется четче, т.к. "противоположная" волатильность на монотонном фронте не меняется (просто нет новых точек для счета), а остается такой же, как и до фронта.
Период индикатора на выбранном тайм-фрейме следует брать примерно равным длительности "волночек ряби" на графике ряда после скачков; одной-двух волн обычно вполне достаточно, слишком большие периоды захватывают "давно прошедшие" времена, затемняя текущие события.
Индикатор сделан для "точечных" входных рядов (не для свечек); модифицировать его в "свечной" вариант не представляет труда.
NVLTD - логическое продолжение NVLT. В NATR и NVLT значение зазора до порога регулируется просто постоянным параметром N (зазор=N*волатильность). Однако, свойства ряда могут меняться со временем, константа N на достаточно большом промежутке может стать неоптимальной. Для реакции на реалии ряда в NVLTD порог вычисляется как ("средняя медианная" волатильность)+N*(СКО тех самых 40% точек из ряда). Для нормального распределения (нормальность распределения цен я, конечно, не проверял, оставляю это энтузиастам ) 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_ тоже с такой же придурью. Бред...)