Warrior_EA/Mind/TradeBook.mqh

173 lines
7.8 KiB
MQL5

//+------------------------------------------------------------------+
//| TradeBook.mqh |
//| AnimateDread |
//| |
//| THE JOURNAL OF REAL TRADES, one line each, written when they |
//| CLOSE: what the trader saw (the whole SMarketContext), what they |
//| decided (size, and why), and what happened (R, the worst and best |
//| the trade was ever worth, how long it took, how it ended). |
//| |
//| This is not Database\WarriorJournal.mqh. That one journals every |
//| FILTER FIRING as a virtual trade against invented barriers, to |
//| rank modules. This one records the trades the account actually |
//| took, at the barriers it actually had, with the costs it actually |
//| paid - the only kind of evidence the Mind acts on. |
//| |
//| R = net result / the money the stop put at risk when the trade |
//| opened. It is computed from the broker's own OrderCalcProfit, so |
//| it needs no contract-size arithmetic here and includes swap and |
//| commission. MAE/MFE are read from the bars the trade lived through, |
//| not from ticks, so they are the same under any tester model. |
//| |
//| FILE: Common\Files\Warrior_EA\Mind\book_<SYMBOL>_<TF>.csv |
//| * tester -> truncated at Init. A backtest's book is that |
//| backtest's, never last week's. |
//| * live -> appended, and read back at Init so the reviewer |
//| resumes where the account left off. |
//+------------------------------------------------------------------+
#ifndef WARRIOR_TRADEBOOK_MQH
#define WARRIOR_TRADEBOOK_MQH
#include "MarketContext.mqh"
//--- A trade from the moment it opens to the moment it is filed.
struct STradeRecord
{
ulong position; // position id
datetime openTime, closeTime;
int side; // +1 long, -1 short
double entry, exit, sl, lots;
double riskMoney; // what the stop put at risk when it opened
double net; // profit + swap + commission
double r; // net / riskMoney
double maeR, mfeR; // worst / best excursion, in R (mae <= 0 <= mfe)
int bars; // bars the trade lived through
string reason; // stop / stopout / expert
double scale; // risk multiple applied to the base (1.0 = untouched)
double reviewP; // the reviewer's P(win), or CTX_NA
int reviewN; // trades behind it
SMarketContext ctx;
};
class CTradeBook
{
protected:
string m_path;
bool m_live;
int m_written;
static string BaseHeader(void)
{
return "position,open_time,close_time,side,entry,exit,sl,lots,risk_money,net,r,mae_r,mfe_r,"
"bars,reason,scale,review_p,review_n,";
}
public:
CTradeBook(void) : m_path(""), m_live(false), m_written(0) {}
//--- Opens (tester: truncates; live: keeps) the file for this symbol and timeframe.
bool Init(const string symbol, const ENUM_TIMEFRAMES tf, const bool tester);
string Path(void) const { return m_path; }
int Written(void) const { return m_written; }
void Append(const STradeRecord &t);
//--- Live only: replay the file, handing each row's context and R to the caller.
//--- Rows are parsed by the width of the header this build writes; a file from a build with
//--- different fields is refused rather than mis-read.
int Load(SMarketContext &ctxs[], double &rs[]) const;
};
//+------------------------------------------------------------------+
bool CTradeBook::Init(const string symbol, const ENUM_TIMEFRAMES tf, const bool tester)
{
FolderCreate("Warrior_EA", FILE_COMMON);
FolderCreate("Warrior_EA\\Mind", FILE_COMMON);
m_path = "Warrior_EA\\Mind\\book_" + symbol + "_" + IntegerToString((int)tf) + ".csv";
m_live = !tester;
const string header = BaseHeader() + CtxHeader() + ",story";
if(tester || !FileIsExist(m_path, FILE_COMMON))
{
const int h = FileOpen(m_path, FILE_WRITE | FILE_TXT | FILE_ANSI | FILE_COMMON);
if(h == INVALID_HANDLE)
{
PrintFormat("CTradeBook: cannot write %s (error %d) - no book will be kept.", m_path, GetLastError());
m_path = "";
return false;
}
FileWriteString(h, header + "\r\n");
FileClose(h);
return true;
}
//--- Live restart: the existing file must be one this build can read.
const int h = FileOpen(m_path, FILE_READ | FILE_TXT | FILE_ANSI | FILE_COMMON);
if(h == INVALID_HANDLE)
return false;
const string first = FileReadString(h);
FileClose(h);
if(first != header)
{
PrintFormat("CTradeBook: %s was written by a different build (columns differ) - starting a"
" new book beside it.", m_path);
m_path = "Warrior_EA\\Mind\\book_" + symbol + "_" + IntegerToString((int)tf) + "_" +
TimeToString(TimeLocal(), TIME_DATE) + ".csv";
const int w = FileOpen(m_path, FILE_WRITE | FILE_TXT | FILE_ANSI | FILE_COMMON);
if(w == INVALID_HANDLE)
{
m_path = "";
return false;
}
FileWriteString(w, header + "\r\n");
FileClose(w);
}
return true;
}
//+------------------------------------------------------------------+
void CTradeBook::Append(const STradeRecord &t)
{
if(m_path == "")
return;
const int h = FileOpen(m_path, FILE_READ | FILE_WRITE | FILE_TXT | FILE_ANSI | FILE_COMMON);
if(h == INVALID_HANDLE)
return;
FileSeek(h, 0, SEEK_END);
string story = t.ctx.story;
StringReplace(story, ",", ";");
StringReplace(story, "\n", " ");
const string row = StringFormat("%I64u,%s,%s,%d,%.5f,%.5f,%.5f,%.2f,%.2f,%.2f,%.4f,%.4f,%.4f,%d,%s,%.2f,%s,%d,",
t.position, TimeToString(t.openTime, TIME_DATE | TIME_MINUTES),
TimeToString(t.closeTime, TIME_DATE | TIME_MINUTES), t.side,
t.entry, t.exit, t.sl, t.lots, t.riskMoney, t.net, t.r, t.maeR,
t.mfeR, t.bars, t.reason, t.scale,
(t.reviewP == CTX_NA ? "" : DoubleToString(t.reviewP, 4)), t.reviewN)
+ CtxRow(t.ctx) + "," + story;
FileWriteString(h, row + "\r\n");
FileClose(h);
m_written++;
}
//+------------------------------------------------------------------+
int CTradeBook::Load(SMarketContext &ctxs[], double &rs[]) const
{
if(m_path == "" || !m_live)
return 0;
const int h = FileOpen(m_path, FILE_READ | FILE_TXT | FILE_ANSI | FILE_COMMON);
if(h == INVALID_HANDLE)
return 0;
FileReadString(h); // header
const int base = 18; // columns before the context
int n = 0;
while(!FileIsEnding(h))
{
const string line = FileReadString(h);
string f[];
const int cols = StringSplit(line, ',', f);
if(cols < base + CTX_N)
continue;
ArrayResize(ctxs, n + 1, 64);
ArrayResize(rs, n + 1, 64);
ctxs[n].Reset(0);
for(int i = 0; i < CTX_N; i++)
ctxs[n].v[i] = StringToDouble(f[base + i]);
rs[n] = StringToDouble(f[10]); // the `r` column
n++;
}
FileClose(h);
return n;
}
#endif // WARRIOR_TRADEBOOK_MQH