Код: Выделить всё
function Initialize()
{
IndicatorName = "TFConverter";
PriceStudy=true;
AddInput("Input", Inputs.Candle);
// серии объема сделаны невидимыми, т.к. иначе индикаторы, привязанные к сериям цен, рисуются неправильно
AddSeries("Volume", DrawAs.Histogram, Color.Green, AxisType.ZeroBased, false, Axes.New); // true
AddSeries("AskVolume", DrawAs.Histogram, Color.Green, AxisType.ZeroBased, false, Axes.New); // намеренно невидимая
AddSeries("BidVolume", DrawAs.Histogram, Color.Red, AxisType.ZeroBased, false, Axes.New); // true
AddSeries("Open", DrawAs.Line, Color.Transparent); // исходно невидимы; для визуализации выбрать цвет в окне настройки индикатора
AddSeries("High", DrawAs.Line, Color.Transparent);
AddSeries("Low", DrawAs.Line, Color.Transparent);
AddSeries("Close", DrawAs.Line, Color.Transparent); // плохо для визуализации, т.к. скачет за последней ценой закрытия
AddSeries("Mid", DrawAs.Line, Color.Transparent); // (H+L)/2 ведет себя более консервативно
AddParameter("OutputTF_sec", 1800); // взять значение в скобках из таблицы внизу (или любое другое >=1)
AddParameter("OutputHistory", 50); // число выходных сконвертированных свечек; выбрать число, достаточне для построения индикаторов по выходным данным
AddGlobalVariable("InputTF_sec", Types.Int, 1);
AddGlobalVariable("Period", Types.Int, 0); // окно во входных барах
}
function Evaluate()
{
// вычисления можно легко перетащить для счета непосредственно в стратегию
//========================================================
int history=(int)OutputHistory; // результаты конверсии
double[] Open_ =new double[history]; // индекс: 0-самая свежая свеча, history-1 - самая старая
double[] Close_ =new double[history]; // т.е. индекс типа того, что для серий (но без знака!)
double[] High_ =new double[history];
double[] Low_ =new double[history];
double[] Volume_ =new double[history];
double[] AskVolume_=new double[history];
double[] BidVolume_=new double[history];
//========================================================
Action<int> ConvertTF = (int period) =>
{
double high, low, vol, ask;
int index1, index2, ci=CurrentIndex;
for(int i=0; i<history; ++i)
{
index2=-i*period;
if(index2<=-ci) break;
index1=index2-period+1;
if(index1<=-ci) index1=-ci+1;
high=Input.High[index1];
low =Input.Low [index1];
vol =Input.Volume[index1];
ask =Input.VolumeAsk[index1];
for(int t=index1+1; t<=index2; t++)
{
high=Math.Max(high, Input.High[t]);
low =Math.Min(low, Input.Low[t]);
vol+=Input.Volume[t];
ask+=Input.VolumeAsk[t];
}
Open_ [i]=Input.Open[-index1];
Close_[i]=Input.Close[-index2];
High_ [i]=high;
Low_ [i]=low;
Volume_ [i]=vol;
AskVolume_[i]=ask;
BidVolume_[i]=vol-ask;
}
};
//========================================================
if(CurrentIndex==0)
{
int tf=Input.Timeframe;
switch(tf)
{
case -1: InputTF_sec=1; break; // 1S (1 секунда)
case -2: InputTF_sec=2; break; // 2S (2)
case -3: InputTF_sec=3; break; // 3S (3)
case -4: InputTF_sec=4; break; // 4S (4)
case -5: InputTF_sec=5; break; // 5S (5)
case -6: InputTF_sec=6; break; // 6S (6)
case -10: InputTF_sec=10; break; // S10 (10)
case -12: InputTF_sec=12; break; // S12 (12)
case -15: InputTF_sec=15; break; // S15 (15)
case -20: InputTF_sec=20; break; // S20 (20)
case -30: InputTF_sec=30; break; // S30 (30)
case 1: InputTF_sec=60; break; // M1 (60)
case 2: InputTF_sec=60*2; break; // M2 (120)
case 3: InputTF_sec=60*3; break; // M3 (180)
case 4: InputTF_sec=60*4; break; // M4 (240)
case 5: InputTF_sec=60*5; break; // M5 (300)
case 6: InputTF_sec=60*6; break; // M6 (360)
case 10: InputTF_sec=60*10; break; // M10 (600)
case 12: InputTF_sec=60*12; break; // M12 (720)
case 15: InputTF_sec=60*15; break; // M15 (900)
case 20: InputTF_sec=60*20; break; // M20 (1200)
case 30: InputTF_sec=60*30; break; // M30 (1800)
case 60: InputTF_sec=3600; break; // H1 (3600)
case 60*2: InputTF_sec=3600*2; break; // H2 (7200)
case 60*3: InputTF_sec=3600*3; break; // H3 (10800)
case 60*4: InputTF_sec=3600*4; break; // H4 (14400)
case 60*6: InputTF_sec=3600*6; break; // H6 (21600)
case 60*8: InputTF_sec=3600*8; break; // H8 (28800)
case 60*12: InputTF_sec=3600*12; break; // H12 (43200) чисто по времени
// case 60*24: InputTF_sec=3600*24; break; // D1 (86400) чисто по времени, но дневные бары на самом деле считают с учетом длительности дневной сессии
case 60*24: InputTF_sec=60*(60*8+45); break; // D1 (31500) день с учетом длительности дневной сессии; выходит меньше, чем для TF=12H
default: InputTF_sec=0; break;
}
if(InputTF_sec!=0)
{
Period=(int)(OutputTF_sec/InputTF_sec+0.5);
if(Period<1) Period=1;
}
else Period=1;
}
// ConvertTF((int)Period); // выходные числовые данные можно использовать в расчетах
// визуализация; меняет данные в прошлом, серии нельзя использовать при тестировании стратегий!
if(CurrentIndex==MaxIndex)
{
ConvertTF((int)Period);
double hi, lo;
int si;
for(int t=0; t<history; t++)
{
si=t*Period;
Open [si] =Open_ [t];
Close[si] =Close_[t];
High [si]=hi =High_ [t];
Low [si]=lo =Low_ [t];
Mid [si]=(hi+lo)/2.0;
Volume[si] =Volume_[t];
AskVolume[si]=AskVolume_[t];
BidVolume[si]=BidVolume_[t];
}
}
}
Поскольку по приходе очередного бара границы данных вычисляемого ТФ меняются (в описанном выше случае минуты раз от раза попадают в разные получасовки), как-то меняется и весь график. Однако, если на, скажем, Close напустить пару скользящих средних, то они ведут себя неплохо, формируемые ими пересечения визуально живут нормально.
К сожалению, непосредственно на результаты пересчета можно напустить только "точечные" (Input.Price) индикаторы; "свечные" же (Input.Candle) придется переписывать.