English 中文 Español Deutsch 日本語 Português
Управление капиталом по Винсу. Реализация в виде модуля Мастера MQL5

Управление капиталом по Винсу. Реализация в виде модуля Мастера MQL5

MetaTrader 5Торговые системы | 18 января 2018, 08:24
9 282 4
Dmitrii Troshin
Dmitrii Troshin

Введение

Работая на финансовых рынках, мы постоянно заняты поиском системы, которая приносила бы нам прибыль. При этом, конечно, хотелось бы, чтобы эта система была максимально стабильной и обладала минимальным риском. Чтобы этого достичь, разрабатываются торговые системы, основной упор в которых сделан на поиске оптимальных точек входа/выхода. Создаются технические индикаторы и торговые сигналы, указывающие, когда покупать/продавать. Разработана целая система ценовых моделей (фигур) для технического анализа. Вместе с тем, как показывает в своей работе "Математика управления капиталом" Ральф Винс, размер капитала, который используется для проведения сделок, не менее важен. Для оптимизации прибыли и, что не менее важно, сохранения депозита нужно определиться с размером лота, которым мы торгуем.

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

Потенциальная прибыль — линейная функция потенциального риска. Это неверно!

Следующая "ложная концепция" — диверсификация уменьшает убытки. Но и это не так. По Винсу:

 Она может это сделать, но только в определенной степени — намного меньшей, чем считает большинство трейдеров.


Основные положения

Для наглядности рассмотрим основные идеи на примерах. Допустим, у нас есть некоторая условная система из двух сделок. Первая сделка выигрывает 50%, а вторая проигрывает 40%. Если мы не реинвестируем прибыль, то выигрываем 10%, а если реинвестируем — та же последовательность сделок дает проигрыш 10%. (P&L=Profit or Loss).

Номер сделки P&L без реинвестирования Полный капитал
P&L с реинвестированием Полный капитал


100

100
1 +50 150
+50 150
2 -40 110
-60 90

При реинвестировании прибыли выигрышная система превратилась в проигрышную. Нетрудно убедиться, что порядок сделок не имеет значения. Из этого примера видно, что при реинвестировании капитала мы не можем действовать точно так же, как и при торговле с фиксированным лотом. Собственно, поиск оптимального размера лота при реинвестировании и составляет основу метода Винса по управлению капиталом.

Пойдем от простого к сложному — начнём с подбрасывания монетки. Пусть в случае выигрыша мы получаем 2 доллара, а в случае проигрыша теряем 1 доллар. Вероятность проигрыша или выигрыша равна 1/2. Допустим, у нас есть 100 долларов. Тогда если мы поставим на кон все 100 долларов, наша потенциальная прибыль будет 200 долларов. Но в случае проигрыша мы потеряем сразу всю сумму и не сможем дальше продолжать игру. При бесконечной игре — а именно такая игра предполагается для оптимизации — мы гарантированно окажемся в проигрыше.

Если бы мы ставили не всю сумму сразу, а какую-то её часть — например, 20 долларов из 100 — то в случае проигрыша у нас оставались бы деньги для продолжения игры. Рассмотрим последовательность возможных сделок при разной доле капитала на одну сделку. Первоначальный капитал везде 100 долларов.

Сделка P&L при К=0.1 Капитал    P&L при К=0.2 Капитал     P&L при К=0.5  Капитал    P&L при К=0.7 Капитал     P&L при К=1 Капитал  
    100         100         100         100         100
+2 20 120    40 140    100 200    140 240    200 300 
-1 -12 108    -28 112    -100  100   -168 72    -300
+2 21.6 129.6    44.8 156.8    100 200    100.8 172.8    0
-1 -12.96 116.64    -31.36 125.44    -100 100    -120.96 51.84    0
+2 23.33 139.97    50.18 175.62    100 200    72.58 124.42    0
-1 -14 125.97   -35.12 140.5   -100 100   -87.09 37.32   0 0
Итого     126      141      100      37      0

Как отмечалось выше, прибыль/убыток не зависит от последовательности сделок. Поэтому вполне корректно, что у нас прибыльные сделки чередуются с убыточными. 

Очевидно, что существует некоторый оптимальный коэффициент (делитель) при котором прибыль максимальна. Для простых случаев, когда вероятность выигрыша и отношение прибыль/проигрыш постоянны, этот коэффициент находят по формуле Келли:

f=((B+1)*P-1)/B

f — оптимальная фиксированная доля, которую далее мы и будем искать;

P — вероятность выигрыша;

B — отношение выигрыш/проигрыш.

Далее для удобства f буду называть просто коэффициентом.

Понятно, что на практике размер и вероятность выигрыша постоянно меняются и формула Келли неприменима. Поэтому для эмпирических данных коэффициент f находится численными методами. Оптимизировать будем прибыльность системы по произвольному эмпирическому потоку сделок. Для прибыли по сделке Винс применяет термин HPR (holding period returns, или прибыль за период удержания позиции). Если сделка принесла прибыль 10%, то HPR =1+0.1=1.1. Следовательно, для одной сделки HPR =1+f*Прибыль/(Максимальный возможный проигрыш), где прибыль берётся со знаком плюс или минус, в зависимости от того получен проигрыш или выигрыш. Фактически коэффициент  f — это коэффициент максимальной возможной просадки. Чтобы найти оптимальный f, нам надо найти максимум произведения по всем сделкам max(HPR1 * HPR2 * ... *HPRn).

