amibroker

Предохранитель торговых систем

Часто задаюсь вопросом: как формально определить что та или иная торговая система сломалась. Хочу поделиться некоторыми мыслями по этому поводу, и обсудить другие подходы к этой проблеме.

Первое что приходит мне в голову – это управление лимитом стратегии на основе анализа ее же equity. Основная идея такого подхода заключается в том, что в момент просадки по системе у нас срабатывал бы предохранитель, который консервирует стратегию или снижает ее лимит.
По такому случаю я написал пару простеньких скриптов в Амиброкере. Эта штука помогает быстро прикинуть качество того или иного алгоритма, я добавил 3 простых Equity стратегии: Parabolic SAR, EMA, и на основе Median.

Как оно работает:

1. В после теста Амиброкер создает тикер с названием ~~Equity
2. Накидываете оба скрипта на график и наглядно видите улучшились или ухудшились результаты при применении Equity стратегии.

Вот как это будет выглядеть:

31

Теперь немного отойдем от технических вещей, и поговорим о том что ближе к деньгам :)

Читать далее…

Amibroker: несколько способов сварить странную систему и еще один рецепт

Weird Systems Cookbook

Адептам for-loop тестирования посвящается :)

Этот топик даст вам несколько путей создания торговых системы, которые можно оттестировать, и которые никогда не будут работать в реале так же как на тестах.

1. Системы входящие лимитными ордерами
Самая любимая тема. Идея проста как 2 пальца, берем некое среднее, откладываем от него вверх и вниз какой-то канал. Продаем когда цена (High) выше, по цене канала, и покупаем когда цена (Low) ниже нижней границы. Классический пример BollingerBands. В реале предполагается, что эта система работает лимитниками. Амиброкер эту стратегию прекрасно может оттестировать.

Но в риал-тайме вы никогда не получите сделки 1 в 1 с моделью, по следующим причинам:
– Нет гарантий исполнения лимитов (Амиброкер будет считать что 100% сделок исполнены)
– Вам на 100% будут наливать в худших сценариях, и часть самых нажористых сделок будет уходить
– Что делать если волатильность свечи такая, что H и L коснулись обеих границ канала?

2. Система торгующая на одном и том же баре
Что может быть проще: вошли по Open свечи, вышли по Close. Aх да, стопы не забудьте, на выходе грояль.
Один вопрос: а что первое срабатывает у этой weird системы стоп или профит? Ами не знает, я тоже :)

3. Система электросенс
Мои самые любимые грояли. Когда-то я входил на отрытии свечи, если RSI(20) превышал что-то. Все было хорошо, и миллиардный профит, и мечты о машине, квартире, и острове. Кроме риал тайма 😀 Только тогда мне было невдомек, что все встроенные индикаторы Ami используют Close для расчетов, тот самый Close который должен появиться после того самого Open на котором я открыл трейд.

Вы уже поняли, что векторные ништяки языка имеют в себе некоторые подводные камни – это издержки подхода. Есть event модель тестера, но там другие косяки, скорость тестирования на пару порядков ниже, и объем кода в 3-5 раз выше.

Anti-weird trading system

Чтобы избежать множество этих камней, я разработал несколько простых принципов построения системы:

  1. Самое главное: Входите в сделку только по Close, весь анализ стройте на основе Close – это избавит от заглядывания в будущее, в том числе скрытого как у “системы электросенс” и тестер станет прямым! Это относится только к цене входа, конечно же, OHLCV – можно использовать в анализе, а не только Close
  2. Избегайте торговать на одном баре
  3. Избегайте ставить близкие стопы/тейки, Ами не знает что у свечи было первым High или Low. Иначе перейдите на более мелкий фрейм, вплоть до тиков.

Все вместе это выливается в следующий шаблон кода:

//
// Author: ubertrader
// 24.09.2012
// </code>

// П.1: Запретить выход на том же баре что и вход
//
SetOption( "AllowSameBarExit", False);
//
// Если вы входите по Close, эту опцию нужно ставить False,
// иначе если H или L удовлетворяют условию стопа, он у вас сработает.
// Это заглядывание в прошлое - другой тип weird систем :)
//
SetOption( "ActivateStopsImmediately", False);

//
// Обратный сигнал активирует выход из открытой позиции
// ЗАПРЕЩЕНО! В моей парадигме - это так.
SetOption( "ReverseSignalForcesExit", False);

//
// Отключает задержки входа выхода
// В нашем случае они не нужны
SetTradeDelays(0,0,0,0);

//
// Сбрасываем настройки всех стопов, которые установлены в свойствах тестера.
// Стопы/Тейки/трейлинги - делаем все в коде, ставить их через GUI идиотизм 1999 года выпуска!
ApplyStop(stopTypeLoss, stopModeDisable, 1);
ApplyStop(stopTypeNBar, stopModeDisable, 1);
ApplyStop(stopTypeTrailing, stopModeDisable, 1);
ApplyStop(stopTypeProfit, stopModeDisable, 1);

//
// SAMPLE NON-WEIRD SYSTEM CODE
// Пусть это будет RSI

Buy = RSI(30) &gt; 70; //Покупаем когда RSI выше 70
Sell = C &lt; MA(C, 20); //Продаем когда цена ниже средней

Short = Cover = 0; //нэту шортов
//BuyPrice = SellPrice = Close; // -- это по умолчанию задается в настройках как Close и желательно ничего не менять

