2013-01-23

Системные вариации на тему древних мудрецов.

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

Не будем выяснять как эти обрывки рукописей попали ко мне, опустим этот момент как не имеющий отношения к тому, о чем пойдет речь далее.

Итак, без лишних предисловий сразу начнем с главного. Самым важным моментом древней торговли было определение -- какой сегодня будет день -- день благосклонный к покупкам или день благосклонный к продажам? Свойство дня определялось следующим образом:

-Если цена вчерашнего закрытия сессии была больше цены вчерашнего открытия.
и
-Если цена вчерашнего закрытия сессии была больше цены позавчерашнего закрытия.
то
Сегодня будет день, благосклонный к продажам.

-Если цена вчерашнего закрытия сессии была меньше цены вчерашнего открытия
и
-Если цена вчерашнего закрытия сессии была меньше цены позавчерашнего закрытия
то
Сегодня будет день, благосклонный к покупкам.



Сразу же, попробуем этот момент отразить в коде для WealthLab 4:

if (PriceClose(bar-1) > PriceOpen(bar-1)) and
   (PriceClose(bar-1) > PriceClose(bar-2)) then
      begin
           SellDay := 1;
           BuyDay := 0;
      end;
    if (PriceClose(bar-1) < PriceOpen(bar-1)) and
   (PriceClose(bar-1) < PriceClose(bar-2)) then
      begin
           SellDay := 0;
           BuyDay := 1;
      end;

Далее, нам надо определить диапазон размаха цены в течении сессии. Древние мудрецы это определяют простым вычитанием:

-самая высокая цена сессии минус самая низкая цена сессии

Попробуем закодировать этот момент:

Range := PriceHigh(bar-1) - PriceLow(bar-1);


Далее,

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

-Цена открытия сегодняшней сессии плюс половина вчерашнего диапазона размаха цены.

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

-Цена открытия сегодняшней сессии минус вчерашний диапазон размаха цены.

В коде это выглядит так:

if BuyDay = 1 then
 begin
    BuyAtStop( Bar, PriceOpen(bar) + 0.5 * Range, 'BuyBuyDay' );
    ShortAtStop( Bar, PriceOpen(bar) - 1 * Range, 'ShortBuyDay' );
 end;


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

-Цена открытия сегодняшней сессии минус половина вчерашнего диапазона цены.

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

--Цена открытия сегодняшней сессии плюс весь вчерашний диапазон цены.

В коде это будет так:

if SellDay = 1 then
 begin
    ShortAtStop( Bar, PriceOpen(bar) - 0,5 * Range, 'ShortSellDay' );
    BuyAtStop( Bar, PriceOpen(bar) + 1 * Range, 'BuySellDay');
 end;

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

Соответственно, если сегодня день продаж, то продаем:

-Цена открытия сессии минус половина вчерашнего диапазона.

Если сегодня день покупок, то продаем

-Цена открытия сессии минус весь вчерашний диапазон.

if SellDay = 1 then
    SellAtStop( Bar, PriceOpen(bar) - 0,5 * Range, p, 'SellSellDay' );
if BuyDay = 1 then
    SellAtStop( Bar, PriceOpen(bar) - 1 * Range, p, 'SellBuyDay' );

Если же имеем короткую позицию,то

Если сегодня день покупок, то покупаем:

-Цена сегодняшнего открытия плюс половина вчерашнего диапазона.

Если сегодня день продаж, то покупаем

-Цена сегодняшнего открытия плюс весь вчерашний диапазон.

if BuyDay = 1 then
    CoverAtStop( Bar, PriceOpen(bar) + 0,5 * Range, p, 'CoverBuyDay' );
if SellDay = 1 then
    CoverAtStop( Bar, PriceOpen(bar) + 1 * Range, p, 'CoverSellDay');


Ну что, теперь объединим все вместе и посмотрим что из этого выйдет. Потестируем, например, на одном их самых древних фьючерсов -- Соевых Бобах.

И вот что получаем:

Ну что, теперь объединим все вместе и посмотрим что из этого выйдет. Потестируем, например, на одном их самых древних фьючерсов -- Соевых Бобах.

И вот что получаем:



Ах да, при чем тут Соевые Бобы -- играть на бирже в РФ по умолчанию означает торговать фьючерс на индекс РТС.

Потестируем его условный дневной график, полученный с Финама:



Заметьте, это все, вообще, без оптимизации.