Напишем программу нахождения f для произвольного массива данных.

Программа 1. Поиск оптимального f.

double PL[]={9,18,7,1,10,-5,-3,-17,-7};  // Массив прибылей/убытков из книги
double Arr[]={2,-1};

void OnStart()
{
SearchMaxFactor(Arr);                   //Или PL и любой другой массив

}

void SearchMaxFactor(double &arr[])
{
double MaxProfit=0,K=0;                  // Максимальная прибыль
                                         // и коэффициент, ей соответствующий
for(int i=1;i<=100;i++)
{
   double k,profit,min;
   min =MathAbs(arr[ArrayMinimum(arr)]); // находим максимальный убыток в массиве
   k =i*0.01;                            
   profit =1;
// Находим доходность при заданном коэффициенте
      for(int j=0;j<ArraySize(arr);j++)
      {
         profit =profit*(1+k*arr[j]/min);
      }
// Сравниваем с максимальной доходностью 
   if(profit>MaxProfit)
   {
   MaxProfit =profit;
   K=k;
   }
}
Print("Optimal K  ",K," Profit   ",NormalizeDouble(MaxProfit,2));

}

Можно убедиться, что для случая +2,-1,+2,-1 и т.д. f будет тот же, что и полученный с помощью формулы Келли.

Имейте в виду, что оптимизация имеет смысл только для прибыльных систем — т.е. систем, для которых математическое ожидание (средняя прибыль) положительное. Для убыточных систем оптимальное f=0. Управление размером лота не делает убыточную систему прибыльной. Наоборот, если в потоке нет убытков, т.е. если все P&L>0, оптимизация также не имеет смысла, f=1, и нужно торговать максимальным лотом.

Мы можем, пользуясь графическими возможностями MQL5, найти не только максимальное значение f, но и посмотреть всю кривую распределения прибыли в зависимости от f. Ниже представлена программа, рисующая график прибыли в зависимости от коэффициента f.

Программа 2. График прибыли в зависимости от f.

//+------------------------------------------------------------------+
//|                                                      Graphic.mq5 |
//|                                                       Orangetree |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Orangetree"
#property link      "https://www.mql5.com"
#property version   "1.00"

#include<Graphics\Graphic.mqh>
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
//double PL[]={9,18,7,1,10,-5,-3,-17,-7};             // Массив прибылей/убытков из книги
double PL[]={2,-1};

void OnStart()
  {
double X[100]={0};
for(int i=1;i<=100;i++)
   X[i-1]=i*0.01;
double Y[100];
                                      
double min =PL[ArrayMinimum(PL)];                   

if(min>=0){Comment("f=1");return;}
min =MathAbs(min);

int n = ArraySize(X);
double maxX[1]= {0};
double maxY[1] ={0};

for(int j=0;j<n;j++)
{
   double k =X[j];
   double profit =1;
   for(int i=0;i<ArraySize(PL);i++)
   {
     profit =profit*(1+k*PL[i]/min);
   }
   Y[j] =profit;
   if(maxY[0]<profit)
   {
      maxY[0] =profit;
      maxX[0] =k;
   }
}  
CGraphic Graphic;
Graphic.Create(0,"Graphic",0,30,30,630,330);
CCurve *Curve=Graphic.CurveAdd(X,Y,ColorToARGB(clrBlue,255),CURVE_LINES,"Profit");
Curve.LinesStyle(STYLE_DOT);

//При желании график можно сглаживать
/*Curve.LinesSmooth(true);
Curve.LinesSmoothTension(0.8);
Curve.LinesSmoothStep(0.2);*/

CCurve *MAX =Graphic.CurveAdd(maxX,maxY,ColorToARGB(clrBlue,255),CURVE_POINTS,"Maximum"); MAX.PointsSize(8); MAX.PointsFill(true); MAX.PointsColor(ColorToARGB(clrRed,255)); Graphic.XAxis().MaxLabels(100); Graphic.TextAdd(30,30,"Text",255);   Graphic.CurvePlotAll(); Graphic.Update(); Print("Max factor f =   ", maxX[0]);     }


График для  {+2,-1} имеет вид:

Prifit


Из  полученного графика видно, что правило "чем больше риск, тем больше прибыль" неверно. Во всех случаях, когда кривая лежит ниже 1 (f>0.5), мы в конечном итоге получим убыток, а при бесконечной игре — 0 на счету.

Здесь есть одно интересное противоречие. Чем выше математическое ожидание прибыли и стабильнее система, тем больше коэффициент f. Например, для потока {-1,1,1,1,1,1,1,1,1,1} коэффициент равен 0.8, и о такой системе, казалось бы, можно только мечтать. Но коэффициент 0.8 означает,что максимальный допустимый убыток равен 80%, и в один прекрасный день вы потеряете 80% счёта! Да, с точки зрения математической статистики это оптимальный размер лота для максимального увеличения баланса, но готовы ли вы к таким потерям?