//
// Правила в том виде как они записаны, генерируют десятки или даже сотни повторяющихся
// на каждом баре сигналов. Тестер конечно приведет это в норму, но я привык к тому,
// чтобы мои сигналы на графике совпадали с тестером 1 в 1
// Equity(1, 0) - расставляет все сигналы точно также как это делал бы тестер
// Более того эта функция считает стопы и их цену - все это можно вывести на график.
Equity(1, 0);

//
// Финт ушами!
// Эквивалентно Buy at next bar Open
// Да, посчитали по Close и в самом конце перенесли на Open!
// Это позволит нормально торговать эту систему в RT.
//
Short=Ref(Short,-1);
Cover=Ref(Cover,-1);
Buy=Ref(Buy,-1);
Sell=Ref(Sell,-1);
BuyPrice= O;
SellPrice = O;
ShortPrice= O;
CoverPrice = O;

//
// И последнее стопы!
// Стопы только после того как перенесли все на Open, иначе будут считаться неправильно!
//
ApplyStop(stopTypeNBar, stopModeBars, 90); // Этот стоп можно использовать и до "финта ушами"
ApplyStop(stopTypeProfit, stopModePoint, 1000, 1);
ApplyStop(stopTypeLoss, stopModePoint, 500, 1);

//Чтобы на графике отразились и стопы!
Equity(1,0);

//
// Последнее рисуем цену и стрелочки входов/выходов
// Аргумент "-8" в PlotShapes() для того чтобы стрелка четко указывала на уровень (это смещение в пикселях)
Plot(Close,"",colorBlack,styleCandle);

PlotShapes((Buy > 0) * shapeUpArrow, colorGreen, 0, BuyPrice, -8);
PlotShapes((Sell > 0) * shapeDownArrow, colorRed, 0, SellPrice, -8);
PlotShapes((Cover > 0) * shapeHollowUpArrow, colorGreen, 0, CoverPrice, -8);
PlotShapes((Short > 0) * shapeHollowDownArrow, colorRed, 0, ShortPrice, -8);

Amibroker: как работает тестер

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

Чтобы создать простейщую систему нужно просто инициализировать несколько переменных (тоже векторные!):

  • Buy – вход в лонг
  • Sell – выход из лонга
  • Short – вход в шорт
  • Cover – выход из шорта

Также есть переменные, которые называются BuyPrice,SellPrice, ShortPrice, CoverPrice – они указывают тестеру на цены по которым мы будем входить/выходить из позиции.

Вернемся к посту об архитектуре языка и возьмем оттуда примеры.

Пусть наше правило входа в лонг будет гласить: MidPx > 96.
Для этого пишем код:

MidPx = (H + L) / 2;
MidPxGt96 = MidPx > 96;
Buy = MidPxGt96; //или Buy = MidPx > 96;

16

Как видно на примере из Excel, переменная Buy (которая равна MidPxGt96) принимает значение True на 3-м индексе. Именно этот момент входа использует тестер для входа в лонг. Но у нас есть еще сигналы на 4 и 5 барах – как быть с ними? Их тестер Amibroker просто проигнорирует!. Более того, так как мы не указали тестеру переменную Sell, он не закроет эту сделку никогда, Вы получите аналог Buy-And-Hold.

Добавляем выходы


MidPx = (H + L) / 2;
MidPxGt96 = MidPx > 96;
Buy = MidPxGt96; //или Buy = MidPx > sy96;
Sell = MidPx == 98;

Значение Sell принимает True только на 4 баре. Теперь тестер посчитает нам 2 сделки, на барах с 3 по 4, и с 5 по бесконечность.

Небольшая иллюстрация из хелпа:

Тем кто хочет вникнуть в детали:
1. Back-testing your trading ideas
2. Portfolio-level backtesting

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

Поговорим о более интересных вещах – что может и что не может тестер Amibroker, и какие подводные камни он имеет.

Что умеет тестер Amibroker

  1. Портфельное тестирование – стоков, фьючей и форекса. Можно каждому сигналу проставить рейтинг, и если не будет хватать денег или тестер упрется в ограничение по отрытым позициям, он из двух и более кандидатов выберет лучшего. (см. главу USING POSITION SCORE)
  2. Rotational Trading – собирает портфели на основе неких числовых рейтингов. Можно с помощью этой штуки собирать разные нейтральные long/short портфели, или создать TAA(Tactical Asset Allocation) модели. Вот описание.
  3. Можно получать доступ к котировкам других инструментов и принимать на их основе торговые решения

Что НЕ умеет тестер Amibroker или делает плохо

  1. Торговля парами – не смотря на то, что в features list Amibroker парный трейтинг есть. Делается он через жопу, я себе примерно представляю как это сделать, но это будет очень криво и некрасиво. Все упирается в архитектуру, как только тестер переходит к другому инструменту, он напрочь забывает, что у него открыто и по какой цене на других инструментах.
    Как вариант можно сделать синтетический инструмент – спрэд, и тестить на его основе, я так реализовал свои парные стратегии, но у этого подхода свои ограничения.
  2. Управление позицией – доливки/урезки позиции есть, но сделаны тоже через одно место. И без черного пояса по Amibroker и бутылки с ними толком не разобраться, даже я не представляю как это работает :) Хотя просто никогда не нуждался во всякого рода алгоритмах управления позицией. У кого есть желание прошу читать: Pyramiding (scaling in/out) and mutliple currencies in the portfolio backtester
    Но что точно вы никогда не сделаете, так это не получите значения эквити из кода стратегии. Это просто АДов АДъ, даже не пытайтесь этот вопрос задавать мне. Все хитрые схемы ММ идут лесом, юзайте Excel :) Нет не совсем все так плохо, у Amibroker есть специальный Custom Backtester Interface, который может сгладить некоторые косяки тестера, напр. можно реализовать хитрые схемы ММ. Но из кода к еквити все равно не обратиться.
  3. Amibroker не умеет обращаться из старшего таймфрейма к младшему. Нельзя из дневок обратиться к минуткам, но из минуток к дневкам можно.

