Возможности Matlab для разработки и тестирования механистических торговых систем

в 15:44, , рубрики: forex, Matlab, metatrader, математика, Программирование, тестирование стратегий, торговля на бирже, торговые роботы

Думаю, обосновывать необходимость тщательного тестирования и подбора параметров торговых стратегий нет необходимости… Лучше поясню, почему именно Matlab.

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

image
image

На мой взгляд, эта система тестирования обладает рядом существенных недостатков:
1) Нельзя использовать реальные тиковые данные, можно их только моделировать по хранящимся на сервере данным минутных, 5-минутных, 15-минутных и т.д. таймфреймов;
2) Для подбора оптимальных параметров стратегии достаточно скудный набор доступных оптимизационных процедур;
3) Чуть более разнообразный, но всё же недостаточный набор целевых показателей торговой стратегии, которые можно оптимизировать;
4) Сама среда разработки торговых стратегий подходит для самого минимального программирования, не впечатляет разнообразием возможностей и доступного инструментария.

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

Всё это возможно при использовании Matlab в качестве платформы разработки и тестирования стратегий. От MetaTrader нам понадобятся исходные данные (в моём примере: ежесекундные текущие цены по 27-ми валютным парам). Потом MetaTrader всё-таки будет использоваться для работы с готовой торговой стратегией, реализованной, например, в виде DLL, но вся самая творческая часть создания системы произойдёт в более математической среде.

Данные собираются пользовательским индикатором, код которого на MQL4 выглядит так:

//+------------------------------------------------------------------+
//|                                               pack_collector.mq4 |
//|                                         Copyright 2014, JamaGava |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, JamaGava"
#property link      ""
#property version   "1.00"
#property strict