Однако, взглянув на графики, можно заметить что при довольно сильных движениях цены, так называемых трендах, часто задевается стоп и движение опять продолжается в прежнем направлении. Например, когда все сильно падает, при обвале рынка несколько баров подряд, у нас бывает день покупок и уровень ее (покупки) находится довольно близко. Поэтому он часто ложно срабатывает. Что мы можем тут сделать? Либо стандартно разделить график на тренд вверх/тренд вниз и соответственно покупать только когда тренд вверх, а продавать только когда тренд вниз, либо придумать еще что-то.

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

Например, если сегодня день покупок, то будем покупать:

-Цена открытия сессии плюс половина вчерашнего диапазона ЛИБО по самой низкой цене позавчерашнего дня -- смотря что будет больше.

В коде выглядит так

if BuyDay = 1 then
    BuyAtStop( Bar, Max(PriceOpen(bar) + 0,5 * Range, PriceLow(bar-2)), 'BuyBuyDay' );


Все то же и для продаж, только наоборот.

Смотрим что получается для фьючерса на индекс РТС. Видно что ложных срабатываний на графике стало поменьше, да и эквити стала чуть ровнее:




Ну а теперь можно и пооптимизировать. Смотрим, что конкретно. Видно что всего два параметра, коэффициенты к диапазону вчерашнего дня, которые мы приняли по умолчанию 0,5 и 1.

Вот окончательный код, готовый к оптимизации:

{#OptVar1 5;1;10;1}
{#OptVar2 10;1;10;1}
var Bar, p, BuyDay, SellDay: integer;
var Range: float;
var bLongSAR: boolean;
PlotStops;
SellDay := 0;
BuyDay := 0;

for Bar := 5 to BarCount - 1 do
begin

    ////////////////////////////////////////////   BuyDay/SellDay
    if (PriceClose(bar-1) > PriceOpen(bar-1)) and
   (PriceClose(bar-1) > PriceClose(bar-2)) then
      begin
           SellDay := 1;
           BuyDay := 0;
      end;
    if (PriceClose(bar-1) < PriceOpen(bar-1)) and
   (PriceClose(bar-1) < PriceClose(bar-2)) then
      begin
           SellDay := 0;
           BuyDay := 1;
      end;
    /////////////////////////////////////////////  Range
    Range := PriceHigh(bar-1) - PriceLow(bar-1);
    /////////////////////////////////////////////


  if LastPositionActive then
  begin
    p := LastPosition;
    bLongSAR := PositionLong( p );
    if PositionLong( p ) then
    begin
    if SellDay = 1 then
    SellAtStop( Bar, Min(PriceOpen(bar) - 0.1*#OptVar1*Range, PriceHigh(bar-2)), p, 'SellSellDay' );
    if BuyDay = 1 then
    SellAtStop( Bar, Min(PriceOpen(bar) - 0.1*#OptVar2*Range, PriceHigh(bar-2)), p, 'SellBuyDay' );
    end;
    if PositionShort( p ) then
    begin
    if BuyDay = 1 then
    CoverAtStop( Bar, Max(PriceOpen(bar) + 0.1*#OptVar1*Range, PriceLow(bar-2)), p, 'CoverBuyDay' );
    if SellDay = 1 then
    CoverAtStop( Bar, Max(PriceOpen(bar) + 0.1*#OptVar2*Range, PriceLow(bar-2)), p, 'CoverSellDay');
    end;
  end;
  if not bLongSAR then
  begin

    if BuyDay = 1 then
    BuyAtStop( Bar, Max(PriceOpen(bar) + 0.1*#OptVar1*Range, PriceLow(bar-2)), 'BuyBuyDay' );
    if SellDay = 1 then
    BuyAtStop( Bar, Max(PriceOpen(bar) + 0.1*#OptVar2*Range, PriceLow(bar-2)), 'BuySellDay');
  end;
  if bLongSAR then
  begin
    if SellDay = 1 then
    ShortAtStop( Bar, Min(PriceOpen(bar) - 0.1*#OptVar1*Range, PriceHigh(bar-2)), 'ShortSellDay' );
    if BuyDay = 1 then
    ShortAtStop( Bar, Min(PriceOpen(bar) - 0.1*#OptVar2*Range, PriceHigh(bar-2)), 'ShortBuyDay' );
  end;
end;

Ну вот, 20 секунд и прооптимизировали фьючерс на Соевые Бобы за 10 лет. Лучшие коэффициенты по RecoveryFactor получились 0,5 и 0,5. То есть, как бы Соевые Бобы не признают день продаж и день покупок -- они для них равноправны

Вот эквити



Но что САМОЕ ВАЖНОЕ -- ВСЕ РЕЗУЛЬТАТЫ ОПТИМИЗАЦИИ, ВСЕ ПАРАМЕТРЫ ДАЮТ ПОЛОЖИТЕЛЬНЫЙ РЕЗУЛЬТАТ, что свидетельствует о безусловной робастности этой системы по отношению к этому инструменту.



Ой забыл OUT OF SAMPLE посмотреть, а то подумают что переоптимизация. То было за 10 последних лет, а сейчас посмотрим за 20 лет.

Как видим, кривая в OUT OF SAMPLE еще лучше на вид чем в IN SAMPLE


Ах да, опять забыл что мы не признаем других инструментов кроме фьючерса на индекс РТС. Прооптимизируем его.

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



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



И даже выбрав самый лучший результат, видим что заработать в данном случае проблематично.



Вывод -- не все инструменты годятся для любых систем. Есть конкретные системы устойчивые для конкретных инструментов, как видели выше на примере Соевых Бобов и индекса РТС, а есть неустойчивые -- пример SP-500.
Поэтому, кто предварительно не тестирует систему, вполне может нарваться на инструмент, который просто не подходит под эту систему.

Пожалуй все. Теперь можно подумать как усовершенствовать музейную реликвию. А также, кто умеет, перекодировать в другие программы

Код для WealthLab 6

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using WealthLab;
using WealthLab.Indicators;

namespace WealthLab.Strategies
{
public class MyStrategy : WealthScript
{



double range;

StrategyParameter _par1;
StrategyParameter _par2;

public MyStrategy()
{
_par1 = CreateParameter("par1",5,1,10,1);
_par2 = CreateParameter("par2",10,1,10,1);
}

protected override void Execute()
{
int par1 = _par1.ValueInt;
int par2 = _par2.ValueInt;
PlotStops();

bool BuyDay = false;
bool SellDay = false;
for(int bar = 20; bar < Bars.Count; bar++)
{
if(Close[bar-1] > Open[bar-1] && Close[bar-1] > Close[bar-2])
{
SetBarColor(bar, Color.Red);
SellDay  =true;
BuyDay = false;
}
if(Close[bar-1] < Open[bar-1]&&Close[bar-1] < Close[bar-2])
{
SetBarColor(bar, Color.Green);
SellDay  =false;
BuyDay = true;
}
range= High[bar-1] - Low[bar-1];
if(IsLastPositionActive)
{
Position p = LastActivePosition;
if(p.PositionType == PositionType.Long)
{
if(BuyDay)
{
ExitAtStop( bar, p,  Math.Min(Open[bar] - 0.1 * par2 * range, High[bar-2]),  "SellBuyDay" );
ShortAtStop(bar,   Math.Min(Open[bar] - 0.1 * par2 * range, High[bar-2]),  "SellBuyDay" );
}
if(SellDay)
{
ExitAtStop( bar,  p, Math.Min(Open[bar] - 0.1 * par1 * range, High[bar-2]),  "SellSellDay" );
ShortAtStop(bar,   Math.Min(Open[bar] - 0.1 * par1 * range, High[bar-2]), "SellSellDay" );
}
}
else if(p.PositionType == PositionType.Short)
{
if(BuyDay)
{
CoverAtStop( bar,  p, Math.Max( Open[bar] + 0.1 * par1 * range, Low[bar-2]), "CoverBuyDay" );
BuyAtStop( bar,  Math.Max( Open[bar] + 0.1 * par1 * range, Low[bar-2]), "CoverBuyDay" );
}
if(SellDay)
{
CoverAtStop( bar,  p, Math.Max( Open[bar] +0.1 * par2 * range, Low[bar-2]),  "SellSellDay" );
BuyAtStop( bar,  Math.Max( Open[bar] + 0.1 * par2 * range, Low[bar-2]),  "SellSellDay" );
}
}
}
else
if (!IsLastPositionActive )
{
if(BuyDay)
{
BuyAtStop(bar, Open[bar] + 0.1 * par1 * range, "BuyBuyDay");
ShortAtStop(bar, Open[bar] - 0.1 * par2 * range, "ShortBuyDay");
}
if(SellDay)
{
BuyAtStop(bar, Open[bar] + 0.1 * par2 * range, "ShortSellDay");
ShortAtStop(bar, Open[bar] - 0.1 * par1 * range, "BuySellDay");
}
}
}
}
}
}

Комментариев нет:

Отправка комментария