Amibroker: введение

Многие знают, что я использую Amibroker, как один из рисерч тулзов, в нем я делаю все свои системы на фьючерсах и акциях. Лицензия Amibroker стоит около 200$, это делает его одним из самых доступных программ такого уровня.

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

Хочу сразу сказать, что в RT я торгую через C# софт, собственного производства, а Amibroker использую только в качестве платформы для рисерча.

В чем главная сила Amibroker:

  1. Векторный язык, который позволяет максимально быстро и эффективно проверять множество идей, тратя на это пару строк кода. Это свойство языка незаменимо, в периоды рисерча, когда пишутся тонны “чернового” кода. Если взять код с таким же функционалом написанный на C#, объем кода будет превышать код Ami в 10 и более раз.
  2. Быстрый много-поточный тестер и оптимизатор – с версии 5.50 в Amibroker появился новый многопоточный тестер. Например, 1 прогон стратегии на 5000 американских акций у меня на 6-ядерном Phenom занимает около 50 секунд.
  3. Возможности для исследований у Amibroker есть много механизмов, которые позволяют сделать исследование более быстрым и удобным. Например, режим Exploration, который выводит в удобном табличном формате данные, которые можно обрабатывать во внешних приложениях Excel, Matlab, Python. Сущуствуют механизмы низкоуровневой графики, которые позволяют добавлять как новые картинки в отчеты тестера, так и проводить анализ онлайн.
  4. Плагины на C++ и .NET (3rd party dotnetforab.com)

Amibroker – идеальная рисерч платформа для акций, фьючерсов и форекса. Но у него начинаются проблемы, когда нужно сделать шаг в сторону парного трейдинга, опционов, умного ММ.

Слабые стороны Amibroker:

  1. Нет интеграции с онлайном – Amibroker такая штука, которая умеет много считать, но не умеет торговать. В этом смысле Amibroker отстал от конкурентов, например Multicharts или WLD 5+. Конечно Amibroker может получать онлайн данные из разных источников, кидать ордера, даже есть спец либа под IB. Но это у меня вызывает серьезные вопросы, это несерьезный ритейл уровень, года так 2005. Сейчас даже у мейнстримового ритейл софта типа, NinjaTrader дела с execution обстоят лучше.
  2. Шаг влево/шаг вправо – расстрел – это проблема любого специализированного софта, если нужно вылезти за рамки, то приходится долго и мучительно плясать с бубном. С другой стороны, это касается лишь 1% юзеров, и не так это и страшно.В этом плане такие языки как Python или C# имеют неоспоримые преимущества.
  3. Субминутные фрейми идут лесом – если есть желание посчитать тики или секундки, Amibroker это позволитсделать, но скушает немерено памяти и времени. Но дела с потреблением памяти и производительности обстоят лучше, чем в WLD.

Bottom line:
Если вы хотите проверить с десяток идей, быстро обсчитать индикатор и посмотреть как он ведет себя на истории, или подготовит данные для анализа в других приложениях – Amibroker лучший инструмент. Не смотря на то, что я хвалю и Python и Matlab и C#, только Amibroker позволяет с максимальной эффективностью реализовать и проверить множество идей. А уже когда идея реализована и оттестирована, мы берем любимый C#, пишем сотни строк, качественного и оттестированного с помощью UnitTests кода, и запускаем в run-time. Только сотни и тысячи строк, получаются из пары десятков кода Amibroker.

Дальше напишу несколько постов о языке AFL, и good practice в написании AFL кода.

Amibroker: архитектура языка

Язык Amibroker AFL – векторный, именно с этого момента у многих начинается недопонимание. Именно отсюда заявления, что тестер глючит, или стратегия не так как надо считает.

Самое главное, что нужно понять, что все переменные Amibroker – это массивы. Amibroker использует числа float (single precision) в своих расчетах, поэтому результаты расчетов некоторых индикаторов могут отличаться от других программ.

Для примера возьмем встроенные переменные AFL для работы с OHLC, ничего не нужно объявлять дополнительно, чтобы получить данные об инструменте. Список встроенных переменных данных: Open, High, Low, Close, Volume, OI, Aux1, Aux2. Или короткие эквиваленты: O, H, L, C, V. Aux1, Aux2 – это вспомогательные поля в которые можно загрузить разную доп информацию, например из текстовиков.

Простой пример выражения:

MidPx = (H + L) / 2;

Переменные H и L – это массив данные об High и Low текущего инструмента, который выбран на графике или по которому идет бэктест.
Длина всех массивов (переменных) равна числу баров и переменной BarCount.