int filehandleData;
int filehandleHead;
int filehandleVolHead;
int filehandleVolData;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   EventSetTimer(1);
   filehandleData = FileOpen("tickPack.csv",FILE_WRITE|FILE_CSV);
   filehandleHead = FileOpen("tickPackHead.csv",FILE_WRITE|FILE_CSV);
   filehandleVolHead = FileOpen("tickPackVolumeHead.csv",FILE_WRITE|FILE_CSV);
   filehandleVolData = FileOpen("tickPackVolume.csv",FILE_WRITE|FILE_CSV);
   
   FileWrite(filehandleHead, 
   
              "bid:AUD/USD", "ask:AUD/USD",
              "bid:EUR/USD", "ask:EUR/USD",
              "bid:GBP/USD", "ask:GBP/USD",
              "bid:NZD/USD", "ask:NZD/USD",
              "bid:USD/CAD", "ask:USD/CAD",
              "bid:USD/CHF", "ask:USD/CHF",
              "bid:USD/JPY", "ask:USD/JPY",
              "bid:AUD/CAD", "ask:AUD/CAD",
              "bid:AUD/CHF", "ask:AUD/CHF",
              "bid:AUD/NZD", "ask:AUD/NZD",
              "bid:CAD/CHF", "ask:CAD/CHF",
              "bid:EUR/AUD", "ask:EUR/AUD",
              "bid:EUR/CAD", "ask:EUR/CAD",
              "bid:EUR/CHF", "ask:EUR/CHF",
              "bid:EUR/GBP", "ask:EUR/GBP",
              "bid:EUR/NZD", "ask:EUR/NZD",
              "bid:GBP/AUD", "ask:GBP/AUD",
              "bid:GBP/CAD", "ask:GBP/CAD",
              "bid:GBP/CHF", "ask:GBP/CHF",
              "bid:GBP/NZD", "ask:GBP/NZD",
              "bid:NZD/CAD", "ask:NZD/CAD",
              "bid:NZD/CHF", "ask:NZD/CHF",
              "bid:AUD/JPY", "ask:AUD/JPY",
              "bid:CAD/JPY", "ask:CAD/JPY",
              "bid:CHF/JPY", "ask:CHF/JPY",
              "bid:EUR/JPY", "ask:EUR/JPY",
              "bid:GBP/JPY", "ask:GBP/JPY",
              "bid:NZD/JPY", "ask:NZD/JPY"
                          
             );
             
             
   FileWrite(filehandleVolHead, 
   
              "volume:AUD/USD",
              "volume:EUR/USD",
              "volume:GBP/USD",
              "volume:NZD/USD",
              "volume:USD/CAD",
              "volume:USD/CHF",
              "volume:USD/JPY",
              "volume:AUD/CAD",
              "volume:AUD/CHF",
              "volume:AUD/NZD",
              "volume:CAD/CHF",
              "volume:EUR/AUD",
              "volume:EUR/CAD",
              "volume:EUR/CHF",
              "volume:EUR/GBP",
              "volume:EUR/NZD",
              "volume:GBP/AUD",
              "volume:GBP/CAD",
              "volume:GBP/CHF",
              "volume:GBP/NZD",
              "volume:NZD/CAD",
              "volume:NZD/CHF",
              "volume:AUD/JPY",
              "volume:CAD/JPY",
              "volume:CHF/JPY",
              "volume:EUR/JPY",
              "volume:GBP/JPY",
              "volume:NZD/JPY"
                          
             );
   FileClose(filehandleHead);
   FileClose(filehandleVolHead);
   return(INIT_SUCCEEDED);
}
  
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   EventKillTimer();
   FileClose(filehandleData);
}

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
{
   FileWrite(filehandleData, 
              MarketInfo("AUDUSD", MODE_BID), MarketInfo("AUDUSD", MODE_ASK),
              MarketInfo("EURUSD", MODE_BID), MarketInfo("EURUSD", MODE_ASK),
              MarketInfo("GBPUSD", MODE_BID), MarketInfo("GBPUSD", MODE_ASK),
              MarketInfo("NZDUSD", MODE_BID), MarketInfo("NZDUSD", MODE_ASK),
              MarketInfo("USDCAD", MODE_BID), MarketInfo("USDCAD", MODE_ASK),
              MarketInfo("USDCHF", MODE_BID), MarketInfo("USDCHF", MODE_ASK),
              MarketInfo("USDJPY", MODE_BID), MarketInfo("USDJPY", MODE_ASK),
              MarketInfo("AUDCAD", MODE_BID), MarketInfo("AUDCAD", MODE_ASK),
              MarketInfo("AUDCHF", MODE_BID), MarketInfo("AUDCHF", MODE_ASK), 
              MarketInfo("AUDNZD", MODE_BID), MarketInfo("AUDNZD", MODE_ASK),
              MarketInfo("CADCHF", MODE_BID), MarketInfo("CADCHF", MODE_ASK),
              MarketInfo("EURAUD", MODE_BID), MarketInfo("EURAUD", MODE_ASK),
              MarketInfo("EURCAD", MODE_BID), MarketInfo("EURCAD", MODE_ASK),
              MarketInfo("EURCHF", MODE_BID), MarketInfo("EURCHF", MODE_ASK),
              MarketInfo("EURGBP", MODE_BID), MarketInfo("EURGBP", MODE_ASK),
              MarketInfo("EURNZD", MODE_BID), MarketInfo("EURNZD", MODE_ASK),
              MarketInfo("GBPAUD", MODE_BID), MarketInfo("GBPAUD", MODE_ASK),
              MarketInfo("GBPCAD", MODE_BID), MarketInfo("GBPCAD", MODE_ASK),
              MarketInfo("GBPCHF", MODE_BID), MarketInfo("GBPCHF", MODE_ASK),
              MarketInfo("GBPNZD", MODE_BID), MarketInfo("GBPNZD", MODE_ASK),
              MarketInfo("NZDCAD", MODE_BID), MarketInfo("NZDCAD", MODE_ASK),
              MarketInfo("NZDCHF", MODE_BID), MarketInfo("NZDCHF", MODE_ASK),
              MarketInfo("AUDJPY", MODE_BID), MarketInfo("AUDJPY", MODE_ASK),
              MarketInfo("CADJPY", MODE_BID), MarketInfo("CADJPY", MODE_ASK),
              MarketInfo("CHFJPY", MODE_BID), MarketInfo("CHFJPY", MODE_ASK),
              MarketInfo("EURJPY", MODE_BID), MarketInfo("EURJPY", MODE_ASK),
              MarketInfo("GBPJPY", MODE_BID), MarketInfo("GBPJPY", MODE_ASK),
              MarketInfo("NZDJPY", MODE_BID), MarketInfo("NZDJPY", MODE_ASK)
           );
           
   FileWrite(filehandleVolData, 
              iVolume("AUDUSD", PERIOD_H1,0),
              iVolume("EURUSD", PERIOD_H1,0),
              iVolume("GBPUSD", PERIOD_H1,0),
              iVolume("NZDUSD", PERIOD_H1,0),
              iVolume("USDCAD", PERIOD_H1,0),
              iVolume("USDCHF", PERIOD_H1,0),
              iVolume("USDJPY", PERIOD_H1,0),
              iVolume("AUDCAD", PERIOD_H1,0),
              iVolume("AUDCHF", PERIOD_H1,0), 
              iVolume("AUDNZD", PERIOD_H1,0),
              iVolume("CADCHF", PERIOD_H1,0),
              iVolume("EURAUD", PERIOD_H1,0),
              iVolume("EURCAD", PERIOD_H1,0),
              iVolume("EURCHF", PERIOD_H1,0),
              iVolume("EURGBP", PERIOD_H1,0),
              iVolume("EURNZD", PERIOD_H1,0),
              iVolume("GBPAUD", PERIOD_H1,0),
              iVolume("GBPCAD", PERIOD_H1,0),
              iVolume("GBPCHF", PERIOD_H1,0),
              iVolume("GBPNZD", PERIOD_H1,0),
              iVolume("NZDCAD", PERIOD_H1,0),
              iVolume("NZDCHF", PERIOD_H1,0),
              iVolume("AUDJPY", PERIOD_H1,0),
              iVolume("CADJPY", PERIOD_H1,0),
              iVolume("CHFJPY", PERIOD_H1,0),
              iVolume("EURJPY", PERIOD_H1,0),
              iVolume("GBPJPY", PERIOD_H1,0),
              iVolume("NZDJPY", PERIOD_H1,0)
           );


}
//+------------------------------------------------------------------+

