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");
}
}
}
}
}
}

1 комментарий:

  1. Our solely gripe can be the printer's small capability, though this makes it nicely suited for smaller workshops or cramped environments. Simply printing with Carbon’s hardware alone doesn't permit for end use properties with actual world applications. Once the sunshine Air Humidifiers has shaped the part, a second programmable curing process achieves the desired mechanical properties by baking the 3d printed part in a thermal bath or oven.

    ОтветитьУдалить