Теперь разберемся в том, как работает выражение (H + L) / 2, допустим у нас 6 баров, с индексами от 0 до 5. В Amibroker индексация массивов тоже начинается с 0, как в C/C++. Алгоритм проходит все бары, и на каждом баре проводит простейшую математическую операцию.

Простой пример в Excel:

1

Мы можем переписать ту же операцию только через циклы, и она будет работать в Amibroker:

MidPx = 0; //Инициализируем массив, и забиваем его нулями
for( i = 0; i < BarCount; i++ )
{
   MidPx[i] = (H[i] + L[i]) / 2;
}

В точно таком же виде мы бы написали это на C#. Можно для интереса посчитать сколько символов заняло написание этого кода. Код C#-like занял у нас 67 символов, включая “{}[]();\tab\space”, код на векторном диалекте AFL – 20 символов. Эффективность кода с т.з. написания в 3 раза выше (!), и это на простейшей операции, что уж говорить о более сложных. Когда нам нужно будет инициализировать несколько сот таких переменных, в сложном коде – это не редкость, нам придется напечатать несколько сот/тысяч лишних знаков!

Как вы поняли язык Amibroker может работать в 2х режимах: в векторном и “цикловом”. C т.з. циклов и ключевых слов синтаксис Amibroker ничем не отличается от C-like языков. For, while, do-while, switch, if – все это есть.

Логические переменные и условия
В Amibroker все просто 1 – true, 0 – false. Логические переменные – это такие же массивы 0 и 1, длиной BarCount, как и все массивы Amibroker.

Простой пример, логического выражения:

MidPxGt96 = MidPx > 96;

Посмотрим какой результат даст выражение из примера Excel выше и напишем эквивалентную функцию через Excel:

2

Аналогичный код в C-like style:

MidPx = 0; //Инициализируем массив, и забиваем его нулями
MidPxGt96 = 0;
for( i = 0; i < BarCount; i++ )
{
   MidPx[i] = (H[i] + L[i]) / 2;
   if(MidPx[i] > 96)
        MidPxGt96[i] = 1;
    else
        MidPxGt96[i] = 0;
}

Попробуем сделать простейшее условие. Допустим для какого индикатора нам нужно взять Low в случае MidPx <= 96, и High во всех остальных. В Amibroker это делается с помощью функции IIF.

CondHL = IIF(MidPxGt96, H, L);

То же само через Excel:

3

Аналогичный код в C-like style:

MidPx = 0; //Инициализируем массив, и забиваем его нулями
MidPxGt96 = 0;
CondHL = 0;
for( i = 0; i < BarCount; i++ )
{
   MidPx[i] = (H[i] + L[i]) / 2;
   if(MidPx[i] > 96)
        MidPxGt96[i] = 1;
    else
        MidPxGt96[i] = 0;
    
    if(MidPxGt96[i])
        CondHL[i] = H[i];
    else
        CondHL[i] = L[i];
}

Самой распространенной ошибкой новичка является, то что он питается использовать if с массивами, вместо IIF. if – используется в циклах, или для сравнения строковых переменных, или в структурах кода вида if(Action() == actionExploration).

if(MidPxGt96[i]) - OK!
if(MidPxGt96) - FAIL! MidPxGt96 - это массив

Итоговый код
AFL: 3 строчки, 75 знаков

MidPx = (H + L) / 2;
MidPxGt96 = MidPx > 96;
CondHL = IIF(MidPxGt96, H, L);

C-like: 13 строк, 275 знаков

MidPx = 0;
MidPxGt96 = 0;
CondHL = 0;
for( i = 0; i < BarCount; i++ )
{
   MidPx[i] = (H[i] + L[i]) / 2;
   if(MidPx[i] > 96)
        MidPxGt96[i] = 1;
    else
        MidPxGt96[i] = 0;
    
    if(MidPxGt96[i])
        CondHL[i] = H[i];
    else
        CondHL[i] = L[i];
}

Если вам стало интересно и хотите попробовать Amibroker. Обязательно прочтите и проработайтеUnderstanding how AFL works Проработайте с карандашиком, калькулятором и excel! Как только поймете суть векторных вычислений, все пойдет как по маслу.

Оригинал моего поста

Гистограмма месячной прибыли…

Кому интересен код рисующий подобный график:

В

Взят отсюда : www.amibroker.com/kb/2007/10/11/low-level-gfx-example-yearlymonthly-profit-chart/

Только оригинальный код в % считает, а я в деньгах. Можно закинуть его в папку Formulas\Report Charts, и в версиях выше 5.16 Вам в отчеты будет падать график с этой гистограммой.
Посему прикрепляю свой код:

SetBarsRequired(1000000,1000000);
eq = Foreign("~~~EQUITY", "C" );

yr = Year();
mo = Month();

YearChange = yr != Ref( yr, -1 );
MonChange = mo != Ref( mo, -1 );

FirstYr = 0;
LastYr = 0;

startbar = 0;

////////////////////////////
// SKIP non-trading bars
////////////////////////////
for( i = 0; i <; BarCount; i++ )
{
  if( eq[ i ] )
  {
    startbar = i;
    break;
  } 
}

////////////////////////////
// collect yearly / monthly changes in equity
// into dynamic variables
////////////////////////////

LastYrValue = eq[ startbar  ];
LastMoValue = eq[ startbar  ];

MaxYrProfit = MinYrProfit = 0;
MaxMoProfit = MinMoProfit = 0;

