Всем известна знаменитая торговая система Александра Элдера под названием "Три экрана". Смысл этой торговой стратегии заключается в том, что необходимо смотреть на рынок через призму трех разных временных измерений. Сначала на недельном графике определяется общая тенденция, на дневном графике определяются точки входа, а на внутридневном - моменты входа. Эта система является ярким примером работы с разными таймфреймами в одной стратегии.
Однако что делать системному трейдеру, который не хочет одновременно смотреть на три экрана, а хочет запрограммировать все правила торговой стратегии в одной торговой системе? К счастью, программа Wealth-Lab предоставляет возможность работать одновременно с необходимым количеством таймфреймов.
Большое количество популярных техник торговли использует доступ к данным с более высокого таймфрейма. WeatlhScript предоставляет целый набор методов, предназначенных специально для этих целей.
Например для дневных данных можно создать индикаторы на основе недельных или месячных графиков, а данные с внутридневных графиков могут быть сжаты как внутри дня (например с 5-ти минуток до часа) так и до дневных, недельных или даже месячных баров.
Анализ данных с учетом разных таймфреймов (мультитаймфреймовый анализ) позволяет Вам получить доступ к данным и индикаторам, которые базируются на таймфрейме более высокого порядка, но создавать трейда на графике базовой шкалы.
К примеру, предположим, что Вы хотели бы чтобы сделки в Вашей торговой системе осуществлялись в сторону лежащего в основе тренда, который измеряется с помощью скользящей средней на недельных барах. В программе Weatlh-Lab 6 Вы можете определить этот недельный тренд и торговать в его направлении на дневном (или даже внутридневном) интервале.
Как: с помощью программного кода определить шкалу графика
Для того, чтобы определить таймфрейм графика (баров), Вы можете использовать структуру BarDataScale для того, чтобы получить значения простого типа, которые содержит свойства Bars.Scale (Tick - тики, Second - секунды, Minute - минуты, Daily - дни, Weekly - недели, Monthly - месяцы, Quarterly - кварталы, Yearly - годы) и свойство Bars.Interval (20-tick, 30-second, 60-minute). При этом нужно иметь в виду что BarInterval возвращает нулевое значение для не внутридневных шкал.
Пример (как выполнить пример приведенного кода)...
protected override void Execute() { // Получаем информацию об интервале баров графика и типе шкалы графика. BarDataScale ds = Bars.DataScale; //создаем переменную ds типа BarDataScale if( ds.BarInterval == 0 ) //если не интрадейные данные PrintDebug( ds.Scale ); //печатаем тип шкалы графика else PrintDebug( ds.BarInterval + "-" + ds.Scale ); //печатаем информацию об интервале графика и типе шкалы }
В результате появится вот такое сообщение с информацией о шкале и интервале:
Как: получить доступ к данным с верхнего таймфрейма на внутридневной шкале (Intraday - Intraday)
Для получения доступа к данным со старшего таймфрейма на внутридневном графике нужно проделать следующие шаги:
- Используйте метод SetCcaleCompressed() с требуемым периодом сжатия.
- После того, как Вы закончите работать с данными старшего таймфрейма - вызовите метод RestoreScale().
- Далее для получения нового синхронизированного с исходным таймфреймом ряда данных нужно вызвать метод Synchronize().
Этот пример демонстрирует как получить доступ к данным со старшей внутридневной шкалы (для целей иллюстрации рассмотрим 120-ти минутную шкалу) из шкалы с меньшим таймфреймом (в данном случае 15-ти минутной). Мы получим две разных скользящих средних с разными периодами на каждой из шкал и определим их пересечения (как снизу вверх, так и сверху вниз).
Пример (как выполнить пример приведенного кода)...
protected override void Execute() { // Выясняем - является ли исходная шкала внутридневной (выполнение _стратегии требует внутридневных данных). if( Bars.BarInterval == 0 ) { DrawLabel(PricePane, "Данная стратегия работает только с внутридневными данными", Color.Red); return; } // Выясняем - достаточно ли данных для создания 60-ти барного 120-ти минутного индикатора? int barsRequired = 120 * (60 + 1) / Bars.BarInterval; if (Bars.Count < barsRequired) { DrawLabel(PricePane, "Для выполнения стратегии необходимо минимум " + barsRequired.ToString() + " баров", Color.Red); return; } // Получаем две скоьзящие средние с периодом 20 и 60 баров на исходной шкале DataSeries SMA20 = SMA.Series( Close, 20 ); DataSeries SMA60 = SMA.Series( Close, 60 ); //Меняем сжатие шкалы до 120-ти минут SetScaleCompressed( 120 ); // Получаем две скоьзящие средние с периодом 20 и 60 баров на новой (120-ти минутной) шкале DataSeries SMA20_120 = SMA.Series( Close, 20 ); DataSeries SMA60_120 = SMA.Series( Close, 60 ); //Меняем шкалу назад к оригинальному таймфрейму RestoreScale(); SMA20_120 = Synchronize( SMA20_120 ); //синхронизируем данные с исходной шкалой SMA60_120 = Synchronize( SMA60_120 ); //синхронизируем данные с исходной шкалой //Отображаем на графике скользящие средние PlotSeries( PricePane, SMA20, Color.DarkRed, WealthLab.LineStyle.Solid, 2 ); PlotSeries( PricePane, SMA60, Color.DarkGreen, WealthLab.LineStyle.Solid, 2 ); PlotSeries( PricePane, SMA20_120, Color.Red, WealthLab.LineStyle.Solid, 2 ); PlotSeries( PricePane, SMA60_120, Color.Green, WealthLab.LineStyle.Solid, 2 ); // Выделяем на графике пересечения (сверху вниз и снизу вверх) for(int bar = barsRequired; bar < Bars.Count; bar++) { if( CrossOver( bar, SMA20, SMA60 ) ) AnnotateChart( PricePane, Bars.DataScale + "Исходная шкала: снизу вверх", bar, SMA20[bar], Color.Red ); if( CrossOver( bar, SMA20_120, SMA60_120 ) ) AnnotateChart( PricePane, "120 Min: снизу вверх", bar, SMA20_120[bar], Color.Green); if( CrossUnder( bar, SMA20, SMA60 ) ) AnnotateChart( PricePane, Bars.DataScale + "Исходная шкала: сверху вниз", bar, SMA20[bar], Color.Red ); if( CrossUnder( bar, SMA20_120, SMA60_120 ) ) AnnotateChart( PricePane, "120 Min: сверху вниз", bar, SMA20_120[bar], Color.Green); } }
Вот такой график получится в итоге:
Как: получить доступ к дневным данным с внутридневного графика (Intraday - Daily)
Пример демонстрирует как вычислить так называемые уровни Пивот ("Floor Trader Pivots"), известные также как дневные линии поддержки и сопротивления. Для расчета этих уровней используется сжатие внутридневных данных до дневной шкалы. Как и всегда при работе с несколькими таймфреймами отображение индикаторов и выполнение сделок должно осуществляться на искодной шкале с меньшим таймфреймом.
синхронизация данных, сдвинутых по шкале времени вправо
Обратите внимание на то, что в этом примере дневные данные сдвинуты вправо по шкале времени. Таким образом, на внутридневной шкале текущий бар всегда синхронизируется с данными предыдущего дня. С другой стороны, новые пивоты для текущего дня будут отображаться на последнем баре графика, обновляющегося в реальном времени.
Пример (как выполнить пример приведенного кода)...
protected override void Execute() { const int NumTicks = 2; // Количество тиков после пивот-уровня SetScaleDaily(); DataSeries hPivot = AveragePriceC.Series(Bars); DataSeries hDayHigh = High; DataSeries hDayClose = Close; DataSeries hDayLow = Low; RestoreScale(); hPivot = Synchronize( hPivot ) >> 1; hDayHigh = Synchronize( hDayHigh ) >> 1; hDayClose = Synchronize( hDayClose ) >> 1; hDayLow = Synchronize( hDayLow ) >> 1; hPivot.Description = "Pivot"; DataSeries hR1 = ( 2 * hPivot - hDayLow ); hR1.Description = "R1"; DataSeries hS1 = ( 2 * hPivot - hDayHigh ); hS1.Description = "S1"; DataSeries hR2 = ( hPivot - ( hS1 - hR1 ) ); hR2.Description = "R2"; DataSeries hS2 = ( hPivot - ( hR1 - hS1 ) ); hS2.Description = "S2"; HideVolume(); PlotSeries( PricePane, hR2, Color.Maroon, LineStyle.Dotted, 1); PlotSeries( PricePane, hR1, Color.Olive, LineStyle.Solid, 1 ); PlotSeries( PricePane, hPivot, Color.Blue, LineStyle.Solid, 1 ); PlotSeries( PricePane, hS1, Color.Fuchsia, LineStyle.Dotted, 1 ); PlotSeries( PricePane, hS2, Color.Red, LineStyle.Solid, 1 ); // Значение прорыва уровня (будет добавлено и вычтено из уровней поддержки/сопротивления) double boVal = Bars.SymbolInfo.Tick * NumTicks; //в нашем случае - 2 минимальных шага цены (тика) // Find the StartBar of the 2nd day int StartBar = 0; int cnt = 0; for(int bar = 0; bar < Bars.Count; bar++) { if( Bars.IntradayBarNumber(bar) == 0 ) cnt++; if( cnt == 2 ) { StartBar = bar; break; } } for(int bar = StartBar; bar < Bars.Count; bar++) { // Не торговать на первых двух барах в начале дня (на барах с номерами "0" и "1" if( Bars.IntradayBarNumber(bar) < 2 ) continue; //переходим к следующей итерации цикла if (IsLastPositionActive) { Position p = LastPosition; if( p.PositionType == PositionType.Long ) { if( !SellAtStop(bar + 1, p, hS1[bar], "StopLoss" ) ) SellAtLimit(bar + 1, p, hR2[bar], "PftTgt" ); } else if( !CoverAtStop(bar + 1, p, hR1[bar], "StopLoss" ) ) CoverAtLimit(bar + 1, p, hS2[bar], "PftTgt" ); } else if( Bars.IsLastBarOfDay( bar ) == false ) //если не является последним баром дня { bool NewPositionEntered = false; //произведен ли вход в новую позицию if( Close[bar] < hR1[bar] ) NewPositionEntered = BuyAtStop(bar + 1, hR1[bar] + boVal) != null; if( !NewPositionEntered ) if( Close[bar] > hS1[bar] ) ShortAtStop(bar + 1, hS1[bar] - boVal); } } }
В результате на графике внутри дня мы можем просматривать пивот-уровни, которые рассчитываются по дневным барам.
Как: наложить дневные свечи на внутридневной график
Следующий пример показывает Вам как можно отобразить на внутридневном графике искусственные дневные свечи.
Пример (как выполнить пример приведенного кода)...
protected override void Execute() { HideVolume(); // Убедимся в том, что работаем с внутридневными данными: if ( !Bars.IsIntraday ) return; // Получаем дневные бары: SetScaleDaily(); Bars iDay = Bars; //присваиваем переменной iDay типа Bars бары дневного графика RestoreScale(); iDay = Synchronize( iDay ); // Стандартная (быстрая) синхронизация for(int bar = 1; bar < Bars.Count; bar++) { if ( Bars.IsLastBarOfDay(bar) || (bar == Bars.Count-1) ) //если текущий бар является последним баром дня или последним баром на графике { int o = bar - Bars.IntradayBarNumber( bar ); // рассчитываем номер первого бара текущего дня double p1 = Open[o]; //Цена открытия первого бара текущего дня double p2 = Close[bar]; //Цена закрытия текущего дня double p3 = iDay.High[bar]; //максимальная цена текущего дня double p4 = iDay.Low[bar]; //минимальная цена текущего дня double[] rectangle = { o, p1, o, p2, bar, p2, bar, p1 }; // создаем массив с координатами 4-х точек, рисующих прямоугольник тела свечи (против часовой стрелки) int avgBar = bar - Bars.IntradayBarNumber(bar)/2; //находим середину дня: if( p2 > p1 ) //если цена закрытия больше цены открытия (если свеча белая) { DrawPolygon( PricePane, Color.Silver, Color.Empty, WealthLab.LineStyle.Invisible, 2, true, rectangle ); //рисуем прямоугольник, используя заготовленный массив (тело свечи) DrawLine( PricePane, avgBar, p4, avgBar, p1, Color.Silver, WealthLab.LineStyle.Solid, 3 ); //рисуем линию от минимума до окткрытия (нижнюю тень свечи) DrawLine( PricePane, avgBar, p3, avgBar, p2, Color.Silver, WealthLab.LineStyle.Solid, 3 ); //рисуем линию от максимума до закрытия (верхнюю тень свечи) } else //если цена закрытия меньше цены открытия (если свеча черная) { DrawPolygon( PricePane, Color.Silver, Color.Silver, WealthLab.LineStyle.Invisible, 2, true, rectangle ); //рисуем тело свечи (закрашивая его) DrawLine( PricePane, avgBar, p3, avgBar, p4, Color.Silver, WealthLab.LineStyle.Solid, 3 ); //рисуем линию от низа до верха (сразу две тени, т.к. тело свечи непрозрачно) } } } }
Вот так будет выглядеть этот график.
Тут сразу видно и внутридневной график и свечи дневного графика. Это демонстрирует мощность работы программы Wealth-Lab.
Intraday - Weekly, Monthly
К сжатию данных нужно подходить разумно. Конечно, внутридневную шкалу графика можно без проблем сжать до дневной либо даже до недельной или месячной шкалы. Однако нужно учитывать размер данных на внутридневном графике с исходным таймфреймом. К примеру, Вам понадобится более 8000 одноминутных баров для создания всего лишь одной месячной свечи.
Как: отобразить на внутридневном графике недельные уровни поддержки и сопротивления
В стратегии возможно использовать не только старшие таймфреймы на внутридневном графике или дневную шкалу. Еще одним шагом вперед станет использование недельных или даже месячных данных. В следующем примере будет продемонстрировано как с графика, построенного на внутридневном таймфрейме взглянуть на уровни поддержки и сопротивления, построенные по недельным барам. Эти уровни поддержки и сопротивления формируются как скользящие средние максимальных и минимальных цен недельных баров и отражаются на внутридневных графиках.
Пример (как выполнить пример приведенного кода)...
protected override void Execute() { // Ищем недельные уровни поддержки - сопротивления на внутридневных графиках // Уровни поддержки / сопротивления это скользящие средние максимальных и минимальных цен недельных баров if( Bars.Scale != BarScale.Minute ) return; //если шкала не внутридневная - ничего не делаем SetScaleWeekly(); //сжимаем шкалу до недельной DataSeries WeeklySup = SMA.Series( Bars.Low, 5 ); DataSeries WeeklyRes = SMA.Series( Bars.High, 5 ); RestoreScale(); //восстанавливаем шкалу в первоначальное состояние WeeklySup = Synchronize( WeeklySup ); WeeklySup.Description = "Недельный уровень поддержки"; WeeklyRes = Synchronize( WeeklyRes ); WeeklyRes.Description = "Недельный уровень сопротивления"; HideVolume(); for(int bar = 0; bar < Bars.Count; bar++) SetBackgroundColor( bar, Color.LightSkyBlue ); PlotSeriesFillBand(PricePane, WeeklySup, WeeklyRes, Color.Black, Color.White, LineStyle.Solid, 2); //рисуем на внутридневном графике уровни }
График этой стратегии будет выглядеть так:
Как: получить доступ к ряду данных индикатора, построенного по недельным барам из графика дневного таймфрейма (Daily - Weekly)
Для того, чтобы проиллюстрировать идею, следующая стратегия покупает когда дневная MACD пересекает нулевой уровень снизу вверх и продает когда недельный MACD пересекает нулевой уровень сверху вниз.
Пример (как выполнить пример приведенного кода)...
protected override void Execute() { /* Открываем длинную позицию при пересечении дневным MACD нулевого уровня снизу вверх Позиция закрывается когда недельный MACD пересекает нулевой уровень сверху вниз */ if( Bars.Scale != BarScale.Daily ) return; //если шкала не дневная, то ничего не делаем. SetScaleWeekly(); //сжимаем шкалу до недельного уровня DataSeries WeeklyMACD = MACD.Series( Bars.Close ); RestoreScale(); //восстанавливаем шкалу в исходное (дневное) состояние WeeklyMACD = Synchronize( WeeklyMACD ); WeeklyMACD.Description = "Weekly MACD"; for(int bar = 60; bar < Bars.Count; bar++) { if (IsLastPositionActive) { if( CrossUnder( bar, WeeklyMACD, 0 ) ) SellAtMarket( bar+1, LastPosition, "Недельный MACD спустился ниже 0" ); } else { if( CrossOver( bar, MACD.Series( Bars.Close ), 0 ) ) BuyAtMarket( bar+1, "Дневной MACD поднялся выше 0" ); } } HideVolume(); ChartPane WeeklyMACDPane = CreatePane( 30, false, true ); DrawHorzLine( WeeklyMACDPane, 0, Color.Red, WealthLab.LineStyle.Solid, 2 ); PlotSeries( WeeklyMACDPane, WeeklyMACD, Color.Blue, WealthLab.LineStyle.Histogram, 2 ); }
Как: получить доступ к ряду данных индикатора, построенного по месячным барам из графика дневного таймфрейма (Daily - Monthly)
Для получения доступа к недельным и месячным данным из стратегии, которая работает с дневными данными, нужно проделать следующие шаги:
- вызовите методы SetScaleWeekly() и SetScaleMonthly() соответственно;
- после того, как Вы закончите оперировать данными в старшем таймфрейме, вызовите метод RestoreScale();
- следующим шагом станет вызов нового синхронизированного ряда данных (DataSeries) либо нового объекта баров (Bars), которые активируются при вызове метода Synchronize().
Здесь приводится код простой стратегии, которая использует в приказе сразу три таймфрейма (дневной, недельный и месячный) для того, чтобы принять решение.
Если и недельный и месячный индикатор CCI больше чем 100, а дневной CCI пересекает уровень 100 снизу вверх, то создается приказ на вход на следующем баре по рынку. Эта длинная позиция существует до тех пор, пока дневной CCI не пересечет уровень -200 сверху вниз.
Пример (как выполнить пример приведенного кода)...
using System; using System.Collections.Generic; using System.Text; using System.Drawing; using WealthLab; using WealthLab.Indicators; namespace WealthLab.Strategies { public class CCItriplex : WealthScript { private StrategyParameter cciPeriod; // parameter for period public CCItriplex() { cciPeriod = CreateParameter("CCI Period", 10, 10, 60, 5); } protected override void Execute() { int Period = cciPeriod.ValueInt; if (Bars.Scale != BarScale.Daily) return; //если шкала не является дневной, то ничего не делаем DataSeries cciD = CCI.Series(Bars, Period); //дневной CCI SetScaleWeekly(); //сжимаем шкалу до недельной DataSeries cciW = CCI.Series(Bars, Period); //недельный CCI RestoreScale(); //приводим шкалу в первоначальный вид SetScaleMonthly(); //сжимаем шкалу до месячной DataSeries cciM = CCI.Series(Bars, Period); //месячный CCI RestoreScale(); //приводим шкалу в первоначальный вид cciW = Synchronize(cciW); cciW.Description = "Недельный CCI"; cciM = Synchronize(cciM); cciM.Description = "Месячный CCI"; ChartPane cciPane = CreatePane(50, true, true); PlotSeries(cciPane, cciD, Color.Green, WealthLab.LineStyle.Solid, 1); PlotSeries(cciPane, cciW, Color.Blue, WealthLab.LineStyle.Solid, 1); PlotSeries(cciPane, cciM, Color.Red, WealthLab.LineStyle.Solid, 1); for (int bar = Period; bar < Bars.Count; bar++) { if (IsLastPositionActive) { if (CrossUnder(bar, cciD, -200)) SellAtMarket(bar + 1, LastPosition); } else if ((cciW[bar] >= 100) && (cciM[bar] >= 100) && CrossOver(bar, cciD, 100)) BuyAtMarket(bar + 1); } } } }
Вот так будет выглядеть график:
Сегодня мы рассмотрели очень большую и интересную тему - анализ данных в разрезе разных таймфреймов в рамках одной торговой стратегии. Овладев этими несложными методами работы с разными таймфреймами и научившись сжимать шкалу графика до нужного Вам состояния Вы сможете создавать стратегии позволяющие Вам видеть одновременно несколько измерений рынка и ситуации на нем. Следующий раз мы рассмотрим довольно специфический класс WealthLab.Rules. Чтобы не пропустить новые статьи - подписывайтесь на обновления по RSS.
[...] 12. Анализ данных в Wealth-Lab с учетом разных таймфреймов [...]
Дмитрий, поясни, пожалуйста, зачем нужен при расчете уровней пивот этот цикл?
// Find the StartBar of the 2nd day
int StartBar = 0;
int cnt = 0;
for(int bar = 0; bar < Bars.Count; bar++)
{
if( Bars.IntradayBarNumber(bar) == 0 ) cnt++;
if( cnt == 2 )
{
StartBar = bar;
break;
}
}
Ведь мы этого этого и так рассчитали все уровни.