forked from rosh/MQL5Book
168 lines
5.9 KiB
MQL5
168 lines
5.9 KiB
MQL5
//+------------------------------------------------------------------+
| |
//| MatrixForexBasket.mq5 |
| |
//| Copyright 2022, MetaQuotes Ltd. |
| |
//| https://www.mql5.com |
| |
//+------------------------------------------------------------------+
| |
#include <Graphics/Graphic.mqh>
| |
#include "..\..\Include\MatrixProcessor.mqh"
| |
| |
#property script_show_inputs
| |
| |
input int BarCount = 20; // BarCount (in-sample "history" and out-of-sample "future")
| |
input int BarOffset = 10; // BarOffset (where "future" begins)
| |
input ENUM_CURVE_TYPE CurveType = CURVE_LINES;
| |
| |
//+------------------------------------------------------------------+
| |
//| Model of ideal constantly growing balance curve |
| |
//+------------------------------------------------------------------+
| |
void ConstantGrow(vector &v)
| |
{
| |
for(ulong i = 0; i < v.Size(); ++i)
| |
{
| |
v[i] = (double)(i + 1);
| |
}
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Convert vector of one primitive type (S) to another (T) |
| |
//+------------------------------------------------------------------+
| |
template<typename T, typename S>
| |
void ConvertV(vector<T> &t, const S &v)
| |
{
| |
for(ulong i = 0; i < v.Size(); ++i)
| |
{
| |
t[i] = (T)v[i];
| |
}
| |
}
| |
| |
//+------------------------------------------------------------------+
| |
//| Script program start function |
| |
//+------------------------------------------------------------------+
| |
void OnStart()
| |
{
| |
// work symbols are hardcoded (adjust according to your needs and environment)
| |
const string symbols[] = {"EURUSD", "GBPUSD", "USDJPY", "USDCAD", "USDCHF", "AUDUSD", "NZDUSD"};
| |
const int size = ArraySize(symbols);
| |
| |
// check if the linear system is well-defined
| |
if(size > BarCount - BarOffset)
| |
{
| |
Print("Symbol count must be larger than number of historic bars, given: ",
| |
size, " and ", BarCount - BarOffset, " respectively");
| |
return;
| |
}
| |
| |
// create a matrix for given symbols and bar count
| |
matrix rates(BarCount, size);
| |
// build a model of best balance - stable profit on every bar
| |
vector model(BarCount - BarOffset, ConstantGrow);
| |
// aux vector to hold intermediate row of i-th symbol quotes
| |
vector close;
| |
| |
for(int i = 0; i < size; i++) // process all symbols
| |
{
| |
// get rates
| |
if(close.CopyRates(symbols[i], _Period, COPY_RATES_CLOSE, 0, BarCount))
| |
{
| |
// get price increment (profit)
| |
close -= close[0];
| |
// adjust profit by point value
| |
close *= SymbolInfoDouble(symbols[i], SYMBOL_TRADE_TICK_VALUE) /
| |
SymbolInfoDouble(symbols[i], SYMBOL_TRADE_TICK_SIZE);
| |
// place vector to specific column in the matrix
| |
rates.Col(close, i);
| |
}
| |
else
| |
{
| |
Print("vector.CopyRates(%d, COPY_RATES_CLOSE) failed. Error ", symbols[i], _LastError);
| |
return;
| |
}
| |
}
| |
| |
// split the matrix for starting part to get solution
| |
// (which emulates optimization on history)
| |
// and second part for forward test
| |
matrix split[];
| |
if(BarOffset > 0)
| |
{
| |
// training = backtest on BarCount - BarOffset bars
| |
// out of sample future = forward test on BarOffset bars
| |
ulong parts[] = {BarCount - BarOffset, BarOffset};
| |
rates.Split(parts, 0, split);
| |
}
| |
| |
// solve linear system equation against the model
| |
vector x = (BarOffset > 0) ? split[0].LstSq(model) : rates.LstSq(model);
| |
| |
Print("Solution (lots per symbol): ");
| |
{
| |
// use float vector just for shorter printing of the solution
| |
vectorf xf(size, ConvertV, x);
| |
Print(xf);
| |
}
| |
| |
// use vector for simulated balance curve
| |
vector balance = vector::Zeros(BarCount);
| |
for(int i = 1; i < BarCount; ++i)
| |
{
| |
balance[i] = 0;
| |
for(int j = 0; j < size; ++j)
| |
{
| |
balance[i] += (float)(rates[i][j] * x[j]);
| |
}
| |
}
| |
| |
// now estimate the quality of solution
| |
if(BarOffset > 0)
| |
{
| |
// NB: MQL5 doesn't have Split for vectors!
| |
// NB: MQL5 can't assign vector to matrix or matrix to vector!
| |
// make a copy of balance
| |
vector backtest = balance;
| |
// only historic in-sample bars are used for backtest estimation
| |
backtest.Resize(BarCount - BarOffset);
| |
// prepare forward out-of-sample part of the bars manually
| |
vector forward(BarOffset);
| |
for(int i = 0; i < BarOffset; ++i)
| |
{
| |
forward[i] = balance[BarCount - BarOffset + i];
| |
}
| |
// calculate regression metrics for backtest and forward
| |
Print("Backtest R2 = ", backtest.RegressionMetric(model, REGRESSION_R2));
| |
model.Resize(BarOffset);
| |
model += BarCount - BarOffset;
| |
Print("Forward R2 = ", forward.RegressionMetric(model, REGRESSION_R2));
| |
}
| |
else
| |
{
| |
Print("R2 = ", balance.RegressionMetric(model, REGRESSION_R2));
| |
}
| |
| |
// copy the 'balance' vector into array of doubles
| |
double array[];
| |
Export(balance, array); // TODO: balance.Swap(array);
| |
| |
// pretty-printing with 2 digits
| |
Print("Balance: ");
| |
ArrayPrint(array, 2);
| |
| |
// let's draw the graph of the balance (both "backtest" and "forward")
| |
GraphPlot(array, CurveType);
| |
| |
if(MQLInfoInteger(MQL_DEBUG))
| |
{
| |
Sleep(5000);
| |
}
| |
}
| |
//+------------------------------------------------------------------+
| |
/*
| |
| |
(EURUSD,H1) Solution (lots per symbol):
| |
(EURUSD,H1) [-0.0057809334,-0.0079846876,0.0088985749,-0.0041461736,-0.010710154,-0.0025694175,0.01493552]
| |
(EURUSD,H1) Backtest R2 = 0.9896645616246145
| |
(EURUSD,H1) Forward R2 = 0.8667852183780984
| |
(EURUSD,H1) Balance:
| |
(EURUSD,H1) 0.00 1.68 3.38 3.90 5.04 5.92 7.09 7.86 9.17 9.88 9.55 10.77 12.06 13.67 15.35 15.89 16.28 15.91 16.85 16.58
| |
| |
*/
| |
//+------------------------------------------------------------------+
|