for( i = startbar + 1; i < BarCount; i++ )
{
  if( YearChange[ i ] || i == BarCount - 1 )
  {
    Chg = ( eq[ i ] - LastYrValue );
    VarSet("ChgYear"+ yr[ i - 1 ], Chg );

    MaxYrProfit = Max( MaxYrProfit, Chg );
    MinYrProfit = Min( MinYrProfit, Chg );

    if( FirstYr == 0 ) FirstYr = yr[ i - 1 ];
    LastYr = yr[ i ];

    LastYrValue = eq[ i ];
  }

  if( MonChange [ i ] || i == BarCount - 1 )
  {
    mon = mo[ i - 1 ];

    Chg = ( eq[ i ] - LastMoValue );

    VarSet("ChgMon" + yr[ i - 1 ] + "-" + mon, Chg );
    VarSet("SumChgMon"+ mon, Chg + Nz( VarGet("SumChgMon"+ mon ) ) );
    VarSet("SumMon" + mon, 1 + Nz( VarGet("SumMon"+ mon ) ) );
 
    MaxMoProfit = Max( MaxMoProfit, Chg );
    MinMoProfit = Min( MinMoProfit, Chg );

    LastMoValue = eq[ i ];
  }
}

/////////////////////////////////////////////////
// Drawing code & helper functions
////////////////////////////////////////////////

GfxSetOverlayMode( 2 );

CellHeight = (Status("pxheight")-1)/(LastYr - FirstYr + 3 ); 
CellWidth = (Status("pxwidth")-1)/14; 
//GfxSelectFont( "Tahoma", 8.5 ); 
GfxSelectFont( "Tahoma", 8.5 , 1, False, False, 0.3); 


GfxSetBkMode( 1 );

function PrintInCell( string, row, Col ) 
{
 Color =  ColorRGB( IIf( row == 0 || col == 0 || col == 13, 220, 255 ), 255, IIf( row % 2, 255, 220 ) );
 GfxSelectSolidBrush( Color   );
 GfxRectangle( Col * CellWidth, 
                    row * CellHeight, (Col + 1 ) * CellWidth + 1, 
                    (row + 1 ) * CellHeight  + 1); 
 GfxDrawText( string, Col * CellWidth + 1, 
                    row * CellHeight + 1, 
                    (Col + 1 ) * CellWidth, (row + 1 ) * CellHeight, 32+5 ); 
} 

YOffset = 25;
XOffset = 15;

function DrawBar( text, bar, numbars, y, Miny, Maxy )
{
 BarWidth = (Status("pxwidth") - 4 * XOffset )/( numbars + 1 ); 
 BarHeight = Status("pxheight") - 2 * YOffset;
 relpos = ( y - Miny ) / (Maxy - Miny );

 xp = XOffset + ( bar + 0.5 ) * BarWidth;
 yp = YOffset + BarHeight * ( 1 - relpos );
 xe = XOffset + ( bar + 1 ) * BarWidth;
 ye = YOffset + BarHeight * ( 1 - ( -miny )/( maxy - miny ) );
  
 if( y > 0 )
 {
 GfxGradientRect( xp, yp, 
                  xe , ye,
                  ColorHSB( 70, 255 * relpos, 255 ), ColorHSB( 70, 20, 255 ) ); 
 }
 else
 {
 GfxGradientRect( xp, ye, 
                  xe , yp,
                  ColorHSB( 0, 20, 255 ), ColorHSB( 0, 255 * ( 1 - relpos ), 255 ) ); 
 }
 GfxSelectFont( "Tahoma", 8.5 , 1, False, False, 0.3); 
 GfxTextOut( text, xp, ye );
// GfxTextOut( StrFormat("%.2f", y ), xp, yp );
// GfxSelectFont( "Tahoma", 8.5 ); 
}    

function DrawLevels( Miny, Maxy )
{
  range = Maxy - Miny;

  grid = grid = round(range / 20 / 100)* 100; 
/*
  if( range < 10 ) grid = 1;
  else 
  if( range < 20 ) grid = 2;
  else 
  if( range < 50 ) grid = 5;
  else 
  if( range < 100 ) grid = 10;
  else 
  if( range < 200 ) grid = 20;
  else 
  if( range < 500 ) grid = 50;
  else 
  if( range < 5000 ) grid = 500;
  else 
  if( range < 10000 ) grid = 1000;
*/


//  _TRACE("grid = "+grid +" range "+range );
  
  width = Status("pxwidth") - 4 * XOffset;
  height = Status("pxheight") - 2 * YOffset;

  GfxSelectPen( colorBlack, 1, 2 );
  for( y = grid * ceil( Miny / grid ); y <= grid * floor( Maxy / grid ); y += grid )
  {
    yp =  YOffset + Height * ( 1 -  ( y - Miny ) / (Maxy - Miny ) );

    GfxMoveTo( XOffset, yp );
    GfxLineTo( XOffset + width , yp );
    GfxTextOut( ""+ y, XOffset + 2 + width, yp );
  }

  GfxSelectPen( colorBlack, 1, 0 );
  GfxMoveTo( XOffset, YOffset );
  GfxLineTo( XOffset + width, YOffset );
  GfxLineTo( XOffset + width, YOffset + Height );
  GfxLineTo( XOffset , YOffset + Height );
  GfxLineTo( XOffset , YOffset );
}