Итак, данные собираются и сохраняются в файлах CSV. В строках записываются текущие котировки, в столбцах: весь ряд котировок bid и ask для каждой валютной пары. Итого, столбцов в 2 раза больше, чем рассматриваемых валютных пар.

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

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

Скрипт loadPrices.m:

global PRICES;
N = 16;

PRICES = cell( N, 1 );

fNames = {
        'C:matlabR2008a_winworkfrxDATA1tickPack.csv';
        'C:matlabR2008a_winworkfrxDATA2tickPack.csv';
        'C:matlabR2008a_winworkfrxDATA3tickPack.csv';
        'C:matlabR2008a_winworkfrxDATA4tickPack.csv';
        'C:matlabR2008a_winworkfrxDATA5tickPack.csv';
        'C:matlabR2008a_winworkfrxDATA6tickPack.csv';
        'C:matlabR2008a_winworkfrxDATA7tickPack.csv';
        'C:matlabR2008a_winworkfrxDATA8tickPack.csv';
        'C:matlabR2008a_winworkfrxDATA9tickPack.csv';
        'C:matlabR2008a_winworkfrxDATA10tickPack.csv';
        'C:matlabR2008a_winworkfrxDATA11tickPack.csv';
        'C:matlabR2008a_winworkfrxDATA12tickPack.csv';
        'C:matlabR2008a_winworkfrxDATA13tickPack.csv';
        'C:matlabR2008a_winworkfrxDATA14tickPack.csv';
        'C:matlabR2008a_winworkfrxDATA15tickPack.csv';
        'C:matlabR2008a_winworkfrxDATA16tickPack.csv';
    };


