//+------------------------------------------------------------------+ //| ManagementNet.mqh | //| AnimateDread | //| | //| THE ONE QUESTION NO RULE COULD ANSWER. | //| | //| A trade is +0.5R in front. Does it run to target, or give it all | //| back to the stop? | //| | //| Measured on 9,770 resolved firings: 46.0% continue to +2R, 54.0% | //| retrace. A balanced label on a large sample, and a PERFECT caller | //| would be worth +0.540 R per firing - against a pooled deficit of | //| -0.13 to -0.18 R. A quarter of it flips the sign of the strategy. | //| | //| WHY A MODEL AND NOT A RULE. Three rules were tried and all three | //| lost money: an ATR trail (+150.67 -> +90.56, worse as it widened),| //| a nearer target (-20.32), and a breakeven stop (-22.35 at +0.5R, | //| monotonically worse the earlier it fired). They fail for one | //| shared reason - a give-back and a pullback-before-a-run look | //| IDENTICAL at the moment you must act. That is a classification | //| problem wearing a rule's clothes, and a threshold on one number | //| cannot separate two populations that overlap on that number. | //| | //| ⚠ IT DOES NOT PREDICT DIRECTION, and that is the point. Direction | //| was measured dead in this project at bar level (rho 0.00-0.02) | //| and every architecture thrown at it returned no information out | //| of sample. This asks a CONDITIONAL question - given the move has | //| already happened, does it extend or exhaust - which is a question | //| about persistence and volatility, and volatility is the one thing | //| here that has ever been predictable. | //| | //| ⚠ AND IT TRAINS ON THE CROSSING, NOT ON THE ENTRY. The features | //| describe the bar where +0.5R was first touched: how fast it got | //| there, how much pain came first, what the market was doing at | //| that moment. Using the entry bar's state would be answering a | //| different question with the wrong evidence. | //| | //| ⚠ maeR AS STORED IN THE JOURNAL IS LOOKAHEAD FOR THIS. It is the | //| worst excursion over the WHOLE trade, including after the | //| crossing, and it separates the two classes beautifully (0.51 vs | //| 1.21) precisely because it contains the answer. The adverse | //| excursion used here is measured only up TO the crossing bar. | //+------------------------------------------------------------------+ #ifndef WARRIOR_MANAGEMENT_NET_MQH #define WARRIOR_MANAGEMENT_NET_MQH #include "WarriorNet.mqh" #define MGMT_FEATURES 10 //--- The crossing this model is asked about, in R. Matched to the measurement above. #define MGMT_TRIGGER_R 0.5 //--- How long a virtual trade may stay open before it is abandoned unlabelled. #define MGMT_HORIZON 60 class CManagementNet { private: CWarriorNet m_net; bool m_ready; string m_why; public: CManagementNet(void) : m_ready(false), m_why("not trained") {} ~CManagementNet(void) {} bool Ready(void) const { return m_ready; } string Why(void) const { return m_why; } double AUC(void) const { return m_net.AUC(); } static string FeatureName(const int i); //--- P(this trade continues to target). <0 when there is no usable model. double Score(double &x[]) { return m_ready ? m_net.Score(x) : -1.0; } //--- TRAINED IN MEMORY, NEVER SERIALISED. //--- //--- ALGLIB's MQL5 serializer under-allocates its output buffer and dies inside //--- CSerializer::Stop() - "array out of range in ap.mqh (1996,17)" - which killed three of four //--- H4 runs stone dead AFTER the model had trained and reported a perfectly good AUC. The same //--- family of bug already bit DFSerialize on compressed forests in this repo. //--- //--- Nothing is lost by skipping it: the fit is deferred and walk-forward by design, so it is //--- rebuilt from history-so-far on every run regardless. A file would only be a cache of //--- something cheap, bought at the price of a crash. bool Train(CMatrixDouble &xy, const int rows, const string &names[]) { //--- Embargo = the horizon. A virtual trade is a 60-bar window; rows closer than that to the //--- boundary overlap the validation set's first rows. In the training matrix each ENTRY bar //--- contributes two rows (long and short), so one horizon of bars is two horizons of rows. m_ready = m_net.Train(xy, rows, MGMT_FEATURES, names, MGMT_HORIZON * 2); m_why = m_net.Why(); return m_ready; } }; //+------------------------------------------------------------------+ string CManagementNet::FeatureName(const int i) { switch(i) { //--- HOW IT GOT HERE. A move that reached +0.5R in one bar is a different animal from one //--- that ground there over fifteen, and nothing about the entry bar can express that. case 0: return "bars_to_cross"; case 1: return "mae_before_cross_r"; // pain paid BEFORE the gain - never after it case 2: return "cross_speed_r_per_bar"; //--- WHAT THE MARKET IS DOING AT THE CROSSING. Persistence is the whole question, and these //--- two are the direct measures of it: efficient and compounding, or cancelling. case 3: return "er_at_cross"; case 4: return "vr_at_cross"; case 5: return "regime_at_cross"; //--- THE CROSSING BAR ITSELF. A wide bar closing at its extreme is an extension; a wide bar //--- closing mid-range is exhaustion, and they look the same in R. case 6: return "range_atr_at_cross"; case 7: return "close_in_bar_at_cross"; case 8: return "vol_rel_at_cross"; //--- Which way the trade is facing, so one model serves both sides rather than two models //--- each trained on half the data. case 9: return "is_long"; } return "?"; } #endif // WARRIOR_MANAGEMENT_NET_MQH