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

Думаю, обґрунтовувати необхідність ретельного тестування та підбору параметрів торгових стратегій немає необхідності… Краще поясню, чому саме 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_win\work\frx\DATA\1\tickPack.csv';
'C:\matlabR2008a_win\work\frx\DATA\2\tickPack.csv';
'C:\matlabR2008a_win\work\frx\DATA\3\tickPack.csv';
'C:\matlabR2008a_win\work\frx\DATA\4\tickPack.csv';
'C:\matlabR2008a_win\work\frx\DATA\5\tickPack.csv';
'C:\matlabR2008a_win\work\frx\DATA\6\tickPack.csv';
'C:\matlabR2008a_win\work\frx\DATA\7\tickPack.csv';
'C:\matlabR2008a_win\work\frx\DATA\8\tickPack.csv';
'C:\matlabR2008a_win\work\frx\DATA\9\tickPack.csv';
'C:\matlabR2008a_win\work\frx\DATA\10\tickPack.csv';
'C:\matlabR2008a_win\work\frx\DATA\11\tickPack.csv';
'C:\matlabR2008a_win\work\frx\DATA\12\tickPack.csv';
'C:\matlabR2008a_win\work\frx\DATA\13\tickPack.csv';
'C:\matlabR2008a_win\work\frx\DATA\14\tickPack.csv';
'C:\matlabR2008a_win\work\frx\DATA\15\tickPack.csv';
'C:\matlabR2008a_win\work\frx\DATA\16\tickPack.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®;

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 продемонстровані.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.