for i = 1:N
    PRICES{ i } = load( fNames{ i } );
    display( i );
end

В итоге все блоки данных сохранены в глобальной переменной PRICES, представляющей собой 16 ячеек.

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

Скрипт testStrat.m:

function [R] = testStrat( P )

global DrawDown;
global Recovery;
global Equity;
global DealsNumber;
global MeanDeal;
global DealsSigma;
global Sharp;
global ProfitFactor;
global ProfitDealsNumber;
global ProfitDealsRate;
global ProfitMean;
global LossDealsNumber;
global LossDealsRate;
global LossMean;

global returnParamIndex;

global stratName;


initRates( 0 );


feval( stratName, P );


switch( returnParamIndex )
    case 1
        R = DrawDown;
        
    case 2
        R = -Recovery;
        
    case 3
        R = -Equity;
        
    case 4
        R = -DealsNumber;
        
    case 5
        R = -MeanDeal;
        
    case 6
        R = DealsSigma;
        
    case 7
        R = -Sharp;
        
    case 8
        R = -ProfitFactor;
        
    case 9
        R = -ProfitDealsNumber;
        
    case 10
        R = -ProfitDealsRate;
        
    case 11
        R = -ProfitMean;
        
    case 12
        R = LossDealsNumber;
        
    case 13
        R = LossDealsRate;
        
    case 14
        R = LossMean;
        
    case 0
        R = struct(                                              ...
                'DrawDown',              DrawDown,               ...
                'Recovery',              Recovery,               ...
                'Equity',                Equity,                 ...
                'DealsNumber',           DealsNumber,            ...
                'MeanDeal',              MeanDeal,               ...
                'DealsSigma',            DealsSigma,             ...
                'Sharp',                 Sharp,                  ...
                'ProfitFactor',          ProfitFactor,           ...
                'ProfitDealsNumber',     ProfitDealsNumber,      ...
                'ProfitDealsRate',       ProfitDealsRate,        ...
                'ProfitMean',            ProfitMean,             ...
                'LossDealsNumber',       LossDealsNumber,        ...
                'LossDealsRate',         LossDealsRate,          ...
                'LossMean',              LossMean                ...
            );
        
end

Название скрипта, реализующего стратегию, передаётся через глобальную переменную stratName. Характеристика стратегии, которая будет возвращаться определяется переменной returnParamIndex, если значение 0, то возвращаются все характеристики в составе структуры.

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

Собственно, характеристики системы:
DrawDown — максимальная просадка (максимальная разность между предшествующим максимумом и текущим значением);
Recovery — фактор восстановления (отношение итогового выигрыша к максимальной просадке);
Equity — итоговый выигрыш;
DealsNumber — количество сделок;
MeanDeal — средний доход по сделке;
DealsSigma — стандартное отклонение доходов по каждой сделке от среднего значения;
Sharp — коэффициент Шарпа (отношение среднего дохода по сделке к стандартному отклонению доходов по сделкам от среднего значения);
ProfitFactor — профит фактор (отношение суммарной прибыли к суммарному убытку);
ProfitDealsNumber — число выигрышных сделок;
ProfitDealsRate — доля выигрышных сделок;
ProfitMean — средний выигрыш (за успешную сделку);
LossDealsNumber — число убыточных сделок;
LossDealsRate — доля убыточных сделок;
LossMean — средний проигрыш (за убыточную сделку).

Инициализируются эти показатели функцией initRates.m

function [z] = initRates( mode )

global DrawDown;
global Recovery;
global Equity;
global DealsNumber;
global MeanDeal;
global DealsSigma;
global Sharp;
global ProfitFactor;
global ProfitDealsNumber;
global ProfitDealsRate;
global ProfitMean;
global LossDealsNumber;
global LossDealsRate;
global LossMean;

global maxEquity;
global dealSqr;
global totalProfit;
global totalLoss;