function DrawYear(  text, bar, numbars)
{
 BarWidth = (Status("pxwidth") - 4 * XOffset )/( numbars + 1 ); 
 BarHeight = Status("pxheight") - 2 * YOffset;

 height = Status("pxheight") - 2 * YOffset;

 xp = XOffset + ( bar + 0.5 ) * BarWidth;
  
 GfxSelectFont( "Tahoma", 12 , 700); 
 GfxTextOut( text, xp, BarHeight );
 GfxSelectPen( colorGrey40, 1, 2 );

  GfxMoveTo( xp - 0.25  * BarWidth, YOffset );
  GfxLineTo( xp - 0.25  * BarWidth, Status("pxheight") );
 GfxSelectFont( "Tahoma", 8.5 ); 

}


MonthNames = "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec";

function DisplayProfitTable( )
{
 Header = "Year,"+MonthNames+",Yr Profit%";
 for( Col = 0; (Colname = StrExtract( Header, Col ) ) != ""; Col++ )
 {
  PrintInCell( ColName, 0, Col );
 }

 Row = 1;
 for( y = FirstYr; y <= LastYr; y++ )
 {
  PrintInCell( StrFormat("%g", y ), Row, 0 ); 
  PrintInCell( StrFormat("%.1f%", VarGet("ChgYear" + y ) ), Row, 13 ); 
  for( m = 1; m <= 12; m++ )
  { 
   Chg = VarGet("ChgMon" + y + "-" + m);
   if( Chg ) 
     PrintInCell( StrFormat("%.1f%", Chg ), Row, m );
   else
     PrintInCell( "N/A", Row, m );
  }
  Row++;
 } 

 PrintInCell("Mon. Avg", Row, 0 );
 for( m = 1; m <= 12; m++ )
 { 
   PrintInCell( StrFormat("%.1f%",  Nz( VarGet("SumChgMon" + m)/VarGet("SumMon" + m ) ) ), Row, m );
 }

}

function DisplayYearlyProfits()
{
 Bar = 0;
 for( y = FirstYr; y <= LastYr; y++ )
 {
   Chg = VarGet("ChgYear" + y );
   DrawBar( ""+y, Bar++, ( LastYr - FirstYr + 1 ), Chg, MinYrProfit, MaxYrProfit );
 }
 GfxTextOut("Yearly % Profit chart", 10, 10 );
 DrawLevels( MinYrProfit, MaxYrProfit ); 
}

function DisplayMonthlyProfits()
{
 Bar = 0; 
 MaxBars = 0;
 MinAvgProf = 99999999999999999;
 MaxAvgProf = 0;

for( y = FirstYr; y <= LastYr; y++ )
 {
  for( m = 1; m <= 12; m++ )
  { 
   Chg = VarGet("ChgMon" + y + "-" + m);
	if(IsEmpty(Chg))
		continue;
	Maxbars++;
   MinAvgProf = Min( MinAvgProf, Chg );
   MaxAvgProf = Max( MaxAvgProf, Chg );
//	_TRACE("Maxavg: " + MaxAvgProf + "MinAvg " + MinAvgProf);
  }
}

 for( y = FirstYr; y <= LastYr; y++ )
 {
  for( m = 1; m <= 12; m++ )
  { 
   Chg = VarGet("ChgMon" + y + "-" + m);


//DrawBar("" + y + "\n" + StrExtract(MonthNames, m-1 ), Bar++, ( LastYr - FirstYr + 1 ) * 13, Chg, MinAvgProf , MaxAvgProf );   

   if( !IsEmpty(Chg) ) 
//	   DrawBar("" + y + "\n" + StrExtract(MonthNames, m-1 ), Bar++, MaxBars, Chg, MinAvgProf , MaxAvgProf );   
	   DrawBar("" + (m) , Bar++, MaxBars, Chg, MinAvgProf , MaxAvgProf );   
	
	if(m == 1)
	{
		DrawYear("" + y, Bar-1, MaxBars);
	}
  }

 } 
DrawLevels( MinAvgProf , MaxAvgProf ); 


/*

//--------
 Bar = 0; 
 MinAvgProf = MaxAvgProf = 0;
 for( y = 1; y <= 12; y++ )
 {
   Chg = VarGet("SumChgMon" + y ) / VarGet("SumMon" + y );
   MinAvgProf = Min( MinAvgProf, Chg );
   MaxAvgProf = Max( MaxAvgProf, Chg );
 }

 for( y = 1; y <= 12; y++ )
 {
   Chg = VarGet("SumChgMon" + y ) / VarGet("SumMon" + y );
   DrawBar( StrExtract(MonthNames, y-1 ), Bar++, 13, Chg, MinAvgProf , MaxAvgProf );
 }
 GfxTextOut("Avg. Monthly % Profit chart", 10, 10 );

 DrawLevels( MinAvgProf , MaxAvgProf ); 
*/
}

///////////////////////////
// This function checks if currently selected symbol
// is portfolio equity
//////////////////////////
function CheckSymbol()
{
 if( Name() != "~~~EQUITY" )
 {
  GfxSelectFont( "Tahoma", 20 ); 
  GfxSetBkMode( 2 );
  GfxTextOut("For accurate results switch to ~~~EQUITY symbol", 10, 10 );
 }
}

////////////////////////////
// Main program - chart type switch
////////////////////////////
type = ParamList("Chart Type", "Avg. Monthly Profits|Profit Table|Yearly Profits", 0 );

