forked from animatedread/Warrior_EA
- Introduced `FeatureScale.mqh` with `FeatSquash` function for stateless feature scaling. - Added `RegimeMath.mqh` class for regime arithmetic, including efficiency and variance calculations. - Documented the Mind trading logic in `MIND.md`, detailing the trading process and modes. - Created `VOLNORM_PLAN.md` and `VOLNORM_RESULTS.md` for tick-volume normalization testing. - Implemented `read_book.py` for analyzing trade book data and correlations. - Developed `volnorm.py` for testing tick-volume normalization with new and old methods.
173 lines
7.8 KiB
MQL5
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
|