z = mode;

maxEquity   = 0;
dealSqr     = 0;
totalProfit = 0;
totalLoss   = 0;


if mode == 0
    
    DrawDown              = 0;
    Recovery              = 0;
    Equity                = 0;
    DealsNumber           = 0;
    MeanDeal              = 0;
    DealsSigma            = 0;
    Sharp                 = 0;
    ProfitFactor          = 0;
    ProfitDealsNumber     = 0;
    ProfitDealsRate       = 0;
    ProfitMean            = 0;
    LossDealsNumber       = 0;
    LossDealsRate         = 0;
    LossMean              = 0;
    
else
    
    DrawDown              =  1000;
    Recovery              = -1000;
    Equity                = -1000;
    DealsNumber           =  0;
    MeanDeal              = -1000;
    DealsSigma            =  1000;
    Sharp                 = -1000;
    ProfitFactor          =  0;
    ProfitDealsNumber     =  0;
    ProfitDealsRate       =  0;
    ProfitMean            =  0;
    LossDealsNumber       =  100000;
    LossDealsRate         =  1000;
    LossMean              =  1000;
    
end

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

maxEquity, dealSqr, totalProfit, totalLoss,

которые используются в следующем листинге.

В ходе выполнения стратегии, при каждом закрытии сделки, будем обновлять характеристики работы стратегии. Для этого используем функцию updateStratRates.m:

function [z] = updateStratRates( dealResult )

z = sign( dealResult );

global DrawDown;
global Recovery;
global Equity;
global DealsNumber;
global MeanDeal;
global DealsSigma;
global Sharp;
global ProfitFactor;
global ProfitDealsNumber;
global ProfitDealsRate;
global ProfitMean;
global LossDealsNumber;
global LossDealsRate;
global LossMean;


global maxEquity;
global dealSqr;
global totalProfit;
global totalLoss;



Equity = Equity + dealResult;

if Equity > maxEquity
    maxEquity = Equity;
end

down = maxEquity - Equity;
if down > DrawDown
    DrawDown = down;
end

Recovery = Equity / DrawDown;


DealsNumber = DealsNumber + 1;
MeanDeal    = Equity / DealsNumber;
dealSqr     = dealSqr + dealResult^2;
DealsSigma  = sqrt( (dealSqr / DealsNumber) - MeanDeal^2 );
Sharp       = MeanDeal / DealsSigma;

if dealResult > 0
    ProfitDealsNumber = ProfitDealsNumber + 1;
    totalProfit       = totalProfit + dealResult;
    ProfitMean        = totalProfit / ProfitDealsNumber;
else
    LossDealsNumber = LossDealsNumber + 1;
    totalLoss       = totalLoss + dealResult;
    LossMean        = totalLoss / LossDealsNumber;
end

ProfitFactor    = -totalProfit / totalLoss;
ProfitDealsRate = ProfitDealsNumber / DealsNumber;
LossDealsRate   = 1 - ProfitDealsRate;

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

Исходные данные можно получить, например так:

global pairIndex;        % номер/номера интересующей валютной пары/пар
global DATA;             % блоки данных
global dataBlocksNumber; % количество блоков данных (в нашем примере 16)

Теперь подробнее о торговой системе, twoEMA, на примере которой которой покажу, как это всё работает.
Идея системы состоит в использовании двух экспоненциальных скользящих средних. Вход в рынок осуществляется при соблюдении следующих условий:

Покупка/продажа осуществляется, если выполняются все условия:
1) Быстрая скользящая средняя выше/ниже медленной не менее чем на определённое количество 4-х значных пунктов;
2) Цена открытия таймфрейма выше/ниже быстрой скользящей средней;
3) Нет текущих открытых сделок.

Закрытие при выполнении любого из условий:
1) Достижение установленного значения прибыли по текущей сделке;
2) Достижение установленного значения убытка по текущей сделке (tralling stop-loss по мере роста дохода по сделке он подтягивается к текущей цене, вплоть до достижения безубыточных значений);
3) Появление условий 1 и 2 на открытие сделки в противоположную сторону.