Немного о диверсификации

Допустим, у нас есть две торговые стратегии А и Б с одинаковым распределением прибылей/убытков, например те же (+2,-1). Для этих систем оптимальное f равно 0.25. Рассмотрим случаи, когда системы имеют корреляцию 1,0 и -1. Баланс счёта будем просто делить поровну между этими системами.

Корреляция 1, f=0.25

Система А Сделка P&L   Система Б Сделка P&L   Комбинированный счёт
   50      50    100
2 25   2 25   150
-1 -18.75   -1 -18.75   112.5
2 28.13   2 28.13   168.75
-1 -21.09   -1 -21.09   126.56
             прибыль 26.56

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

Корреляция 0, f=0.25

Система А Сделка P&L Система Б Сделка P&L Комбинированный счёт
   50    50  100
2 25 2 25 150
2 37.5 -1 -18.75 168.75
-1 -21.1 2 42.19 189.85
-1 -23.73 -1 -23.73 142.39




Прибыль 42.39

Прибыль значительно больше. И, наконец, случай корреляции -1.

Корреляция -1, f=0.25

Система А Сделка P&L Система Б Сделка P&L Комбинированный счёт
   50    50  100
2 25 -1 -12.5 112.5
-1 -14.08 2 28.12 126.56
2 31.64 -1 -15 142.38
-1 17.8 2 35.59 160.18




Прибыль 60.18

В этом случае прибыль максимальна. На этих и подобных примерах можно увидеть, что в случае реинвестирования прибыли диверсификация даёт лучшие результаты. Но также легко заметить, что она не избавляет от наихудшего случая (в нашем примере наибольший убыток f=0.25 от размера баланса), за исключением варианта, когда системы имеют корреляцию -1. На практике таких систем с корреляцией ровно -1 не бывает. Это аналогично случаю открытия позиций по одному инструменту в разные стороны. На основании подобных рассуждений Винс приходит к следующему выводу. Приведу дословную цитату из книги.

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


Корреляция и другая статистика

Прежде чем перейти к параметрическим методам нахождения коэффициента f, рассмотрим еще несколько характеристик потока прибылей. Может случиться так, что мы получаем серию взаимосвязанных результатов. За прибыльными сделками следуют прибыльные, а за убыточными — убыточные. Для выявления подобных зависимостей рассмотрим два метода: нахождения автокорреляции ряда и серийный тест.

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

Z=(N(R-0.5)-Х)/((Х(Х-N))/(N-1))^(1/2)

илиФормула

где:

  • N — общее число сделок;
  • R — общее число серий;
  • X=2*W*L, где
  • W = общее число выигрышных сделок в последовательности;
  • L = общее число проигрышных сделок в последовательности.

Программа 3. Счёт Z.

double Z(double &arr[])
{
int n =ArraySize(arr);
int W,L,X,R=1;
   if(arr[0]>0)
   {
      W=1;
      L=0;
   }
   else
   {
      W=0;
      L=1;
   }

   for(int i=1;i<n;i++)
   {
    if(arr[i]>0)
      {
      W++;
      if(arr[i-1]<=0){R++;}
      }
    else
      {
      L++;
      if(arr[i-1]>0){R++;}
      }    
   }
 X =2*W*L;
 double x=(n*(R-0.5)-X);
 double y =X*(X-n);
 y=y/(n-1);
 double Z=(n*(R-0.5)-X)/pow(y,0.5);  
Print(Z);
return Z;
}

Счет Z рассчитывается Тестером Стратегий, где в отчете (Бэктест) он так и называется "Z-Счёт".

Автокорреляция — статическая взаимосвязь между последовательностями величин одного ряда, взятыми со сдвигом. Для ряда {1,2,3,4,5,6,7,8,9,10}, это корреляция между рядами {1,2,3,4,5,6,7,8,9} и {2,3,4,5,6,7,8,9,10}. Ниже рассмотрена программа для нахождения автокорреляции.

Программа 4. Автокорреляция.

double AutoCorr(double &arr[])
{
   int n =ArraySize(arr);
   
   double avr0 =0;
   for(int i=0;i<n-1;i++)
   {
   avr0=avr0+arr[i];
   }
   avr0=avr0/(n-1);
   
   double avr1 =0;
   
   for(int i=1;i<n;i++)
   {
   avr1=avr1+arr[i];
   }
   avr1=avr1/(n-1);
   
   double D0 =0;
   double sum =0.0;
   
   for(int i=0;i<n-1;i++)
   {
   sum =sum+(arr[i]-avr0)*(arr[i]-avr0);
   }
   D0 =MathSqrt(sum);
   
   double D1 =0;
   sum =0.0;
   for(int i=1;i<n;i++)
   {
   sum =sum+(arr[i]-avr1)*(arr[i]-avr1);
   }
   D1 =MathSqrt(sum);
   
   sum =0.0;
   for(int i=0;i<n-1;i++)
   {
   sum =sum +(arr[i]-avr0)*(arr[i+1]-avr1);
   }
   if(D0==0||D1==0) return 1;
   double k=sum/(D0*D1);
return k;
}

