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.
203 lines
8 KiB
MQL5
203 lines
8 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| JournalReviewer.mqh |
|
|
//| AnimateDread |
|
|
//| |
|
|
//| THE WEEKLY REVIEW A DISCIPLINED TRADER DOES WITH THEIR JOURNAL. |
|
|
//| |
|
|
//| Every closed trade arrives with the context it was taken in and |
|
|
//| what it paid (in R). The reviewer files it under a small number of |
|
|
//| coarse conditions - how volatile, how much volume for the hour, |
|
|
//| whether the Wyckoff structure agreed, whether the other indices |
|
|
//| were selling off too - and, when a new setup appears, answers |
|
|
//| "in the conditions this looks like, how have my trades done?" |
|
|
//| |
|
|
//| WHY IT IS BUILT TO BE TIMID. |
|
|
//| * ADDITIVE, NOT A LOOK-UP OF THE FULL COMBINATION. Four bands of |
|
|
//| three is 81 cells; the book makes ~650 trades in five years. |
|
|
//| Each dimension is estimated on its own and the deviations are |
|
|
//| AVERAGED - this ignores interactions (the memory of this repo |
|
|
//| is that the edge lives in combinations) and pays for it in |
|
|
//| honesty: it can be wrong about a joint effect, it cannot be |
|
|
//| precise about one it lacks the data for. |
|
|
//| * SHRUNK. A band's deviation from the book average counts for |
|
|
//| n / (n + k) of its size: 30 trades at k = 20 is 60% believed. |
|
|
//| * SILENT until MinTrades have closed, and CAUSAL by construction: |
|
|
//| a trade is added only when it closes, so an estimate can never |
|
|
//| include a trade that is still open or lies in the future. |
|
|
//| * ONE RUN, NO MEMORY ACROSS RUNS in the tester (the book decides |
|
|
//| that, not this class): a persisted verdict would leak one |
|
|
//| backtest into the next. |
|
|
//| |
|
|
//| The output is a shrunk P(win) and its support (how many trades |
|
|
//| stand behind it). CBetSizer turns that into a size. |
|
|
//+------------------------------------------------------------------+
|
|
#ifndef WARRIOR_JOURNALREVIEWER_MQH
|
|
#define WARRIOR_JOURNALREVIEWER_MQH
|
|
|
|
#include "MarketContext.mqh"
|
|
|
|
#define REVIEW_DIMS 4
|
|
#define REVIEW_BANDS 3
|
|
|
|
//--- What the reviewer concluded for one prospective trade.
|
|
struct SReview
|
|
{
|
|
bool valid; // false = too few trades to say anything
|
|
double p; // shrunk P(win) for these conditions
|
|
double p0; // the book's average P(win)
|
|
int n; // trades in the book
|
|
int support; // trades in the LEAST-populated band that contributed
|
|
string bands; // "vol=hi volume=mid wyckoff=lo breadth=hi", for the log
|
|
};
|
|
|
|
class CJournalReviewer
|
|
{
|
|
protected:
|
|
int m_k; // shrinkage strength
|
|
int m_minTrades;
|
|
int m_n, m_wins;
|
|
double m_sumR;
|
|
int m_cnt[REVIEW_DIMS][REVIEW_BANDS];
|
|
int m_win[REVIEW_DIMS][REVIEW_BANDS];
|
|
double m_r[]; // every R, for the edge statistics
|
|
|
|
//--- The dimension table: which context field, and the two edges that cut it into low/mid/high.
|
|
//--- Edges are fixed, not fitted - fitting them to the book would be one more free parameter
|
|
//--- chosen on the very trades it then judges.
|
|
static int FieldOf(const int d)
|
|
{
|
|
switch(d)
|
|
{
|
|
case 0: return CTX_VOL_PCT;
|
|
case 1: return CTX_RVOL;
|
|
case 2: return CTX_WYK_BIAS;
|
|
default: return CTX_BREADTH;
|
|
}
|
|
}
|
|
static void EdgesOf(const int d, double &lo, double &hi)
|
|
{
|
|
switch(d)
|
|
{
|
|
case 0: lo = 0.70; hi = 0.85; break; // the gate already demands >= 0.50
|
|
case 1: lo = -0.15; hi = 0.15; break; // ln volume vs the hour's norm, detrended
|
|
case 2: lo = -0.25; hi = 0.25; break; // the reader's verdict
|
|
default: lo = 0.34; hi = 0.67; break; // share of peers dipping too
|
|
}
|
|
}
|
|
static string DimName(const int d)
|
|
{
|
|
switch(d)
|
|
{
|
|
case 0: return "vol";
|
|
case 1: return "volume";
|
|
case 2: return "wyckoff";
|
|
default: return "breadth";
|
|
}
|
|
}
|
|
//--- 0 low, 1 mid, 2 high. A reading that is absent files under MID: unknown must not push
|
|
//--- an estimate either way.
|
|
int BandOf(const int d, const SMarketContext &c) const
|
|
{
|
|
const double x = c.v[FieldOf(d)];
|
|
if(x == CTX_NA)
|
|
return 1;
|
|
double lo, hi;
|
|
EdgesOf(d, lo, hi);
|
|
return (x < lo) ? 0 : ((x > hi) ? 2 : 1);
|
|
}
|
|
|
|
public:
|
|
CJournalReviewer(void) : m_k(20), m_minTrades(40), m_n(0), m_wins(0), m_sumR(0.0)
|
|
{
|
|
ArrayInitialize(m_cnt, 0);
|
|
ArrayInitialize(m_win, 0);
|
|
}
|
|
void Configure(const int minTrades, const int shrink)
|
|
{
|
|
m_minTrades = MathMax(minTrades, 10);
|
|
m_k = MathMax(shrink, 1);
|
|
}
|
|
int Trades(void) const { return m_n; }
|
|
double MeanR(void) const { return (m_n > 0) ? m_sumR / m_n : 0.0; }
|
|
double WinRate(void) const { return (m_n > 0) ? (double)m_wins / m_n : 0.0; }
|
|
void Returns(double &out[]) const { ArrayCopy(out, m_r); }
|
|
|
|
//--- File one closed trade.
|
|
void Add(const SMarketContext &c, const double r)
|
|
{
|
|
const bool win = (r > 0.0);
|
|
m_n++;
|
|
m_sumR += r;
|
|
if(win)
|
|
m_wins++;
|
|
for(int d = 0; d < REVIEW_DIMS; d++)
|
|
{
|
|
const int b = BandOf(d, c);
|
|
m_cnt[d][b]++;
|
|
if(win)
|
|
m_win[d][b]++;
|
|
}
|
|
const int k = ArraySize(m_r);
|
|
ArrayResize(m_r, k + 1, 256);
|
|
m_r[k] = r;
|
|
}
|
|
SReview Review(const SMarketContext &c) const
|
|
{
|
|
SReview v;
|
|
v.valid = false;
|
|
v.n = m_n;
|
|
v.p0 = WinRate();
|
|
v.p = v.p0;
|
|
v.support = 0;
|
|
v.bands = "";
|
|
if(m_n < m_minTrades)
|
|
return v;
|
|
double dev = 0.0;
|
|
int used = 0, minSupport = m_n;
|
|
for(int d = 0; d < REVIEW_DIMS; d++)
|
|
{
|
|
const int b = BandOf(d, c);
|
|
const int nb = m_cnt[d][b];
|
|
const string tag = (b == 0 ? "lo" : (b == 1 ? "mid" : "hi"));
|
|
v.bands += (d > 0 ? " " : "") + DimName(d) + "=" + tag;
|
|
if(nb == 0)
|
|
continue;
|
|
const double w = (double)nb / (nb + m_k);
|
|
dev += w * ((double)m_win[d][b] / nb - v.p0);
|
|
used++;
|
|
minSupport = MathMin(minSupport, nb);
|
|
}
|
|
if(used == 0)
|
|
return v;
|
|
v.valid = true;
|
|
v.p = MathMax(0.05, MathMin(0.95, v.p0 + dev / used));
|
|
v.support = minSupport;
|
|
return v;
|
|
}
|
|
//--- The review, as a table a person can read at the end of a run.
|
|
void Print(void) const
|
|
{
|
|
if(m_n == 0)
|
|
{
|
|
::Print("CJournalReviewer: no closed trades.");
|
|
return;
|
|
}
|
|
PrintFormat("CJournalReviewer: %d trade(s), win rate %.1f%%, mean R %+.3f.", m_n,
|
|
100.0 * WinRate(), MeanR());
|
|
for(int d = 0; d < REVIEW_DIMS; d++)
|
|
{
|
|
double lo, hi;
|
|
EdgesOf(d, lo, hi);
|
|
string line = " " + DimName(d) + " (" + CtxName(FieldOf(d)) + "; cuts " +
|
|
DoubleToString(lo, 2) + " / " + DoubleToString(hi, 2) + "):";
|
|
for(int b = 0; b < REVIEW_BANDS; b++)
|
|
{
|
|
const int nb = m_cnt[d][b];
|
|
line += StringFormat(" %s n=%d win=%s", (b == 0 ? "lo" : (b == 1 ? "mid" : "hi")), nb,
|
|
(nb > 0 ? DoubleToString(100.0 * m_win[d][b] / nb, 0) + "%" : "-"));
|
|
}
|
|
::Print(line);
|
|
}
|
|
}
|
|
};
|
|
#endif // WARRIOR_JOURNALREVIEWER_MQH
|