Входные параметры для сделки это: длина таймфрейма в секундах, параметры скользящих средних в единицах измерения 0.0001, «зазор» между скользящими средними для открытия сделки, значения фиксации прибыли и убытка в четырёхзначных пунктах.

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

Таким образом, каждая сделка может быть представлена стандартным «баром» вместо традиционного линейного графика Equity. Прибыль считается в 4-х значных пунктах.

twoEMA.m:

function [Z] = twoEma( P )

%{
+---------------------------------------------------------------+
!                                                               !
!  Parameters:                                                  !                                       
!    timeframe = P(1)                                           !
!    E1 - slow EMA with alpha = P(2)                            !
!    E2 - fast EMA with alpha = P(3)                            !
!                                                               !
!  Open:                                                        !
!    long deal:  (openPrice >= E2 > E1) & abs(E1 - E2) >= P(4)  !
!    short deal: (openPrice <= E2 < E1) & abs(E1 - E2) >= P(4)  !
!                                                               !
!  Close:                                                       !
!    by trailing Stop-loss or by Take profit                    !
!                                                               !
+---------------------------------------------------------------+
%}

global pairIndex;

global DATA;
global dataBlocksNumber;

global WithPlot;     % демонстрационный или отладочный режим
global EQ;           % бары Equity
global eqCounter;    % число баров


% проверка значений входных параметров на допустимость
if or( P( 1 ) < 0,                                                           ... 
     or( P( 4 ) * 0.0001 <= 0,                                               ...
       or( P( 5 ) * 0.0001 <= 0,                                             ...
         or( P( 6 ) * 0.0001 <= 0,                                           ... 
           or(                                                               ...
             or( P( 2 ) * 0.0001 <= 0,               P( 2 ) * 0.0001 >= 1 ), ...
             or( P( 3 ) * 0.0001 <= P( 2 ) * 0.0001, P( 3 ) * 0.0001 >= 1 )  ...
           )                                                                 ...
         )                                                                   ...
       )                                                                     ...
     )                                                                       ...
   )

    initRates( 1 ); % если значения входных параметров недопустимы, то характеристики стратегии
                        % инициализируем заведомо плохими значениями
    
    Z = 1;
    return;
    
end


initRates( 0 );    % инициализация значений параметров стратегии


    % инициализация значений бара текущей сделки
currEQopen    = 0;
currEQmin     = 0;
currEQmax     = 0;
currEQclose   = 0;

TF = round( P( 1 ) ); % длина таймфрейма