Если результаты сделок взаимосвязаны, то торговую стратегию имеет смысл скорректировать. Лучшие результаты получатся, если мы будем использовать два разных коэффициента — f1 и f2 — для прибылей и убытков. Для этого случая будет написан отдельный модуль управления капиталом на MQL5.

Параметрические методы

Оптимизируя параметры системы, мы можем использовать два подхода. Первый — эмпирический, основанный непосредственно на экспериментальных данных, когда мы оптимизируем параметры под конкретные результаты. И второй — параметрический, основанный на функциональных или статических зависимостях. Пример параметрического метода — нахождение оптимального коэффициента из формулы Келли. 

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

Задача формулируется так. Допустим, наши прибыли/убытки распределены в соответствии с нормальным (или — в общем случае — с любым другим) распределением. Найдем оптимальный коэффициент f для этого распределения. В случае нормального распределения нам из экспериментальных данных достаточно найти среднее значение потока PL(profit/loss) и стандартное отклонение. Эти два параметра полностью характеризуют нормальное распределение.

Напомню формулу плотности нормального распределения:

Плотность

где 

  • σ — стандартное отклонение
  • m — математическое ожидание (среднее значение).

Сама идея мне понравилась. С помощью эмпирических данных найти характер распределения прибылей/убытков. И уже по этой функции, к которой стремятся результаты, находить параметр f, тем самым избегая влияния случайных значений. К сожалению, на практике всё не так просто. Но всё по порядку. Сначала о самом методе.

Распределение

 

На графике синим цветом построен график плотности нормального распределения, где среднее значение равно нулю, а стандартное отклонение — единице. Красным цветом отображается интеграл этой функции. Это кумулятивная вероятность, то есть вероятность того, что значение меньше или равно заданному Х. Её принято обозначать F(x). Оранжевый график — вероятность того, что значение меньше или равно х при х<0 и что значение больше или равно х при х>0 (F(x)' =1-F(x), при х>0. Все эти функции хорошо известны и их значения легко получить.

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

Сначала находим характеристики распределения — среднее значение и стандартное отклонение. Затем выбираем "доверительный интервал" или ширину отсечения, который выражается в стандартных отклонениях. Обычно выбирают интервал 3σ. Значения больше 3σ отсекаются. Затем данный интервал разбивается на отрезки и находятся "ассоциированные значения" прибылей/убытков (PL). Например, для  σ=1 и m=0 значение ассоциированных PL на краях интервала будут m +- 3σ = +3 и -3. Если мы разбили интервал на отрезки длиной 0.1σ, то ассоциированные PL будут -3, -2.9, -2.8 ... 0 ... 2.8, 2,9, 3. И именно для этого потока PL мы находим оптимальное f.

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

HPR=(1+PL*f/maxLoss)^Pгде maxLoss — максимальный убыток (по модулю).

Здесь Винс предлагает в качестве ассоциированной вероятности брать кумулятивную вероятность, которая у нас на графике показана оранжевым цветом F'(x).

Было бы логично, если бы кумулятивная вероятность бралась только для крайних значений, а для остальных значений P=F'(x)-F'(y), где х и у — значения F(x) на краях интервала.

Вероятности

 

Тогда множитель HPR=(1+PL*f/maxLoss)^P был бы своеобразным "взвешенным значением". Суммарная вероятность этих значений, как и положено, была бы равна единице. В книге Винс признаёт, что полученные таким образом результаты не совпадают с результатами, полученными на фактических данных. Он относит это к ограниченности выборки и отличию фактического распределения от нормального. Утверждается, что при увеличении числа элементов и распределении их по нормальному закону параметрические и фактические значения оптимального коэффициента f будут сходиться.

Интересно, что в разобранном по его методу примере суммарная вероятность оказывается равной 7.9. Ччтобы найти среднее геометрическое, он просто извлекает из результата корень 7.9-ой степени. По всей видимости, для такого подхода есть строгое математическое обоснование.

Мы же, имея в своём распоряжении такой инструмент, как MQL5, можем всё это легко проверить. Для этого у нас есть библиотека Normal.mqh, которая находится по адресу <Math\Stat\Normal.mqh>.

Для экспериментов я сделал два варианта: в точности как у Винса и описанный выше. Для нахождения "ассоциированных вероятностей" используется библиотечная функция MathCumulativeDistributionNormal(PL,mean,stand,ProbCum).

 Программа 5. Поиск оптимального f по нормальному распределению (по Винсу).

//+------------------------------------------------------------------+
//|                                                        Vince.mq5 |
//|                        Copyright 2017, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include<Math\Stat\Math.mqh>
#include<Math\Stat\Normal.mqh>

input double N=3;                      // интервал отсечения в стандартных отклонениях
input int M=60;                        // количество отрезков разбиения
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
double arr[10000];  
bool ch =MathRandomNormal(1,8,10000,arr);
double mean =MathMean(arr);
double stand =MathStandardDeviation(arr);

double PL[];                      // массив "ассоциированных прибылей"
ArrayResize(PL,M+1);
                                  // Заполнение массива
for(int i=0;i<M+1;i++)
   {
   double nn =-N+2.0*i*N/M;
   PL[i] =stand*nn+mean;
   }
//............................. массив "ассоциированных вероятностей"
double ProbCum[];
ArrayResize(ProbCum,M+1);
//............................. Заполнение массива ..................
ch =MathCumulativeDistributionNormal(PL,mean,stand,ProbCum);
//F'(x)= 1-F(x) при х>0

for(int i=0,j=0;i<M+1;i++)
{
if(i<=M/2)continue;
else j=M-i;
ProbCum[i] =ProbCum[j];
}

double SumProb=0;
for(int i=0;i<M+1;i++)
{
SumProb =SumProb+ProbCum[i];
}
Print("SumProb ",SumProb);
double MinPL =PL[ArrayMinimum(PL)];
double min =arr[ArrayMinimum(arr)];

double f=0.01,HPR=1,profit=1; 
double MaxProfit=1,MaxF=0;


for(int k=0;k<1000;k++)
   {
   f=k*0.001;
   profit =1;  
      for(int i=0;i<M+1;i++)
      {
      HPR=pow((1-PL[i]/MinPL*f),ProbCum[i]);
      profit =HPR*profit;
      }
   if(MaxProfit<profit)
     {
     MaxF =f;
     MaxProfit =profit;
     } 
   }
Print("Profit Vince");   
Print(MaxF,"   ",pow(MaxProfit,1/SumProb),"  ",Profit(MaxF,min,arr));
//... Для сравнения найдем максимальную прибыль по фактическим данным
MaxF =0;
MaxProfit =1;

for(int k=0;k<1000;k++)
   {
   f=k*0.001;
   profit =Profit(f,min,arr);  

   if(MaxProfit<profit)
     {
     MaxF =f;
     MaxProfit =profit;
     } 
   }
Print("------MaxProfit-------");   
Print(MaxF,"   ",MaxProfit);

  }

