Формула счета порогов по волатильности баров сделана универсальной:
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_;
}
AS IS WITHOUT WARRANTY