for currBlock = 1:dataBlocksNumber
    
        
    currData = DATA{ currBlock };
    dataSize = size( currData );
    dataLen  = dataSize( 1 );
    tfNumb   = ceil( dataLen / TF );
    
        % подготовка массивов bid и ask для текущего блока данных
    bidPrices = currData( :, 2 * ( pairIndex - 1) + 1 );  
    askPrices = currData( :, 2 * pairIndex );
    
        % инициализация рабочих переменных
    dealDir       = 0;
    openDealPrice = 0;
    stopLoss      = 0;
    takeProfit    = 0;
    currDeal      = 0;
    signal        = 0;

    
    
    E1 = bidPrices( 1 );
    E2 = bidPrices( 1 );
    
    currTime = 1;
    
    for t = 2:tfNumb % по всем таймфреймам...
        
        currTime = currTime + TF;
        
            % определяем длину таймфрейма (последний таймфрейм может быть
            % короче остальных)
        barLen = TF; 
        if t == tfNumb
            barLen = dataLen - TF * ( tfNumb - 1 );
        end
        
        for tick = 2:(barLen - 1) % по всем тикам внутри таймфрейма...
        
                
            if dealDir == 1 % если открыта сделка покупки
                
                    % обновляем текущее состояние сделки
                currDeal = bidPrices( currTime + tick ) - openDealPrice;
                
                currEQmin     = min( currDeal, currEQmin );
                currEQmax     = max( currDeal, currEQmax );
                currEQclose   = currDeal;
                
                    % обновляем текущее стоп-лосс
                stopLoss = max( stopLoss, bidPrices( currTime + tick ) - P( 5 ) * 0.0001 );
                
                     % проверка на закрытие по стоп-лоссу или тейк-профиту
                if or( bidPrices( currTime + tick ) <= stopLoss,    ...
                       bidPrices( currTime + tick ) >= takeProfit   ...
                     )
                    
                   dealDir = 0;
                    
                        % обновление характеристик стратегии
                   updateStratRates( currDeal );

                        % если демонстрационный режим, то закрытие бара
                        % текущей сделки
                   if WithPlot
                       [EQ, eqCounter] = updateSeries( EQ, eqCounter, [currEQopen, currEQmax, currEQmin, currEQclose] );
                   end
                   
                end
                
            end

            if dealDir == -1  % если открыта сделка продажи
                
                currDeal = openDealPrice - askPrices( currTime + tick );
                
                currEQmin     = min( currDeal, currEQmin );
                currEQmax     = max( currDeal, currEQmin );
                currEQclose   = currDeal;
                
                    % обновляем текущее стоп-лосс
                stopLoss = min( stopLoss, askPrices( currTime + tick ) + P( 5 ) * 0.0001 );
                
                    % проверка на закрытие по стоп-лоссу или тейк-профиту
                if or( askPrices( currTime + tick ) >= stopLoss,    ...
                       askPrices( currTime + tick ) <= takeProfit   ...
                     )
                    
                   dealDir = 0;
           
                        % обновление характеристик стратегии
                   updateStratRates( currDeal );

                        % если демонстрационный режим, то закрытие бара
                        % текущей сделки
                   if WithPlot
                       [EQ, eqCounter] = updateSeries( EQ, eqCounter, [currEQopen, currEQmax, currEQmin, currEQclose] );
                   end
                    
                end
                
            end
            
        end
        
            % при открытии нового таймфрейма обновляем скользящие средние
        E1 = E1 + P( 2 ) * 0.0001 * ( bidPrices( currTime ) - E1 );
        E2 = E2 + P( 3 ) * 0.0001 * ( bidPrices( currTime ) - E2 );
        
            % проверка текущего сигнала на открытие сделки
        signal = 0;
        if and( and( bidPrices( currTime ) >= E2, E2 > E1 ), abs( E1 - E2 ) >= P( 4 ) * 0.0001 )
            signal = 1;
        end
        if and( and( bidPrices( currTime ) <= E2, E2 < E1 ), abs( E1 - E2 ) >= P( 4 ) * 0.0001 )
            signal = -1;
        end
        
             % закрытие, если открыта сделка и есть сигнал на открытие
             % противоположной
        if or(                                  ...
            and( dealDir ==  1, signal == -1 ), ...
            and( dealDir == -1, signal ==  1 )  ... 
           )                                    
       
           dealDir = 0;
            
                % обновление характеристик стратегии
           updateStratRates( currDeal );
           
                 % если демонстрационный режим, то закрытие бара
                 % текущей сделки
           if WithPlot
               [EQ, eqCounter] = updateSeries( EQ, eqCounter, [currEQopen, currEQmax, currEQmin, currEQclose] );
           end
       
        end
        
            % открытие сделки на покупку
        if and( dealDir == 0, signal == 1 )
            
            dealDir = 1;
            openDealPrice = askPrices( currTime ); % цена открытия
            
                % стоп-лосс и тэйк-профит
            stopLoss   = bidPrices( currTime + tick ) - P( 5 ) * 0.0001; 
            takeProfit = bidPrices( currTime + tick ) + P( 6 ) * 0.0001;
            
                % инициализация значений бара Equity
            currEQopen    = askPrices( currTime + tick ) - bidPrices( currTime + tick );
            currEQmin     = askPrices( currTime + tick ) - bidPrices( currTime + tick );
            currEQmax     = askPrices( currTime + tick ) - bidPrices( currTime + tick );
            currEQclose   = askPrices( currTime + tick ) - bidPrices( currTime + tick );
            
        end
        
            % открытие сделки на продажу
        if and( dealDir == 0, signal == -1 )
            
            dealDir = -1;
            openDealPrice = bidPrices( currTime ); % цена открытия
            
                % стоп-лосс и тэйк-профит
            stopLoss   = askPrices( currTime + tick ) + P( 5 ) * 0.0001;
            takeProfit = askPrices( currTime + tick ) - P( 6 ) * 0.0001;
            
                % инициализация значений бара Equity
            currEQopen    = askPrices( currTime + tick ) - bidPrices( currTime + tick );
            currEQmin     = askPrices( currTime + tick ) - bidPrices( currTime + tick );
            currEQmax     = askPrices( currTime + tick ) - bidPrices( currTime + tick );
            currEQclose   = askPrices( currTime + tick ) - bidPrices( currTime + tick );
            
        end
        
    end
    
        % закрываем все открытые сделки
    if dealDir ~= 0
        
            % обновление характеристик стратегии
        updateStratRates( currDeal );

            % если демонстрационный режим, то закрытие бара
            % текущей сделки
        if WithPlot
            [EQ, eqCounter] = updateSeries( EQ, eqCounter, [currEQopen, currEQmax, currEQmin, currEQclose] );
        end
        
    end
    