//   Программа нахождения прибыли по фактическим данным 
//   массива arr[] с минимальным значением min
//   и заданным значением f

double Profit(double f,double min, double &arr[])
{
if(min>=0)
{
   return 1.0;
   Alert("min>=0");
}

double profit =1;
int n =ArraySize(arr);
   for(int i=0;i<n;i++)
   {
   profit =profit*(1-arr[i]*f/min);
   }
return profit;
}

Код программы находится в файле Vince.mq5

В этой программе находится коэффициент из нормального распределения и затем — для сравнения — по фактическим данным. Второй вариант отличается только массивом "ассоциированных" вероятностей и PL.

Программа 6. 

.............................................
double ProbDiff[];
ArrayResize(ProbDiff,M+2);
double PLMean[];
ArrayResize(PLMean,M+2);

ProbDiff[0]=ProbCum[0];
ProbDiff[M+1]=ProbCum[M];
PLMean[0]=PL[0];
PLMean[M+1]=PL[M];

for(int i=1;i<M+1;i++)
{
ProbDiff[i] =MathAbs(ProbCum[i]-ProbCum[i-1]);
PLMean[i] =(PL[i]+PL[i-1])/2;
}
..............................................

Код программы находится в файле Vince_2.mq5

Здесь PLMean[i] =(PL[i]+PL[i-1])/2; среднее значение PL на отрезке разбиения, ProbDiff[] — значения вероятности того, что величина находится в заданном интервале. По краям значения отсекаются (возможно стоп-лоссом или тейк-профитом), поэтому вероятность на краях просто равна кумулятивной вероятности.

Обе программы работают примерно одинаково и выдают примерно одинаковые результаты. Оказалось, что ответ сильно зависит от N — ширины отсечения ("доверительного интервала"). Причём, что самое печальное, при увеличении N коэффициент f, полученный из нормального распределения, стремится к 1. Теоретически, чем шире интервал "отсечения", тем точнее должен получиться результат. На практике это не так.

Так может получаться из-за накапливающейся ошибки. Экспоненциальная функция довольно быстро убывает, и нам приходится иметь дело с довольно малыми величинами — HPR=pow((1-PL[i]/MinPL*f),ProbCum[i]). Возможно, что и сама методика где-то содержит ошибку. Но для практического применения это не принципиально. В любом случае, для корректной работы нам нужно каким-то образом "подгонять" параметр N, который сильно влияет на результат.

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

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

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

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

Краткий обзор книги Винса

Настало время кратко резюмировать работу Винса "Математика управления капиталом". Книга представляет собой смесь обзора методов статистики с  различными методами поиска оптимального коэффициента f. В ней рассматривается довольно обширный набор тем: модель Марковица управления портфелем, тест Колмогорова-Смирнова для распределений, модель оценки фондовых опционов Блэка-Шоулса и даже методы решения систем уравнений. Всё это выходит далеко за рамки отдельной статьи. Но главное — все эти методы рассматриваются в контексте поиска оптимального коэффициента f. Поэтому я решил на них не останавливаться, а перейти к практической реализации данного метода. Реализация будет в виде модулей для мастера MQL5.


Модуль мастера MQL5

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

