Проверка системы на синтетике

В прошлой заметке мы говорили о том, как генерировать синтетические ценовые данные из реальных, в этой заметке я покажу, как искусственными данными можно пользоваться. Для демонстрации всего процесса я скачаю торговые данные, создам из них синтетические данные, реализую торговую стратегию, ведущую себя хорошо на исходных данных, после чего посмотрю, как она будет работать на искусственных данных.

В качестве исходных данных я выбрал дневки обыкновенных акций Сбербанка, покрывающие диапазон дат с начала 2008-го года по середину августа 2011-го года. Формат исходных данных следующий (файл *.csv):

Воспользовавшись рабочей формой, созданной в рамках предыдущей заметки, я создал 9 синтетических наборов торговых данных, сохранив их в отдельные *.csv файлы в формате, идентичном исходному. Причины выбора именно этого расширения просты — *.csv файлы легко редактируются с помощью обычного Notepad, кроме того, они открываются с помощью Excel и их могут импортировать различные пакеты технического анализа. Вот как выглядит начало одного из файлов с искусственными данными:

В итоге, на жёстком диске было сохранено 10 файлов с торговыми данными, первый из которых содержит исходные ценовые ряды, тогда как остальные — синтетические:

С помощью Excel можно визуально оценить, насколько непохожими на исходный график получаются графики синтетики. Скомбинировав все 10 файлов в один лист Excel и визуализировав цены, можно видеть такую картину (исходные данные — в левом верхнем углу):

Для создания тестовой торговой стратегии и дальнейшей работы с ней я использовал пакет Wealth-Lab Developer 4. Воспользовавшись соответствующим мастером импорта, я создал источник данных с именем MICEX и внёс данные в WLD:

Для тестов была выбрана стратегия на основе пробоя канала, работающая с длинными и короткими позиции. Логика её работы такова:

  1. Вокруг цен строится канал на основе максимальных максимумов и минимальных минимумов за последние N и M баров соответственно.
  2. Вход в длинную позицию осуществляется стоп-приказом при пробое верхней границы канала вверх, вход в короткую позицию осуществляется стоп-приказом при пробое нижней границы канала вниз.
  3. Выход из длинной позиции осуществляется стоп-приказом при пробое нижней границы канала вниз, выход из короткой позиции осуществляется стоп-приказом при пробое верхней границы канала вверх.

Параметрами, которые можно в данной стратегии оптимизировать, являются величины периодов N и M. Полный код стратегии, реализованной на языке Wealth Script (язык пакета WLD 4), таков:

