//+------------------------------------------------------------------+ //| 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__.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