Для начала создадим в директории Include/Expert новую папку для своих модулей — например, MyMoney. В ней создадим файл MoneyF1.mql.

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

Класс, как правило, содержит:

  • конструктор;
  • деструктор;
  • функции установки входных параметров;
  • функцию проверки введённых значений параметров ValidationSettings(void);
  • методы определения объема позиции CheckOpenLong(double price,double sl) и CheckOpenShort(double price,double sl).

Назовем наш класс CMoneyFactor

class CMoneyFactor : public CExpertMoney
  {
protected:
   //--- input parameters
   double            m_factor;          // коэффициент максимального проигрыша f
   double            m_max_loss;        // максимальный убыток в пунктах

public:
                     CMoneyFactor(void);
                    ~CMoneyFactor(void);
   //---
   void              Factor(double f)       { m_factor=f;}       
   void              MaxLoss(double point)  { m_max_loss=point;}         
   virtual bool      ValidationSettings(void);
   //---
   virtual double    CheckOpenLong(double price,double sl);               
   virtual double    CheckOpenShort(double price,double sl);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
void CMoneyFactor::CMoneyFactor(void) : m_factor(0.1),
                                        m_max_loss(100)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
void CMoneyFactor::~CMoneyFactor(void)
  {
  }

Максимальный проигрыш в пунктах задан типом double для соответствия стандартным модулям. Связано это с тем, что в других модулях, поставляемых вместе с дистрибутивом, стоп-лосс и тейк-профит задаются в пунктах определяемыми в базовом классе ExpertBase.mqh

   ExpertBase.mqh
   int digits_adjust=(m_symbol.Digits()==3 || m_symbol.Digits()==5) ? 10 : 1;
   m_adjusted_point=m_symbol.Point()*digits_adjust;

То есть для котировок с пятью и тремя знаками после запятой один пункт равен 10*Point(). 105 пунктов в смысле Point() равны 10.5 пунктам в стандартных модулях для MQL5.

Функции Factor(double f) и MaxLoss(double point) устанавливают входные параметры и должны называться так же, как затем будут описаны в дескрипторе модуля.

Функция проверки корректности введённых параметров:

bool CMoneyFactor::ValidationSettings(void)
  {
   if(!CExpertMoney::ValidationSettings())
   return(false);
//--- initial data checks  
   if(m_factor<0||m_factor>1)
   {
   Print(__FUNCTION__+"Размер коэффициента должен быть в пределах от 0 до 1");
   return false;   
   }
 
  return true;
  }

Здесь мы проверяем, чтобы значение коэффициента было от 0 до 1.

И наконец, сами функции определения объёма позиции. Для открытия в "лонг":

double CMoneyFactor::CheckOpenLong(double price,double sl)
{
   if(m_symbol==NULL)
      return(0.0);
//--- Определение размера лота
   double lot;
   
/*   
      ExpertBase.mqh
      int digits_adjust=(m_symbol.Digits()==3 || m_symbol.Digits()==5) ? 10 : 1;
      m_adjusted_point=m_symbol.Point()*digits_adjust;
*/
    double loss;
    if(price==0.0)price =m_symbol.Ask();
    loss=-m_account.OrderProfitCheck(m_symbol.Name(),ORDER_TYPE_BUY,1.0,price,price - m_max_loss*m_adjusted_point);
    double stepvol=m_symbol.LotsStep();
    lot=MathFloor(m_account.Balance()*m_factor/loss/stepvol)*stepvol;
   
   double minvol=m_symbol.LotsMin();
//---проверка минимального лота
   if(lot<minvol)
      lot=minvol;
//---проверка максимального лота
   double maxvol=m_symbol.LotsMax();
   if(lot>maxvol)
      lot=maxvol;
//--- return trading volume
   return(lot);

}

Здесь максимальный убыток находится с помощью библиотечного метода класса CAccountInf - OrderProfitCheck(). Затем добавлена проверка лота на его соответствия разрешенным предельным значениям — минимальному и максимальному.

В начале каждого модуля идёт его описание (дескриптор), необходимое компилятору для его распознавания.

// wizard description start
//+------------------------------------------------------------------+
//| Description of the class                                         |
//| Title=Торговля с оптимальным коэффициентом f                     |
//| Type=Money                                                       |
//| Name=FixedPart                                                   |
//| Class=CMoneyFactor                                               |
//| Page= ?                                                          |
//| Parameter=Factor,double,0.1,Оптимальная фиксированная доля       |
//| Parameter=MaxLoss,double,50,Максимальный убыток в пунктах        |
//+------------------------------------------------------------------+
// wizard description end

Для экспериментов этот модуль можно скомпилировать с любым имеющимся модулем торговых сигналов. Предварительно можно выбранный модуль торговых сигналов скомпилировать с модулем управления капиталом с фиксированным лотом. Полученные результаты используем для нахождения максимального убытка и потока PL. Потом по этим результатам найдем оптимальный фактор f с помощь Программы 1. Таким образом по экспериментальным данным можно найти оптимальный f. Другой способ — найти оптимальный f непосредственно из полученного советника на основе нашего модуля путём его оптимизации. У меня результаты расходятся лишь на +/- 0.01. Это связано с погрешностью вычислений, например, из-за округлений.

Код модуля находится в файле MoneyF1.mqh.

Может случиться так, что поток наших прибылей/убытков имеет значимую автокорреляцию. Это можно выяснить с помощью приведённых ранее программ расчёта "счёта Z" и автокорреляции. Тогда имеет смысл задать два коэффициента — f1 и f2. Первый применяется после прибыльных сделок, второй — после убыточных. Напишем для данной стратегии второй модуль управления капиталом. Коэффициенты затем можно находить с помощью оптимизации, а можно — непосредственно по данным потока прибылей/убытков для той же стратегии с фиксированным лотом.

Программа 7. Определения оптимальных f1 и f2 по потоку прибыли/убытка.

void OptimumF1F2(double &arr[])
{
double f1,f2;
double profit=1;
double MaxProfit =0;
double MaxF1 =0,MaxF2 =0;
double min =MathAbs(arr[ArrayMinimum(arr)]);

   for(int i=1;i<=100;i++)
   {
   f1 =i*0.01;
      for(int j=1;j<=100;i++)
      {
         f2 =j*0.01;
         profit =profit*(1+f1*arr[0]/min);
            for(int n=1;n<ArraySize(arr);n++)
            {
            if(arr[n-1]>0){profit =profit*(1+f1*arr[n]/min);}
            else{profit =profit*(1+f2*arr[n]/min);}
            }
         if(MaxProfit<profit)
         {
         MaxProfit=profit;
         MaxF1 =i;MaxF2 =j;
         }   
      }
   }

Соответственно, для мастера MQL5 нужно переделать основные функции модуля управления капиталом. Во-первых, добавим еще один параметр — f2 — и его проверку. Во вторых, переделаем функции CheckOpenLong() и  CheckOpenShort(). Для определения финансового результата предыдущей сделки добавим функцию CheckLoss().

//+------------------------------------------------------------------+
//| Проверяет результат предыдущей сделки                            |
//+------------------------------------------------------------------+
double CMoneyTwoFact:: CheckLoss()
  {
double lot=0.0;
HistorySelect(0,TimeCurrent());

int deals=HistoryDealsTotal();           // количество сделок в истории
CDealInfo deal;
//--- поиск предущей сделки
if(deals==1) return 1;
   for(int i=deals-1;i>=0;i--)
   {
   if(!deal.SelectByIndex(i))
      {
      printf(__FUNCTION__+": ошибка выбора сделки по индексу");
      break;
      }
//--- отбор сделок по символу или любой другой
      if(deal.Symbol()!=m_symbol.Name()) continue;
//--- возвращаем результат сделки
    lot=deal.Profit();
    break;
   }

   return(lot);
  }

Функции CheckOpenLong()  CheckOpenShort():

double CMoneyTwoFact::CheckOpenLong(double price,double sl)
  {
   double lot=0.0;
   double p=CheckLoss();
   
/*   
      ExpertBase.mqh
      int digits_adjust=(m_symbol.Digits()==3 || m_symbol.Digits()==5) ? 10 : 1;
      m_adjusted_point=m_symbol.Point()*digits_adjust;
*/
   
   double loss;
   
   if(price==0.0)price =m_symbol.Ask();   
   if(p>0)
      {
      loss=-m_account.OrderProfitCheck(m_symbol.Name(),ORDER_TYPE_BUY,1.0,price,price - m_max_loss*m_adjusted_point);
      double stepvol=m_symbol.LotsStep();
      lot=MathFloor(m_account.Balance()*m_factor1/loss/stepvol)*stepvol;
      }  
   if(p<0)
      { 
      loss=-m_account.OrderProfitCheck(m_symbol.Name(),ORDER_TYPE_BUY,1.0,price,price - m_max_loss*m_adjusted_point);
      double stepvol=m_symbol.LotsStep();
      lot=MathFloor(m_account.Balance()*m_factor2/loss/stepvol)*stepvol;
      }


  return(lot);   
  }
//+------------------------------------------------------------------+

double CMoneyTwoFact::CheckOpenShort(double price,double sl)
  {
  double lot=0.0;
  double p=CheckLoss();
/*   int digits_adjust=(m_symbol.Digits()==3 || m_symbol.Digits()==5) ? 10 : 1;
   m_adjusted_point=m_symbol.Point()*digits_adjust;*/
   
   double loss;
   
   if(price==0.0)price =m_symbol.Ask();   
   if(p>0)
      {
      loss=-m_account.OrderProfitCheck(m_symbol.Name(),ORDER_TYPE_SELL,1.0,price,price+m_max_loss*m_adjusted_point);
      double stepvol=m_symbol.LotsStep();
      lot=MathFloor(m_account.Balance()*m_factor1/loss/stepvol)*stepvol;
      }  
   if(p<0)
      { 
      loss=-m_account.OrderProfitCheck(m_symbol.Name(),ORDER_TYPE_SELL,1.0,price,price+m_max_loss*m_adjusted_point);
      double stepvol=m_symbol.LotsStep();
      lot=MathFloor(m_account.Balance()*m_factor2/loss/stepvol)*stepvol;
      }
  return(lot);     
  }

Полный код модуля находится в файле MoneyF1F2.mqh.

Как упоминалось выше, в целом концепция управления капиталом по Винсу вертится вокруг оптимального коэффициента f. Поэтому для примера двумя модулями вполне можно ограничиться. Хотя можно изобрести и какие-либо дополнительные вариации. Например, добавить элементы мартингейла.


Прикреплённые файлы

В файле Programs.mq5 находятся коды программ, использованных в статье. Добавлена сюда и программа для чтения данных из файла void ReadFile(string file,double &arr[]). Она нужна для нахождения коэффициентов f по потоку прибылей/убытков из тестера стратегий. Можно, конечно, подойти к делу обстоятельно и написать целый класс для парсинга отчётов, как это сделано в статье "Раскладываем входы по индикаторам". Но это целая отдельная программа со своими классами.

На мой взгляд, проще сделать так. Прогоняем стратегию с фиксированным лотом в тестере стратегий. Сохраняем отчёт тестера в виде Open XML (MS Office Excel). К колонке "прибыль" добавляем "своп" и "комиссия" — получаем поток прибыли PL. Отдельно эту колонку сохраняем в текстовый файл или файл csv. Получаем набор строк, состоящих из отдельных результатов каждой сделки. Эти результаты добавленная функция ReadFile() читает в массив arr[]. Таким несложным путем мы можем найти оптимальный f по данным любой стратегии с фиксированным лотом.

В файлах Vince.mq5 и Vince_2.mq5 находятся исходники параметрических методов нахождения оптимальных коэффициентов, рассмотренные в статье.

Файлы MoneyF1.mqh и MoneyF1F2.mqh исходники торговых модули управления капиталом.

Архивированный файл содержит все эти файлы, структурированные в соответствии с их расположением в MetaEditor.

Прикрепленные файлы |
Programs.mq5 (8.42 KB)
Vince.mq5 (5.95 KB)
Vince_2.mq5 (6.27 KB)
MoneyF1.mqh (9.67 KB)
MoneyF1F2.mqh (12.93 KB)
MQL5.zip (9.29 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
MrShuM
MrShuM | 18 янв. 2018 в 15:51

Восхищён, невероятно хорошо проделанная работа. Благодарю за труды.

Rostislav Voitsehovsky
Rostislav Voitsehovsky | 21 янв. 2018 в 18:00
Работа сюдя по всему титаническая, а как юзать чайникам? Перенес файлы мт5, а дальше? Может можно мануал сделать?
Yury Kirillov
Yury Kirillov | 22 янв. 2018 в 15:01

Спасибо! Хорошая проработка одной из лучших книг!

pivomoe
pivomoe | 3 февр. 2018 в 00:10

Основные положения

Для наглядности рассмотрим основные идеи на примерах. Допустим, у нас есть некоторая условная система из двух сделок. Первая сделка выигрывает 50%, а вторая проигрывает 40%. Если мы не реинвестируем прибыль, то выигрываем 10%, а если реинвестируем — та же последовательность сделок дает проигрыш 10%. (P&L=Profit or Loss).


При реинвестировании прибыли выигрышная система превратилась в проигрышную.


Не возможно превратить минусовую систему в плюсовую с помощью ММ. Но верно и обратное, плюсовую систему в минусовую нельзя   превратить с помощью ММ .

В этом примере автор не учитывает ещё два варианта:

1.  Обе сделки идут в плюс. т.е прибыль равна ( 100*1.5*1.5 - 100 ) = 125.

2. Обе сделки идут в минус т.е прибыль равна ( 100*0,6*0,6-100)=-64.

В общем плюсовая система так и осталось плюсовой. 

Автоматическое построение линий поддержки и сопротивления Автоматическое построение линий поддержки и сопротивления
В статье рассматривается автоматическое построение линий поддержки и сопротивления через локальные максимумы и минимумы ценовых графиков. Для определения этих экстремумов применяется всем известный индикатор ZigZag.
Тестирование паттернов, возникающих при торговле корзинами валютных пар. Часть III Тестирование паттернов, возникающих при торговле корзинами валютных пар. Часть III
Мы заканчиваем тестирование паттернов, которые можно увидеть при торговле корзинами пар. В статье представлены результаты тестирования паттернов, отслеживающих движение валют пары по отношению друг к другу.
LifeHack для трейдера: готовим фастфуд из индикаторов LifeHack для трейдера: готовим фастфуд из индикаторов
Если вы переходите на MQL5 только сейчас, то эта статья вам пригодится: с одной стороны, доступ к данным индикаторов и к сериям выполнен в привычном вам MQL4-стиле, с другой — вся реализация этой простоты написана на MQL5. Все функции максимально понятны и отлично подходят для пошаговой отладки.
Паттерн прорыва канала Паттерн прорыва канала
Как известно, ценовые тренды образуют ценовые каналы. Один из сильных сигналов на изменение тренда — прорыв текущего канала. В этой статье я предлагаю попробовать автоматизировать процесс поиска таких сигналов и посмотреть, действительно ли можно на этом построить свою стратегию торговли.