switch( type )
{
 case "Profit Table": 
         DisplayProfitTable();  
         break;
 case "Yearly Profits": 
         DisplayYearlyProfits();
         break;
 case "Avg. Monthly Profits": 
         DisplayMonthlyProfits();
         break;
}

CheckSymbol();

//
//	Exploration Monthly profits
//
if(Status("Action") == actionExplore)
{
Filter = 0;
SetOption("NoDefaultColumns", True );
fi = 0;

DT = 0;
mRet = 0;

for( y = FirstYr; y <= LastYr; y++ )
 {
  for( m = 1; m <= 12; m++ )
  {	
   Chg = VarGet("ChgMon" + y + "-" + m);
	if(IsEmpty(Chg))
		continue;
	Filter[fi] = 1;
	DT[fi] = 10000 * (y - 1900) + 100 * m + 1;
	mRet[fi] = Chg;	
	fi++;
  }
}

AddColumn(DateTimeConvert( 2, DT ), "Date", formatDateTime);
AddColumn(mRet, "MonthReturn", 1.1);
//AddColumn(StDev(mRet,12), "StDev(12) MoRetrn", 1.2);
//AddColumn(MA(mRet,12), "Avg(12) MoRetrn", 1.2);
//AddColumn(MA(mRet,12) / StDev(mRet,12), "ModSR MoRetrn", 1.2);

}

 

Создание торговой системы

Предположим у нас появилась некая идея, которую мы хотим оттестировать при этом мы предполагаем (помечтаем) что эта идея будет универсально работать на любом финансовом инструменте в мире за любой период времени. Так как инструменты обладают величайшим разнообразием как по стоимости так по волатильности нам необходимо иметь инструмент который приводил бы результаты тестов на каждом инструменте к общему эквиваленту.

Какие подходы существуют к выбору сайза для прикидочных тестов:

  • Тест 1 лотом/контрактом  – самый простой подход, используется как правило для фьючерсов
  • Тест с привязкой к портфелю – подход как правило применим для акций (проблема тут состоит в привязке к начальной сумме на счете, а все результаты как правило считаются в % от портфеля, при этом возможно реинвестирование которое искажает картину)

Проблем с этими подходами не было, если бы нам не требовалось проводить дополнительные расчеты с композитными портфелями систем – так буду называть систему состоящую из других систем. Другая проблема, что эти подходы абсолютно не учитывают волатильность инструментов на котором они тестируются и волатильность периодов, соответственно они будут давать результаты разного порядка.

Каким же образом мы можем привести результаты тестов на совершенно разных инструмента, периодах и таймфреймах к общему знаменателю?

Ответ: отнормировать сайз которым входит система на волатильность инструмента, при этом отвязавшить от начального капитала, и проводить все расчеты в деньгах. Такой подход позволяет отличить реальную доходность системы на периоде, а не то что она попала в высокую волатильность.

Для иллюстрации:

Для простоты возьмем элементарное подобие системы: купить на открытии продать на закрытии дня, еще для упрощения задачи тестируем на 1 инструменте – индексе РТС (дневки 1995года).

Задача: количественно оценить эффективность системы в различные периоды времени.

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

1. Тестирование без реинвестирования – думаю всем ясно почему. Как частный случай – будем тестировать 1 лотом.

2. Отвязка результата от первоначального капитала – тут вопрос более тонкий, т.к. тестируем 1 лотом мы можем абстрагироваться как от начального капитала, так и стоимости удержания позиции, естесственно мы потеряем такую оценку как профит на вложенный капитал. Но отвязка от капитала умышленна дальше попробую объяснить почему, но сейчас примем как данность, что результативность системы мы будем оценивать в деньгах.

3. Оценка показателей системы в денежном выражении (уход от относительного изменения) – в связи с тем что мы отказались от стоимости позиции и начального капитала естесственно мы не можем посчитать профит в %. Умышленно я не считаю его от изменения цен в %, т.к. мой подход подразумевает мультиинструментальность (стоки, фьючи, форекс, сложные композитные инструменты типа пар и т.п.) Все это дело я хочу сравнивать между собой в рамках одного подхода, и естесственно нужно как-то нормировать, об этом далее.

Задача оценить ее ежемесячные доходности и сравнить между собой. На суд общественности выложу 3 графика с месячными доходностиями этой системы но с разнымы подходами к выбору сайза:

1. Сайз – 1 лот

2. Сайз – тарим на все деньги с реинвестированием.

3. Сайз – нормированный (об ентом дальше)

Вопрос: можем ли мы глядя на эти графики сказать, что система отработала лучше/хуже в 2004 году чем например в 2008 ?

Месячные доходности при тестировании 1 лотом

Месячные доходности при тестировании всем сайзом

Месячные доходности при тестировании 1 Unit (нормировка на волатильность)

Посмотрим на диаграммы доходностей с сайзом "на все деньги + реинвест", и с нормированным сайзом (см. рисунки выше). Что мы видим в контексте 2004 и 2008 года? В первом случае (где на все деньги таримся), "+" и "-" результаты в 2004 по масштабу явно отличаются от 2008 года, можно ли сделать вывод – система отработала в 2004 году хуже т.к. принесла меньше профита? Думаю нельзя, т.к. любая система зависит от рыночной волатильности, которая по сути влияет на профит. Во втором случае мы как раз нормируем наш сайз на рыночную волатильность, и приводим доходности системы к общему знаменателю. Месячные доходности нашей системы теперь распределены более менее равномерно, и мы можем сравнивать помимо абсолютного профита, еще просадки, средние сделки в пунктах, станд. отклонения и др. абсолютные показатели систем(ы) между собой.