end


Z = 0;

В приведённом выше листинге используется вспомогательная функция updateSeries, это своего рода «push_back».

Функция updateSeries.m:

function [S, I] = updateSeries(s, i, v)

if i == 0
    
    S = v;
    I = 1;
    
else
    
    I = i + 1;
    S = [s; v];
    
    
end

Ну и наконец, как всё это вместе можно использовать:

Скрипт mainScript.m:

% loadPrices;            % загрузка данных

global stratName;
global returnParamIndex;

global pairIndex;
global DATA;
global PRICES;
global dataBlocksNumber;

global WithPlot;
global EQ;
global eqCounter;


stratName        = 'twoEma'; % тестируем twoEMA

pairIndex = 2;               % вторая валютная пара (EUR/USD) 

DATA      = PRICES;

dataBlocksNumber = 16;

WithPlot = false;           % режим отладки 

P = [900, 100, 310, 25, 100, 40]; % стартовые значения

returnParamIndex = 7;             % оптимизируем коэффициент Шарпа

P = fminsearch( 'testStrat', P );
display(P);


WithPlot = true;                % режим демонстрации
EQ = 0;
eqCounter = 0;
returnParamIndex = 0;
R = testStrat( P );

display(R);

for i = 2:eqCounter   % приводим бары по сделкам к накопительному виду
    
    EQ( i, 1 ) = EQ( i, 1 ) + EQ( i - 1, 4 );
    EQ( i, 2 ) = EQ( i, 2 ) + EQ( i - 1, 4 );
    EQ( i, 3 ) = EQ( i, 3 ) + EQ( i - 1, 4 );
    EQ( i, 4 ) = EQ( i, 4 ) + EQ( i - 1, 4 );
    
end

candle(EQ(:, 2), EQ(:, 3), EQ(:, 4), EQ(:, 1)); title('Equity');

В результате equity торговой системы будет выглядеть так:

image

А характеристики так:

DrawDown: 0.0105
Recovery: 12.6103
Equity: 0.1320
DealsNumber: 47
MeanDeal: 0.0028
DealsSigma: 0.0056
Sharp: 0.5034
ProfitFactor: 3.3393
ProfitDealsNumber: 34
ProfitDealsRate: 0.7234
ProfitMean: 0.0055
LossDealsNumber: 13
LossDealsRate: 0.2766
LossMean: -0.0043

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

Но ведь у нас не было цели создать супер систему. А возможности использования Matlab продемонстрированы.

Автор: JamaGava

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js