forked from mnbvc188199/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
|