Продолжение следует…

Шаблон торговой системы для Amibroker

В предыдущем посте были приведены доказательства преимуществ нормированного по волатильности сайза, называю его UnitSize, он считается как отношение константы K к некоему динамическому показателю волатильности рынка, я взял 10ти дневный ATR(). Важно понимать что 10 дневный считается именно на дневном таймфрейме, в т.ч. для внутридневных систем идет обращения к старшему фрейму, важно то чтобы для любого таймфрейма одного инструмента размер UnitSize был эквивалентен дневному!
Ниже “шапка” кода для Амиброкера которая идет для любой моей системы:

SetOption( "InitialEquity", 100000);
SetOption( "DisableRuinStop", True); 
SetOption( "AllowSameBarExit", True); 
SetOption( "ActivateStopsImmediately", True);
SetOption( "MinShares", 0.0001);
SetOption( "AllowPositionShrinking", True);
SetOption( "MinPosValue", 0);
SetOption( "MaxOpenPositions", 100); 
SetOption( "FuturesMode", True);
SetOption( "InterestRate", 0);
SetOption( "PriceBoundChecking", False);
SetOption( "AccountMargin", 100);
SetOption( "ReverseSignalForcesExit", False); 
SetTradeDelays(0,0,0,0);  

//Расчитываем UnitSize

TimeFrameSet(inDaily);

  UnitDaily = Nz(1000/ATR(10));

TimeFrameRestore();

UnitSize = TimeFrameExpand(UnitDaily, inDaily, expandFirst);

MarginDeposit = 1;

PositionSize = UnitSize * MarginDeposit;

SetBarsRequired(sbrAll,sbrAll); // Включаемвсебары


UPD. 8.09.2010 У некоторых людей получались нули на выходе при тестах с UnitSize, из-за того что подход позволяет использовать дробные лоты < 0, задайте в окне Information и настройках тестера поле RoundLotSize = 0

Теперь усложним задачу, хочу посмотреть как моя “супер система” из предыдущего поста отработает на индексе S&P500 (^GSPC:Yahoo). Опять же месячные доходности, обратите внимание на порядок цифр доходностей индекса РТС(см. предыдущий пост, последний рис.) и SP500 (5000;-5000).

Ну и пожалуй еще добавлю 10y TREASURY NOTE (^TNX:Yahoo) – порядок цифр тот же (5000;-5000). Для интереса можете на Yahoo прикинуть цены и волатильности каждого инструмента

Что это нам дает?
В итоге мы получили возможность сравнивать результаты систем на любом инструменте, это касается не только месячных доходностей но и Equity, Avg Profit и пр. показателей эффективности систем.
UnitSize – это фундамент System Development Framework который я использую, хочу отметить нормировка по волатильности не является особой методикой ММ, тут задача абсолютно прикладная – привести системы к общему знаменателю. А уже к нормированным результатам мы можем применять абсолютно любые схемы ММ.

Исследуем сделки торговой системы

Накидал небольшой код, суть его проста: в режиме Exploration добавлять к сделкам сгенерированным из Buy/Sell сигналов, любые характеристики (будь то уровень RSI, соотношение объемов и т.п. до входа в сделку).

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

Главное, что этот подход позволяет отойти от модели “написал правило – оттестировал – оптимизировал”, так скажем новый взгляд – осознанный подход. Это позволяет трейдеру стать имхо более робастным, в поиске закономерностей в системе.
Области применения вижу следующие:
– Анализ существующих систем, для поиска оптимальных стопов/тейков
– Поиск фильтров для систем
– Анализ серий сделок для реализации ММ на основе последовательностей выигрышных и проигрышных сделок
– Определение фазы рынка в котором система может работать наиболее эффективно
– И многое другое

Полученные от Exploration’a данные можно вставить в эксель и обработать как душе угодно, как всегда дальше вы ограничены только своей фантазией.
Читать далее…

Мультисистемный тестер стратегий (часть 1)

Давно задался вопросом: как оттестировать в один прогон и построить Equity несколько систем одновременно. Не прибегая к танцам с бубном вроде объединения данных Equity каждой системы или написанию своего тестера стратегий.

Идею мне подкинул Олег (000), суть ее проста:
1. Создаем несколько клонов-инструментов (с одинаковыми данными и именами например EESR_1, EESR_2, EESR_N – по количеству систем)
2. Создаем один файл мега-систему, которая содержит в себе несколько систем.
3. В этом файле настраиваем фильтры для каждой системы(чтобы она торговала только определенной серией инструментов *_2 например)
4. Задаем объем капитала каждой системе
5. И тестим.
На выходе мы получаем, уже составную Equity, посчитанные общие к-ты Шарпа, CAR/MDD и т.п.

Основной вопрос, что при тесте 10 систем на 100 инструиментах, ручками все переименовывать не хватит жизни, поэтому мое врожденное чувство лени заставило меня накатать следующий скриптик: который помогает сделать(или очистить ранее созданную) группу клонов-инструментов для дальнейшего тестирования.
Читать далее…