{#OptVar1 5; 1; 30; 1}
{#OptVar2 1; 1; 30; 1}

var nHighPeriod: integer = #OptVar1;
var nLowPeriod: integer  = #OptVar2;

var nStartBar: integer = Int(Max(nHighPeriod, nLowPeriod)) - 1;

var serHigh: integer = HighestSeries(#High, nHighPeriod);
var serLow: integer  = LowestSeries(#Low, nLowPeriod);

PlotStops();

var nBar: integer;
for nBar := nStartBar to BarCount() - 1 do
begin
    if not LastPositionActive() then
    begin
        if not BuyAtStop(nBar + 1, @serHigh[nBar], 'Enter') then
            ShortAtStop(nBar + 1, @serLow[nBar], 'Enter')
    end
    else
    begin
        var nPos: integer = LastPosition();
        if PositionLong(nPos) then
            SellAtStop(nBar + 1, @serLow[nBar], nPos, 'Exit')
        else
            CoverAtStop(nBar + 1, @serHigh[nBar], nPos, 'Exit');
    end;
end;

HideVolume();

PlotSeriesLabel(serHigh, 0, #Blue, #Thin, 'High');
PlotSeriesLabel(serLow,  0, #Red,  #Thin, 'Low');

Этот код можно скопировать в редактор ЧартСкрипта в Wealth-Lab Developer, он полностью готов к тестированию. Разберу его подробнее.

{#OptVar1 5; 1; 30; 1}
{#OptVar2 1; 1; 30; 1}

В этих двух строках задаются параметры переменных оптимизации, к значениям которых в коде можно обращаться через зарезервированные ключевые слова #OptVar1 и #OptVar2. Эти строки необходимы для того, чтобы код стратегии был готов к обработке во встроенном в Wealth-Lab Developer оптимизаторе. Первое число в фигурных скобках задаёт значение переменной оптимизации по умолчанию, второе и третье числа задают пределы, в которых оптимизатор может менять значения переменной, третье число определяет шаг изменений.

var nHighPeriod: integer = #OptVar1;
var nLowPeriod: integer  = #OptVar2;

var nStartBar: integer = Int(Max(nHighPeriod, nLowPeriod)) - 1;

Этот код объявляет переменные nHighPeriod (период, используемый для вычисления верхней границы канала) и nLowPeriod (период, используемый для вычисления нижней границы канала). В переменную nStartBar помещается номер первого бара, с которого стратегия может начинать работать. Поскольку значения переменных оптимизации #OptVar1 и #OptVar2 по умолчанию равны 5 и 1 соответственно, вычисление верхней границы канала производится по прошедшим пяти барам, а вычисление нижней границы канала производится по минимуму предыдущего бара.

var serHigh: integer = HighestSeries(#High, nHighPeriod);
var serLow: integer  = LowestSeries(#Low, nLowPeriod);

Эти команды производят расчёт верхней и нижней границ канала.

PlotStops();

Вызов функции PlotStops() включает визуализацию уровней стоп-приказов на графике.

var nBar: integer;
for nBar := nStartBar to BarCount() - 1 do
begin
    if not LastPositionActive() then
    begin
        if not BuyAtStop(nBar + 1, @serHigh[nBar], 'Enter') then
            ShortAtStop(nBar + 1, @serLow[nBar], 'Enter')
    end
    else
    begin
        var nPos: integer = LastPosition();
        if PositionLong(nPos) then
            SellAtStop(nBar + 1, @serLow[nBar], nPos, 'Exit')
        else
            CoverAtStop(nBar + 1, @serHigh[nBar], nPos, 'Exit');
    end;
end;

Это главный рабочий цикл стратегии. В его рамках поочерёдно перебираются все доступные бары и реализуется логика открытия (внутри блока «if not LastPositionActive() then») и закрытия торговых позиций.

HideVolume();

PlotSeriesLabel(serHigh, 0, #Blue, #Thin, 'High');
PlotSeriesLabel(serLow,  0, #Red,  #Thin, 'Low');

Вызов функции HideVolume() убирает панель объёмов с графика, вызов функций PlotSeriesLabel(..) отрисовывает границы канала.

Протестировав стратегию на исторических данных, получаем следующую визуализацию процесса торговли на графике:

И следующую обнадёживающую линию капитала (equity):

Для того, чтобы иметь возможность сравнить линии капитала, генерируемые системой на всех наборах данных, воспользуемся контекстным меню, с помощью которого можно поместить данные графика equity в буфер обмена:

Эти данные можно вставить в рабочий лист Excel, после чего отобразить. Таблица с данными выглядит следующим образом (на картинке показано начало таблицы):

Отрисовка всех десяти столбцов с данными по линиям капитала системы, протестированной на одном наборе исторических и девяти наборах синтетических данных, такова:

Визуальный анализ характеристик работы торговых систем позволяет решать определённые оценочные задачи, однако, существенно больше информации, выраженной в цифрах, можно получить, применяя статистический аппарат. Я могу порекомендовать к изучению книгу, освещающую методы статистического анализа результатов работы торговых стратегий: Dr. Howard Bandy, «Modeling Trading System Performance», Blue Owl Press 2011.

На картинке выше видно, что результаты тестирования на синтетических данных существенно уступают результатам теста на данных реальных. Это говорит о неустойчивости торговой системы, причиной которой является чрезмерная подгонка параметров её работы (периодов вычисления границ канала) под характеристики прошлых цен. С одной стороны, это неприятная новость — систему едва ли можно торговать в её нынешнем виде, с другой стороны — мы узнали это до того, как потеряли реальные деньги.

В двух последних заметках я разобрал алгоритм создания синтетических данных и привёл простой пример их использования, далее я покажу, какую дополнительную информацию о возможном поведении системы можно извлекать из метрик, полученных в результате тестирования на реальных исторических данных.

Назад   На оглавление   Вперед

Написать администратору