forked from airat77786/MQL5Book
223 lines
8.4 KiB
MQL5
223 lines
8.4 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| TradeReportWriter.mqh |
| |||
//| Copyright (c) 2022, MetaQuotes Ltd. |
| |||
//| https://www.mql5.com |
| |||
//+------------------------------------------------------------------+
| |||
#include "TradeReport.mqh"
| |||
#include "AutoPtr.mqh"
| |||
| |||
#resource "TradeReportPage.htm" as string ReportPageTemplate
| |||
#resource "TradeReportTable.htm" as string ReportTableTemplate
| |||
#resource "TradeReportSVG.htm" as string SVGBoxTemplate
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Base abstract class to write trade stats into some storage |
| |||
//+------------------------------------------------------------------+
| |||
class TradeReportWriter
| |||
{
| |||
protected:
| |||
class DataHolder
| |||
{
| |||
public:
| |||
double data[]; // balance marks
| |||
datetime when[]; // time stamps for the marks
| |||
string name; // description
| |||
color clr; // visible color
| |||
int width; // line width
| |||
TradeReport::GenericStats stats; // trade statistics
| |||
};
| |||
| |||
AutoPtr<DataHolder> curves[];
| |||
double lower, upper;
| |||
datetime start, stop;
| |||
| |||
public:
| |||
TradeReportWriter(): lower(DBL_MAX), upper(-DBL_MAX), start(0), stop(0) { }
| |||
| |||
virtual bool addCurve(TradeReport::GenericStats &stats,
| |||
double &data[], datetime &when[], const string name,
| |||
const color clr = clrNONE, const int width = 1)
| |||
{
| |||
if(addCurve(data, when, name, clr, width))
| |||
{
| |||
curves[ArraySize(curves) - 1][].stats = stats;
| |||
return true;
| |||
}
| |||
return false;
| |||
}
| |||
| |||
virtual bool addCurve(double &data[], datetime &when[], const string name,
| |||
const color clr = clrNONE, const int width = 1)
| |||
{
| |||
if(ArraySize(data) == 0 || ArraySize(when) == 0) return false;
| |||
if(ArraySize(data) != ArraySize(when)) return false;
| |||
DataHolder *c = new DataHolder();
| |||
if(!ArraySwap(data, c.data) || !ArraySwap(when, c.when))
| |||
{
| |||
delete c;
| |||
return false;
| |||
}
| |||
| |||
const double max = c.data[ArrayMaximum(c.data)];
| |||
const double min = c.data[ArrayMinimum(c.data)];
| |||
| |||
lower = fmin(min, lower);
| |||
upper = fmax(max, upper);
| |||
if(start == 0) start = c.when[0];
| |||
else if(c.when[0] != 0) start = fmin(c.when[0], start);
| |||
stop = fmax(c.when[ArraySize(c.when) - 1], stop);
| |||
| |||
c.name = name;
| |||
c.clr = clr;
| |||
c.width = width;
| |||
ZeroMemory(c.stats); // no stats by default
| |||
PUSH(curves, c);
| |||
return true;
| |||
}
| |||
| |||
virtual void render() = 0;
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Concrete class to write trade stats into HTML-file |
| |||
//+------------------------------------------------------------------+
| |||
class HTMLReportWriter: public TradeReportWriter
| |||
{
| |||
int handle;
| |||
int width, height;
| |||
| |||
public:
| |||
HTMLReportWriter(const string name, const int w = 600, const int h = 400):
| |||
width(w), height(h)
| |||
{
| |||
handle = FileOpen(name,
| |||
FILE_WRITE | FILE_TXT | FILE_ANSI | FILE_REWRITE | FILE_SHARE_READ | FILE_SHARE_WRITE);
| |||
}
| |||
| |||
~HTMLReportWriter()
| |||
{
| |||
if(handle != 0) FileClose(handle);
| |||
}
| |||
| |||
void close()
| |||
{
| |||
if(handle != 0) FileClose(handle);
| |||
handle = 0;
| |||
}
| |||
| |||
virtual void render() override
| |||
{
| |||
string headerAndFooter[2];
| |||
StringSplit(ReportPageTemplate, '~', headerAndFooter);
| |||
FileWriteString(handle, headerAndFooter[0]);
| |||
renderContent();
| |||
FileWriteString(handle, headerAndFooter[1]);
| |||
}
| |||
| |||
private:
| |||
void renderContent()
| |||
{
| |||
renderSVG();
| |||
renderTables();
| |||
}
| |||
| |||
void renderTable(const int k, TradeReport::GenericStats &stats,
| |||
const string name, const color clr)
| |||
{
| |||
string table = ReportTableTemplate;
| |||
StringReplace(table, "%COLOR%", colorToString(k, clr));
| |||
StringReplace(table, "%NAME%", name);
| |||
| |||
StringReplace(table, "%PROFITS%", DoubleToString(stats.profits, 2));
| |||
StringReplace(table, "%PF%", DoubleToString(stats.pf, 2));
| |||
StringReplace(table, "%LOSSES%", DoubleToString(stats.losses, 2));
| |||
StringReplace(table, "%SHARPE%", DoubleToString(stats.sharpe, 2));
| |||
StringReplace(table, "%NET%", DoubleToString(stats.profits + stats.losses, 2));
| |||
StringReplace(table, "%RF%", DoubleToString(stats.recovery, 2));
| |||
StringReplace(table, "%DEPOSIT%", DoubleToString(stats.series_start, 2));
| |||
StringReplace(table, "%DEALS%", (string)stats.deals);
| |||
| |||
StringReplace(table, "%BUYWIN%", (string)stats.buy_wins);
| |||
StringReplace(table, "%BUYLOSS%", (string)(stats.buy_trades - stats.buy_wins));
| |||
StringReplace(table, "%BUY%", (string)stats.buy_trades);
| |||
StringReplace(table, "%SELLWIN%", (string)stats.sell_wins);
| |||
StringReplace(table, "%SELLLOSS%", (string)(stats.trades - stats.buy_trades - stats.sell_wins));
| |||
StringReplace(table, "%SELL%", (string)(stats.trades - stats.buy_trades));
| |||
StringReplace(table, "%WIN%", (string)stats.wins);
| |||
StringReplace(table, "%LOSS%", (string)(stats.trades - stats.wins));
| |||
StringReplace(table, "%TOTAL%", (string)stats.trades);
| |||
| |||
StringReplace(table, "%DD%", DoubleToString(stats.series_dd, 2));
| |||
StringReplace(table, "%DD_PCT%", DoubleToString(stats.series_dd_percent, 2));
| |||
StringReplace(table, "%DD_REL%", DoubleToString(stats.series_dd_relative, 2));
| |||
StringReplace(table, "%DD_REL_PCT%", DoubleToString(stats.series_dd_relative_percent, 2));
| |||
const double depo_dd = -fmin(stats.series_start - stats.series_min, 0);
| |||
StringReplace(table, "%DD_DEPO%", DoubleToString(depo_dd, 2));
| |||
StringReplace(table, "%DD_DEPO_PCT%", DoubleToString(depo_dd * 100 / stats.series_start, 2));
| |||
| |||
StringReplace(table, "%MAX_PROFIT%", DoubleToString(stats.max_profit, 2));
| |||
StringReplace(table, "%AVERAGE%", DoubleToString(stats.average_trade, 2));
| |||
StringReplace(table, "%MAX_LOSS%", DoubleToString(stats.max_loss, 2));
| |||
| |||
FileWriteString(handle, table);
| |||
}
| |||
| |||
void renderTables()
| |||
{
| |||
for(int i = 0; i < ArraySize(curves); ++i)
| |||
{
| |||
renderTable(i, curves[i][].stats, curves[i][].name, curves[i][].clr);
| |||
}
| |||
}
| |||
| |||
static string colorToString(const int k, const color c)
| |||
{
| |||
#define NUM_COLORS 6
| |||
static const color autoColor[NUM_COLORS] =
| |||
{clrRed, clrLimeGreen, clrBlue, clrMagenta, clrDodgerBlue, clrOrange};
| |||
string s = ColorToString(c == clrNONE ? autoColor[k % NUM_COLORS] : c, true);
| |||
StringReplace(s, "clr", "");
| |||
StringToLower(s);
| |||
return s;
| |||
#undef NUM_COLORS
| |||
}
| |||
| |||
void renderCurve(const int k, const double &data[], const datetime &when[],
| |||
const string name, const color c, const int w)
| |||
{
| |||
const string polyline = StringFormat(
| |||
"<polyline stroke=\"%s\" stroke-width=\"%dpx\" fill=\"none\" points=\"",
| |||
colorToString(k, c), w);
| |||
FileWriteString(handle, polyline);
| |||
const int n = ArraySize(data);
| |||
for(int i = 0; i < n; ++i)
| |||
{
| |||
const int y = height - (int)((data[i] - lower) / fmax(upper - lower, 1) * height);
| |||
const int x = (int)((when[i] - start) * width / fmax(stop - start, 1));
| |||
FileWriteString(handle, (string)x + ", " + (string)y + " ");
| |||
}
| |||
const string polylineend = "\"/>\n";
| |||
FileWriteString(handle, polylineend);
| |||
| |||
const string legend = "<text x=\"%d\" y=\"%d\" fill=\"%s\" class=\"legend\">%s</text>";
| |||
FileWriteString(handle, StringFormat(legend, 10, 20 + k * 10, colorToString(k, c), name));
| |||
}
| |||
| |||
void renderSVG()
| |||
{
| |||
string headerAndFooter[2];
| |||
if(StringSplit(SVGBoxTemplate, '~', headerAndFooter) != 2) return;
| |||
StringReplace(headerAndFooter[0], "%WIDTH%", (string)width);
| |||
StringReplace(headerAndFooter[0], "%HEIGHT%", (string)height);
| |||
FileWriteString(handle, headerAndFooter[0]);
| |||
| |||
for(int i = 0; i < ArraySize(curves); ++i)
| |||
{
| |||
renderCurve(i, curves[i][].data, curves[i][].when,
| |||
curves[i][].name, curves[i][].clr, curves[i][].width);
| |||
}
| |||
| |||
FileWriteString(handle, headerAndFooter[1]);
| |||
}
| |||
};
| |||
//+------------------------------------------------------------------+
|