Warrior_EA/Enumerations/InputEnums.mqh

513 lines
23 KiB
MQL5
Raw Permalink Normal View History

fix(ai): drop the conv pooling stage - it reduced across filters, not time FeedForwardConv emits POSITION-MAJOR output, matrix_o[out + window_out * i], so one bar's window_out filter responses are contiguous and consecutive bars sit window_out apart. Both pooling implementations (FeedForwardProof and CPU_FeedForwardProof) slide FLAT over that buffer - pos = i * step, reducing `window` CONSECUTIVE elements. On a position-major layout those neighbours are different FILTERS of the same bar, never one filter across time. At the shipped 3/2 the pool computed max(bar0_f0, bar0_f1, bar0_f2), then max(bar0_f2, bar0_f3, bar0_f4), with every 8th window straddling a bar boundary. So it collapsed unrelated feature detectors into whichever fired hardest, passed gradient to that winner only, and halved the feature map while doing it - all below every learnable layer, where nothing above can recover it. The removed inputs' own labels ("3 Bars") show time-axis pooling was the intent throughout. Measured cost: CONV sat pinned at ~40% balanced accuracy for 510 eras with Sell recall 0%, while plain MLPs on the same data reached 57-61%. HYBRID, which also carried this stage, came second-worst of the batch-norm group. Not fixable in the topology: pooling one filter across time needs a stride of window_out BETWEEN samples within a window, which a consecutive-window kernel cannot express at any window/step. That needs a stride-aware kernel in Network.cl + WarriorCPU.cpp + WarriorDML.cpp and a DLL rebuild, and is only worth doing if a conv front-end earns its place without downsampling first - with 20 sliding positions there is little to gain by halving them. ConvPoolWindow/ConvPoolStep and their enums are removed with it, along with the |CP: fingerprint term added earlier today. Both builds compile 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 19:28:44 -04:00
//+------------------------------------------------------------------+
//| CustomEnums.mqh |
//| AnimateDread |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "AnimateDread"
#property link "https://www.mql5.com"
//--- Weight-update optimizer. This is really an AI\Network.mqh library type; a guarded duplicate is
//--- kept here so Variables\Inputs.mqh (which uses it for the TrainingOptimizer input) can be included
//--- before the AI headers - putting the EA's own inputs at the top of the Inputs tab. Keep in sync
//--- with AI\Network.mqh's copy; the shared WARRIOR_ENUM_OPTIMIZATION_DEFINED guard prevents a
//--- duplicate definition whichever header is parsed first.
#ifndef WARRIOR_ENUM_OPTIMIZATION_DEFINED
#define WARRIOR_ENUM_OPTIMIZATION_DEFINED
//--- A third DFA entry was removed 2026-07-28 - see AI\Network.mqh's copy for the full rationale (it was
//--- a deterministic index-parity sign flip on the gradient, i.e. ascent on half of every weight tensor,
//--- not Direct Feedback Alignment). SGD/ADAM keep ordinals 0/1: they feed the weights-filename
//--- fingerprint and must never be renumbered.
enum ENUM_OPTIMIZATION
{
SGD, // SGD + Momentum (heavy-ball, simpler, needs more eras)
ADAM // Adam (adaptive step, faster convergence, can overfit)
};
#endif
//--- Logical, commonly-used Moving Average / RSI periods only - keeps the Classic Signals inputs (and
//--- the AutoTuneIndicators search space over them, see ADIndicatorTuner.mqh) from being set/perturbed
//--- to an arbitrary, non-standard period.
enum MA_PERIOD_PRESETS
{
MA_PERIOD_5 = 5, // 5
MA_PERIOD_8 = 8, // 8
MA_PERIOD_9 = 9, // 9
MA_PERIOD_10 = 10, // 10
MA_PERIOD_13 = 13, // 13
MA_PERIOD_20 = 20, // 20
MA_PERIOD_21 = 21, // 21
MA_PERIOD_50 = 50, // 50
MA_PERIOD_100 = 100, // 100
MA_PERIOD_200 = 200, // 200
};
//--- Unified moving-average TYPE, spanning the advanced/institutional MAs AND the standard methods, all
//--- served by the one CustomIndicators\ADMovingAverage.mq5. VALUES ARE THE INDICATOR'S OWN InpType codes
//--- and MUST stay in sync with it: 0..4 (ALMA/DEMA/ZLEMA/T3/Kalman) are the original codes, unchanged for
//--- cross-platform parity with the SQX build; 5..8 (SMA/EMA/SMMA/LWMA) were added on top. Drives both the
//--- classic MA vote (Signals\SignalMA.mqh) and the NN MA input feature, and is auto-tuner-searchable.
enum MA_TYPE_PRESETS
{
MA_TYPE_ALMA = 0, // ALMA (Arnaud Legoux)
MA_TYPE_DEMA = 1, // DEMA (double exponential)
MA_TYPE_ZLEMA = 2, // ZLEMA (zero-lag)
MA_TYPE_T3 = 3, // T3 (Tillson)
MA_TYPE_KALMAN = 4, // Kalman filter
MA_TYPE_SMA = 5, // SMA (simple)
MA_TYPE_EMA = 6, // EMA (exponential)
MA_TYPE_SMMA = 7, // SMMA (smoothed)
MA_TYPE_LWMA = 8, // LWMA (linear weighted)
};
enum RSI_PERIOD_PRESETS
{
RSI_PERIOD_2 = 2, // 2
RSI_PERIOD_5 = 5, // 5
RSI_PERIOD_7 = 7, // 7
RSI_PERIOD_9 = 9, // 9
RSI_PERIOD_14 = 14, // 14 (classic)
RSI_PERIOD_21 = 21, // 21
RSI_PERIOD_25 = 25, // 25
};
//--- MACD periods (Signals\SignalMACD.mqh classic vote + the MACD input feature). The preset SETS are
//--- deliberately chosen so that EVERY fast/slow combination satisfies CSignalMACD::ValidationSettings()'s
//--- "slow must exceed fast" rule - the fast list tops out at 15, the slow list starts at 17. A trader
//--- picking two legal-looking values from the dropdowns can therefore never produce a combination that
//--- fails init, and the auto-tuner (ADIndicatorTuner::PerturbRandom) can perturb either one in isolation
//--- without having to know the other's current value.
enum MACD_FAST_PRESETS
{
MACD_FAST_5 = 5, // 5
MACD_FAST_8 = 8, // 8
MACD_FAST_12 = 12, // 12 (classic)
MACD_FAST_15 = 15, // 15
};
enum MACD_SLOW_PRESETS
{
MACD_SLOW_17 = 17, // 17
MACD_SLOW_21 = 21, // 21
MACD_SLOW_26 = 26, // 26 (classic)
MACD_SLOW_34 = 34, // 34
MACD_SLOW_50 = 50, // 50
};
enum MACD_SIGNAL_PRESETS
{
MACD_SIGNAL_5 = 5, // 5
MACD_SIGNAL_7 = 7, // 7
MACD_SIGNAL_9 = 9, // 9 (classic)
MACD_SIGNAL_12 = 12, // 12
};
//--- Ichimoku periods (Signals\SignalIchimoku.mqh classic vote + the Ichimoku input feature). Same
//--- all-combinations-are-legal design as the MACD presets above, against
//--- CSignalIchimoku::ValidationSettings()'s "Tenkan < Kijun < Senkou B" rule: Tenkan tops out at 20,
//--- Kijun spans 22-40, Senkou B starts at 44. The classic 9/26/52 triple is in the middle of each.
enum ICHIMOKU_TENKAN_PRESETS
{
ICHI_TENKAN_7 = 7, // 7
ICHI_TENKAN_9 = 9, // 9 (classic)
ICHI_TENKAN_12 = 12, // 12
ICHI_TENKAN_20 = 20, // 20
};
enum ICHIMOKU_KIJUN_PRESETS
{
ICHI_KIJUN_22 = 22, // 22
ICHI_KIJUN_26 = 26, // 26 (classic)
ICHI_KIJUN_30 = 30, // 30
ICHI_KIJUN_40 = 40, // 40
};
enum ICHIMOKU_SENKOU_PRESETS
{
ICHI_SENKOU_44 = 44, // 44
ICHI_SENKOU_52 = 52, // 52 (classic)
ICHI_SENKOU_60 = 60, // 60
ICHI_SENKOU_120 = 120, // 120
};
//--- custom enumerations for certain settings, minimizes overfitting
enum IND_PERIODS_PRESETS
{
PERIOD_5 = 5, // 5 Periods
PERIOD_10 = 10, // 10 Periods
PERIOD_14 = 14, // 14 Periods (classic)
PERIOD_20 = 20, // 20 Periods
PERIOD_30 = 30, // 30 Periods
PERIOD_50 = 50, // 50 Periods
PERIOD_100 = 100, // 100 Periods
PERIOD_200 = 200, // 200 Periods
};
enum TRAINING_YEARS_PRESET
{
YEARS_1 = 1, // 1 year
YEARS_2 = 2, // 2 years
YEARS_5 = 5, // 5 years
YEARS_10 = 10, // 10 years
YEARS_20 = 20, // 20 years
};
feat(trade): anchor SL and TP to the entry price, not the last swing Stops keyed to the recent swing extreme make a trade's risk a function of how far the last swing happens to sit rather than of current volatility. On a shallow pullback the swing sits close to the fill, so the stop is tight enough to be taken out by noise on setups that then run to target - which is what the Perceptron's signals were showing. SL: lowest_low/highest_high -/+ mult*ATR -> entry -/+ mult*ATR TP: TP_PREV_SWING (opposite swing) -> removed; ATR-from-entry SL_PREV_SWING, TP_PREV_SWING -> removed from the enums The SL anchors to `price` (the resolved entry), not to base_price: with a pending entry those differ by the whole entry offset, and the risk Money sizes against is entry-to-stop. MIN_SL_ATR_MULTIPLIER 2.0 -> 0.5. That floor existed because a swing- anchored stop could land arbitrarily close to the entry and needed a bound unrelated to the chosen multiple. An entry-anchored stop is exactly mult*ATR by construction and cannot collapse, so leaving it at 2.0 would have silently overridden SL_ATR_x1 to 2*ATR - making the input a lie AND forcing TP >= 4*ATR just to clear the default 1:2 rejection filter. The broker's own stop level is enforced separately and precisely by TCAdjustStops(), so this is now a pure sanity net. Default TP_Mode TP_PREV_SWING -> TP_ATR_x3, so SL_ATR_x1 + TP_ATR_x3 gives a realised 3:1 against the 1:2 filter. TP_ATR_x2 would sit EXACTLY on the 2.0 boundary where price-normalization rounding alone can reject the setup; the default leaves a deliberate gap. This is the same interaction that once rejected 100% of setups on every symbol (see TP_INTELLIGENT_BASE_RR). Swing validity guards now reject only when the configuration actually uses a swing - i.e. ENTRY_PREV_SWING. Previously an unsynced or thin history rejected EVERY trade, including configurations whose levels no longer reference a swing at all. The guards are kept, not deleted: a bad swing must still never reach an entry price, and iLow/iHigh are no longer called with a possibly-negative index. TP_INTELLIGENT stays risk-relative. Now that risk is exactly mult*ATR the risk- and ATR-relative forms coincide, but risk-relative keeps its reward:risk guarantee exact after the floor or TCAdjustStops widens a stop. Both builds compile 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:11:13 -04:00
//--- Stop-loss sizing mode. The ATR_* presets place the SL a fixed multiple of ATR FROM THE ENTRY
//--- PRICE. SL_INTELLIGENT uses the same entry anchor but tightens the distance as live AI/DB
//--- confidence rises (see CExpertSignalCustom::OpenParams()'s AI_SL_TIGHTEN_FACTOR) - a
//--- high-conviction setup gets a tighter stop, a marginal one keeps the full ATR cushion. Negative
//--- sentinel so it can never be mistaken for a literal ATR multiple.
//--- SWING-ANCHORED STOPS WERE REMOVED 2026-07-31. Both the ATR presets ("N ATR beyond the swing") and
//--- SL_PREV_SWING ("exactly at the swing") keyed the stop to the recent swing high/low, which makes
//--- the risk on a trade a function of how far away the last swing happens to sit rather than of
//--- current volatility: a shallow pullback produced a stop tight enough to be taken out by noise on a
//--- setup that then ran to target. Anchoring to the entry makes risk exactly N*ATR by construction,
feat: remove Min_Risk_Reward_Ratio - a guess was overriding a measurement The barrier geometry is derived from the instrument's own excursion distribution (stop at q75 of adverse travel, target at q50 of favourable), and then a 1:2 floor was applied on top, raising the target to twice whatever the stop happened to be. On SP500 H1 that pushed the target to 6.66*ATR, reached on 3.3% of bars inside the horizon - so the label became "almost never a win" and every topology was trained to predict an event that essentially does not occur. A measured target has to stay measured. The ratio never bought what it was believed to buy. A reward:risk floor does not create expectancy; it trades hit rate against payoff at a break-even the geometry already fixes - which this project has separately MEASURED (payoff 0.92 -> 5.72 with expectancy flat). What it did buy was two outages: four consecutive Market validation rejections for "no trading operations" when it rejected 100% of setups, and the label corruption above. Removed: - the input and the RISK_REWARD_RATIO enum (deleted, not left dangling - a live enum with no input behind it is the shape of the stale-.set incident that trained ~250 eras on the wrong target) - the forced target raise in the label geometry - the rrOK eligibility gate in the barrier-geometry scan, so every unclamped pairing now competes on the measurement alone. Clamping stays disqualifying for its own unrelated reason. - the reward < minRR*risk veto in OpenParams Kept: g_TradeRewardRiskRatio still computed and still bridged to Kelly sizing in MoneyIntelligent - the ratio as a SIZING input was always the sound use. Risk stays bounded where it actually is - account risk % and CRiskBudget. The low-reachability warning survives but is re-aimed: with nothing inflating the target, a target the market rarely reaches can only mean the horizon is truncating the excursions the geometry is derived from. Both build variants compile 0 errors / 0 warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 14:51:59 -04:00
//--- which is also what kept the old minimum-reward:risk rejection satisfiable without depending on
//--- swing geometry. That filter is gone (2026-08-09); the coupling is still the right shape.
refactor(inputs): 96 -> 70 inputs; remove two untested/unusable filter modules Every removal below is FINGERPRINT-NEUTRAL by construction: each retired input is pinned to the exact value it already shipped with, so running models keep their filenames and resume rather than restarting at era 0. Verified field by field against BuildConfigFingerprint. Removed as inputs, kept as pinned constants (the value was never a preference the user had a basis to change): - OutputNeuronsCount. The regression head predicts a continuous quantity the triple-barrier label does not contain; the target is an EVENT, so the right output is its probability. The regression code paths stay implemented and dormant - they cost nothing and removing them would touch every scoring path at once. - MinRecall. A safety floor, not a preference, and the only direction a user can move it is the harmful one: raising it past what the config reaches yields NO model, not a better one (observed repeatedly at 60). - SwingConfirmationBars. Stopped gating the labels with the relabel, but is STILL load-bearing for the swing-context input features - it is the ZigZag repainting embargo, and without it those 9 features read a leg the live bar could not have had yet. Pinned, not deleted. - MaxErasPerRun (runaway backstop, never reached in a healthy run), FreezePriorCalibration (unanswerable by a user; near-balanced labels make the priors stable anyway), VerboseMode (developer view, joins DebuggingMode), MACD/Ichimoku periods x6 (both indicators ship disabled, and as optimizer dimensions they are pure overfitting surface - the AI auto-tuner is the supported way to move them). - SignalClusterWindow -> 3, no longer an input. Barrier labels make consecutive setups real, which argued for 0; it is not 0 because on D1+ a 6-bar window spans over a week and two arrows a day apart on a weekly-scale move are one event. 3 splits it correctly by timeframe. - EnableOnlineLearning -> ON. Adapting to a changing market is what keeps a months-attached model from going stale, and the rolling-accuracy freeze is what makes it safe. See the caveat noted in the handoff: it had not been forward-tested on a live feed when this became default. Removed entirely: - Intraday Time Filter (5 inputs + Signals/SignalITF.mqh). Two of its five inputs were raw BITMASKS, which is an implementation detail exposed as a control. The job is covered three times over by things that are declarative or that learn: the session filter, the time-of-day/day-of-week input features (the network discovers which hours are good rather than being told), and the journal's time buckets. - Market Depth Filter (5 inputs + Signals/SignalMarketDepth.mqh, plus its OnInit probe and OnDeinit release). It needs real level-2 data that this broker - and most retail MT5 brokers - do not provide, so the module has never once executed against real data. Shipping four tuning dropdowns for an untested path is worse than shipping nothing: the only users who could enable it would be its first-ever testers, live. If DOM returns it should be a FEATURE fed to the network, not a rule-based veto with hand-tuned thresholds - imbalance is data. - IndicatorTuneTrials, replaced by ComputeTuneTrialBudget(). The useful budget depends on how many parameters are actually being searched, which depends on which features are enabled - so one number meant wildly different things run to run. The shipped 32 was ~10 candidates per dimension against one enabled indicator (wasteful: each costs GA_SEEDS full training runs) and under one per dimension against all nine (blind). Now population ~ 4 x active dimensions, clamped [8,64], with CADIndicatorTuner::ActiveDimensions() defined immediately above PerturbRandom() so the two cannot drift apart. - Six orphaned enums (TUNE_TRIALS_PRESET, DOM_*, ENTRY_HOUR_OF_DAY, TIME_FILTER_DAY_OF_WEEK), 81 lines. Other UX: - SL_ATR_x1 / TP_ATR_x3 now carry the "(classic)" default marker every other preset enum in the file already used. Nothing in the SL/TP dropdowns previously told a user which pair was the shipped default - which matters far more since the relabel, because those two define the labels and changing either forces a retrain. - Neural Network section moved directly ABOVE AI Input Features: choose the architecture, then choose what it sees. NN Optimizer / Performance stays last - the Adam/Sgd inputs are declared in AI/Network.mqh and render immediately after that divider. - News feature + window moved to the end of the AI feature list, below Wyckoff Bar Inversion. - Dropped "(0-100)" from Min vote to open - it is an enum, not a number. Both builds compile 0 errors / 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 21:22:02 -04:00
//--- The "(classic)" marker on the shipped default follows the same convention as every period preset
//--- below. It matters more here than anywhere else in this file: since the 2026-08-01 triple-barrier
//--- relabel, SL_Mode and TP_Mode DEFINE THE TRAINING LABELS, so they are in the weights-filename
//--- fingerprint and changing either one re-keys the model and starts a fresh retrain. A user needs to
//--- be able to see which pair the shipped model was actually trained on.
enum STOP_LOSS_MODE
{
SL_INTELLIGENT = -1, // Intelligent (AI-confidence scaled)
perf(autotune): replace the genetic search with a filter score - hours to seconds MEASURED COST OF THE GA, which is what retired it. Per generation: rung 0: 8 cand x 3 seeds x 3 eras = 72 eras rung 1: 4 cand x 3 seeds x 8 eras = 96 rung 2: 2 cand x 3 seeds x 20 eras = 120 = 288 eras/generation x 4 generations = 1152 eras BEFORE the winner's real training began. Against the observed era times on SP500 H1: PAI 29.1 s/era -> 9.3 h (matches the observed 00:37 -> 09:22) CONV 41.3 s/era -> 13.2 h LSTM 150.4 s/era -> 48.1 h HYBRID 154.6 s/era -> 49.5 h Two days to tune is not a first-run experience, and it is the phase in which the panel goes quiet, which is what made it look like a hang. It also bought nothing. The space is 90 points (10 MA periods x 9 MA types), so 1152 evaluations revisited each point ~13 times; and rungs of 3 and 8 eras cannot separate two MA periods at all. The 2026-08-01 run proves it: every finalist scored 25.0-25.9% balanced accuracy - below the 33.3% one-class floor, i.e. indistinguishable noise - and the search then "deployed the winner" of that. THE ERROR WAS THE SCORING FUNCTION, not its constants. Using a full training run to choose a feature's period is a wrapper method paying wrapper prices for a decision that does not need one. The reference book does not do this: ch. 3.3 selects inputs by measuring each candidate indicator's CORRELATION with the target and dropping the ones with none, with no network involved. So: rank candidates by the MUTUAL INFORMATION between the resulting feature vector and the triple-barrier label. MI rather than correlation because the label is 3-class categorical and the features are not monotonically related to it. Equal-FREQUENCY binning (rank-based), because these features are ATR-normalised and heavy-tailed - fixed-width bins put nearly everything in one bucket and report ~0 information for a genuinely useful feature. Scoring is arithmetic over the feature cache, so it costs seconds and its cost is independent of topology: LSTM now tunes as fast as the MLP. Coordinate sweep, not product sweep - cost is the SUM of per-parameter candidate counts, so enabling every indicator stays affordable - with a second pass that breaks early once nothing moves. Sampling is IS-ONLY. Letting the OOS window influence which indicator settings ship would mean the holdout had been used for selection and had stopped being a holdout. HONEST LIMIT, recorded because it is the price: MI is marginal, so a parameter that only pays off in combination with another can be missed (Guyon & Elisseeff 2003, filter vs wrapper). Given the wrapper it replaces was ranking pure noise at 48 h a run, this is strictly better. Deleted with it: GaRungEras/GaExtract/GaStore/GaMutate/GaRandomCandidate/ GaBlockCrossover/GaSortAliveByScoreDesc/GaBreedNextGeneration, 14 m_ga* members, the GA_*/TUNE_POP_* constants, and ComputeTuneTrialBudget. AND m_evalMode/m_evalEraBudget, because nothing set them any more - 28 read sites all permanently inert. That is not a tidy-up: the `if (!m_evalMode)` guard on UpdateClassPriors is exactly what silently disabled the imbalance correction for entire runs two commits ago. Dead machinery that still reads like live machinery is this codebase's most expensive recurring bug, and leaving 28 more instances of it would have been indefensible. The panel's tuning-progress state goes too - tuning no longer takes long enough to need one. Both builds compile 0 errors / 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 10:29:31 -04:00
SL_ATR_x1 = 1, // ATR * 1 from entry
feat(trade): anchor SL and TP to the entry price, not the last swing Stops keyed to the recent swing extreme make a trade's risk a function of how far the last swing happens to sit rather than of current volatility. On a shallow pullback the swing sits close to the fill, so the stop is tight enough to be taken out by noise on setups that then run to target - which is what the Perceptron's signals were showing. SL: lowest_low/highest_high -/+ mult*ATR -> entry -/+ mult*ATR TP: TP_PREV_SWING (opposite swing) -> removed; ATR-from-entry SL_PREV_SWING, TP_PREV_SWING -> removed from the enums The SL anchors to `price` (the resolved entry), not to base_price: with a pending entry those differ by the whole entry offset, and the risk Money sizes against is entry-to-stop. MIN_SL_ATR_MULTIPLIER 2.0 -> 0.5. That floor existed because a swing- anchored stop could land arbitrarily close to the entry and needed a bound unrelated to the chosen multiple. An entry-anchored stop is exactly mult*ATR by construction and cannot collapse, so leaving it at 2.0 would have silently overridden SL_ATR_x1 to 2*ATR - making the input a lie AND forcing TP >= 4*ATR just to clear the default 1:2 rejection filter. The broker's own stop level is enforced separately and precisely by TCAdjustStops(), so this is now a pure sanity net. Default TP_Mode TP_PREV_SWING -> TP_ATR_x3, so SL_ATR_x1 + TP_ATR_x3 gives a realised 3:1 against the 1:2 filter. TP_ATR_x2 would sit EXACTLY on the 2.0 boundary where price-normalization rounding alone can reject the setup; the default leaves a deliberate gap. This is the same interaction that once rejected 100% of setups on every symbol (see TP_INTELLIGENT_BASE_RR). Swing validity guards now reject only when the configuration actually uses a swing - i.e. ENTRY_PREV_SWING. Previously an unsynced or thin history rejected EVERY trade, including configurations whose levels no longer reference a swing at all. The guards are kept, not deleted: a bad swing must still never reach an entry price, and iLow/iHigh are no longer called with a possibly-negative index. TP_INTELLIGENT stays risk-relative. Now that risk is exactly mult*ATR the risk- and ATR-relative forms coincide, but risk-relative keeps its reward:risk guarantee exact after the floor or TCAdjustStops widens a stop. Both builds compile 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:11:13 -04:00
SL_ATR_x2 = 2, // ATR * 2 from entry
SL_ATR_x3 = 3, // ATR * 3 from entry
};
//--- Take-profit sizing mode. The ATR_* presets set the TP a fixed multiple of ATR FROM THE ENTRY
feat: remove Min_Risk_Reward_Ratio - a guess was overriding a measurement The barrier geometry is derived from the instrument's own excursion distribution (stop at q75 of adverse travel, target at q50 of favourable), and then a 1:2 floor was applied on top, raising the target to twice whatever the stop happened to be. On SP500 H1 that pushed the target to 6.66*ATR, reached on 3.3% of bars inside the horizon - so the label became "almost never a win" and every topology was trained to predict an event that essentially does not occur. A measured target has to stay measured. The ratio never bought what it was believed to buy. A reward:risk floor does not create expectancy; it trades hit rate against payoff at a break-even the geometry already fixes - which this project has separately MEASURED (payoff 0.92 -> 5.72 with expectancy flat). What it did buy was two outages: four consecutive Market validation rejections for "no trading operations" when it rejected 100% of setups, and the label corruption above. Removed: - the input and the RISK_REWARD_RATIO enum (deleted, not left dangling - a live enum with no input behind it is the shape of the stale-.set incident that trained ~250 eras on the wrong target) - the forced target raise in the label geometry - the rrOK eligibility gate in the barrier-geometry scan, so every unclamped pairing now competes on the measurement alone. Clamping stays disqualifying for its own unrelated reason. - the reward < minRR*risk veto in OpenParams Kept: g_TradeRewardRiskRatio still computed and still bridged to Kelly sizing in MoneyIntelligent - the ratio as a SIZING input was always the sound use. Risk stays bounded where it actually is - account risk % and CRiskBudget. The low-reachability warning survives but is re-aimed: with nothing inflating the target, a target the market rarely reaches can only mean the horizon is truncating the excursions the geometry is derived from. Both build variants compile 0 errors / 0 warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 14:51:59 -04:00
//--- PRICE (no longer derived from the reward:risk ratio - that ratio was a pure
feat(trade): anchor SL and TP to the entry price, not the last swing Stops keyed to the recent swing extreme make a trade's risk a function of how far the last swing happens to sit rather than of current volatility. On a shallow pullback the swing sits close to the fill, so the stop is tight enough to be taken out by noise on setups that then run to target - which is what the Perceptron's signals were showing. SL: lowest_low/highest_high -/+ mult*ATR -> entry -/+ mult*ATR TP: TP_PREV_SWING (opposite swing) -> removed; ATR-from-entry SL_PREV_SWING, TP_PREV_SWING -> removed from the enums The SL anchors to `price` (the resolved entry), not to base_price: with a pending entry those differ by the whole entry offset, and the risk Money sizes against is entry-to-stop. MIN_SL_ATR_MULTIPLIER 2.0 -> 0.5. That floor existed because a swing- anchored stop could land arbitrarily close to the entry and needed a bound unrelated to the chosen multiple. An entry-anchored stop is exactly mult*ATR by construction and cannot collapse, so leaving it at 2.0 would have silently overridden SL_ATR_x1 to 2*ATR - making the input a lie AND forcing TP >= 4*ATR just to clear the default 1:2 rejection filter. The broker's own stop level is enforced separately and precisely by TCAdjustStops(), so this is now a pure sanity net. Default TP_Mode TP_PREV_SWING -> TP_ATR_x3, so SL_ATR_x1 + TP_ATR_x3 gives a realised 3:1 against the 1:2 filter. TP_ATR_x2 would sit EXACTLY on the 2.0 boundary where price-normalization rounding alone can reject the setup; the default leaves a deliberate gap. This is the same interaction that once rejected 100% of setups on every symbol (see TP_INTELLIGENT_BASE_RR). Swing validity guards now reject only when the configuration actually uses a swing - i.e. ENTRY_PREV_SWING. Previously an unsynced or thin history rejected EVERY trade, including configurations whose levels no longer reference a swing at all. The guards are kept, not deleted: a bad swing must still never reach an entry price, and iLow/iHigh are no longer called with a possibly-negative index. TP_INTELLIGENT stays risk-relative. Now that risk is exactly mult*ATR the risk- and ATR-relative forms coincide, but risk-relative keeps its reward:risk guarantee exact after the floor or TCAdjustStops widens a stop. Both builds compile 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:11:13 -04:00
//--- rejection filter). TP_INTELLIGENT scales the target UP with confidence (lets high-conviction
//--- winners run further). Negative sentinel as above.
//--- TP_PREV_SWING REMOVED 2026-07-31 alongside the swing-anchored stops: targeting the opposite swing
//--- caps the reward at whatever structure happens to be overhead, which on a trending signal exits
//--- well before the move is done and, paired with a swing-anchored stop, made the realised
//--- reward:risk a property of the chart's geometry rather than of the setup.
enum TAKE_PROFIT_MODE
{
TP_INTELLIGENT = -1, // Intelligent (AI-confidence scaled)
TP_ATR_x1 = 1, // ATR * 1 from entry
TP_ATR_x2 = 2, // ATR * 2 from entry
perf(autotune): replace the genetic search with a filter score - hours to seconds MEASURED COST OF THE GA, which is what retired it. Per generation: rung 0: 8 cand x 3 seeds x 3 eras = 72 eras rung 1: 4 cand x 3 seeds x 8 eras = 96 rung 2: 2 cand x 3 seeds x 20 eras = 120 = 288 eras/generation x 4 generations = 1152 eras BEFORE the winner's real training began. Against the observed era times on SP500 H1: PAI 29.1 s/era -> 9.3 h (matches the observed 00:37 -> 09:22) CONV 41.3 s/era -> 13.2 h LSTM 150.4 s/era -> 48.1 h HYBRID 154.6 s/era -> 49.5 h Two days to tune is not a first-run experience, and it is the phase in which the panel goes quiet, which is what made it look like a hang. It also bought nothing. The space is 90 points (10 MA periods x 9 MA types), so 1152 evaluations revisited each point ~13 times; and rungs of 3 and 8 eras cannot separate two MA periods at all. The 2026-08-01 run proves it: every finalist scored 25.0-25.9% balanced accuracy - below the 33.3% one-class floor, i.e. indistinguishable noise - and the search then "deployed the winner" of that. THE ERROR WAS THE SCORING FUNCTION, not its constants. Using a full training run to choose a feature's period is a wrapper method paying wrapper prices for a decision that does not need one. The reference book does not do this: ch. 3.3 selects inputs by measuring each candidate indicator's CORRELATION with the target and dropping the ones with none, with no network involved. So: rank candidates by the MUTUAL INFORMATION between the resulting feature vector and the triple-barrier label. MI rather than correlation because the label is 3-class categorical and the features are not monotonically related to it. Equal-FREQUENCY binning (rank-based), because these features are ATR-normalised and heavy-tailed - fixed-width bins put nearly everything in one bucket and report ~0 information for a genuinely useful feature. Scoring is arithmetic over the feature cache, so it costs seconds and its cost is independent of topology: LSTM now tunes as fast as the MLP. Coordinate sweep, not product sweep - cost is the SUM of per-parameter candidate counts, so enabling every indicator stays affordable - with a second pass that breaks early once nothing moves. Sampling is IS-ONLY. Letting the OOS window influence which indicator settings ship would mean the holdout had been used for selection and had stopped being a holdout. HONEST LIMIT, recorded because it is the price: MI is marginal, so a parameter that only pays off in combination with another can be missed (Guyon & Elisseeff 2003, filter vs wrapper). Given the wrapper it replaces was ranking pure noise at 48 h a run, this is strictly better. Deleted with it: GaRungEras/GaExtract/GaStore/GaMutate/GaRandomCandidate/ GaBlockCrossover/GaSortAliveByScoreDesc/GaBreedNextGeneration, 14 m_ga* members, the GA_*/TUNE_POP_* constants, and ComputeTuneTrialBudget. AND m_evalMode/m_evalEraBudget, because nothing set them any more - 28 read sites all permanently inert. That is not a tidy-up: the `if (!m_evalMode)` guard on UpdateClassPriors is exactly what silently disabled the imbalance correction for entire runs two commits ago. Dead machinery that still reads like live machinery is this codebase's most expensive recurring bug, and leaving 28 more instances of it would have been indefensible. The panel's tuning-progress state goes too - tuning no longer takes long enough to need one. Both builds compile 0 errors / 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 10:29:31 -04:00
TP_ATR_x3 = 3, // ATR * 3 from entry
TP_ATR_x4 = 4, // ATR * 4 from entry
TP_ATR_x6 = 6, // ATR * 6 from entry
TP_ATR_x8 = 8, // ATR * 8 from entry
TP_ATR_x10 = 10, // ATR * 10 from entry
};
feat: remove Min_Risk_Reward_Ratio - a guess was overriding a measurement The barrier geometry is derived from the instrument's own excursion distribution (stop at q75 of adverse travel, target at q50 of favourable), and then a 1:2 floor was applied on top, raising the target to twice whatever the stop happened to be. On SP500 H1 that pushed the target to 6.66*ATR, reached on 3.3% of bars inside the horizon - so the label became "almost never a win" and every topology was trained to predict an event that essentially does not occur. A measured target has to stay measured. The ratio never bought what it was believed to buy. A reward:risk floor does not create expectancy; it trades hit rate against payoff at a break-even the geometry already fixes - which this project has separately MEASURED (payoff 0.92 -> 5.72 with expectancy flat). What it did buy was two outages: four consecutive Market validation rejections for "no trading operations" when it rejected 100% of setups, and the label corruption above. Removed: - the input and the RISK_REWARD_RATIO enum (deleted, not left dangling - a live enum with no input behind it is the shape of the stale-.set incident that trained ~250 eras on the wrong target) - the forced target raise in the label geometry - the rrOK eligibility gate in the barrier-geometry scan, so every unclamped pairing now competes on the measurement alone. Clamping stays disqualifying for its own unrelated reason. - the reward < minRR*risk veto in OpenParams Kept: g_TradeRewardRiskRatio still computed and still bridged to Kelly sizing in MoneyIntelligent - the ratio as a SIZING input was always the sound use. Risk stays bounded where it actually is - account risk % and CRiskBudget. The low-reachability warning survives but is re-aimed: with nothing inflating the target, a target the market rarely reaches can only mean the horizon is truncating the excursions the geometry is derived from. Both build variants compile 0 errors / 0 warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 14:51:59 -04:00
//--- RISK_REWARD_RATIO removed 2026-08-09 along with its only consumer, the Min_Risk_Reward_Ratio
//--- input. Deleted rather than left dangling: a live enum with no input behind it is exactly the shape
//--- of the 2026-07 incident where a saved .set kept feeding a deleted option's ordinal back in and
//--- trained ~250 eras on the wrong target (MT5 does not validate saved enum inputs). See
//--- Variables\Inputs.mqh for why the ratio itself had to go.
enum MONEY_RISK_PERCENT_PRESET
{
perf(autotune): replace the genetic search with a filter score - hours to seconds MEASURED COST OF THE GA, which is what retired it. Per generation: rung 0: 8 cand x 3 seeds x 3 eras = 72 eras rung 1: 4 cand x 3 seeds x 8 eras = 96 rung 2: 2 cand x 3 seeds x 20 eras = 120 = 288 eras/generation x 4 generations = 1152 eras BEFORE the winner's real training began. Against the observed era times on SP500 H1: PAI 29.1 s/era -> 9.3 h (matches the observed 00:37 -> 09:22) CONV 41.3 s/era -> 13.2 h LSTM 150.4 s/era -> 48.1 h HYBRID 154.6 s/era -> 49.5 h Two days to tune is not a first-run experience, and it is the phase in which the panel goes quiet, which is what made it look like a hang. It also bought nothing. The space is 90 points (10 MA periods x 9 MA types), so 1152 evaluations revisited each point ~13 times; and rungs of 3 and 8 eras cannot separate two MA periods at all. The 2026-08-01 run proves it: every finalist scored 25.0-25.9% balanced accuracy - below the 33.3% one-class floor, i.e. indistinguishable noise - and the search then "deployed the winner" of that. THE ERROR WAS THE SCORING FUNCTION, not its constants. Using a full training run to choose a feature's period is a wrapper method paying wrapper prices for a decision that does not need one. The reference book does not do this: ch. 3.3 selects inputs by measuring each candidate indicator's CORRELATION with the target and dropping the ones with none, with no network involved. So: rank candidates by the MUTUAL INFORMATION between the resulting feature vector and the triple-barrier label. MI rather than correlation because the label is 3-class categorical and the features are not monotonically related to it. Equal-FREQUENCY binning (rank-based), because these features are ATR-normalised and heavy-tailed - fixed-width bins put nearly everything in one bucket and report ~0 information for a genuinely useful feature. Scoring is arithmetic over the feature cache, so it costs seconds and its cost is independent of topology: LSTM now tunes as fast as the MLP. Coordinate sweep, not product sweep - cost is the SUM of per-parameter candidate counts, so enabling every indicator stays affordable - with a second pass that breaks early once nothing moves. Sampling is IS-ONLY. Letting the OOS window influence which indicator settings ship would mean the holdout had been used for selection and had stopped being a holdout. HONEST LIMIT, recorded because it is the price: MI is marginal, so a parameter that only pays off in combination with another can be missed (Guyon & Elisseeff 2003, filter vs wrapper). Given the wrapper it replaces was ranking pure noise at 48 h a run, this is strictly better. Deleted with it: GaRungEras/GaExtract/GaStore/GaMutate/GaRandomCandidate/ GaBlockCrossover/GaSortAliveByScoreDesc/GaBreedNextGeneration, 14 m_ga* members, the GA_*/TUNE_POP_* constants, and ComputeTuneTrialBudget. AND m_evalMode/m_evalEraBudget, because nothing set them any more - 28 read sites all permanently inert. That is not a tidy-up: the `if (!m_evalMode)` guard on UpdateClassPriors is exactly what silently disabled the imbalance correction for entire runs two commits ago. Dead machinery that still reads like live machinery is this codebase's most expensive recurring bug, and leaving 28 more instances of it would have been indefensible. The panel's tuning-progress state goes too - tuning no longer takes long enough to need one. Both builds compile 0 errors / 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 10:29:31 -04:00
RISK_PCT_1 = 1, // 1
RISK_PCT_2 = 2, // 2
RISK_PCT_3 = 3, // 3
RISK_PCT_4 = 4, // 4
RISK_PCT_5 = 5, // 5
};
enum BARS_EXPIRATION
{
BARS_X1 = 1, // 1 Candle
BARS_X2 = 2, // 2 Candles
BARS_X3 = 3, // 3 Candles
BARS_X5 = 5, // 5 Candles
BARS_X10 = 10, // 10 Candles
BARS_X20 = 20, // 20 Candles
};
//--- Entry order placement. All ATR offsets are measured from the CURRENT price (bid/ask), NOT the
//--- swing - this is the deliberate change for stability. Sign picks the side, magnitude is the ATR
//--- multiple:
//--- MARKET - fill immediately at market.
//--- LIMIT_*xATR - pending LIMIT that many ATR on the favorable side of bid/ask (buy below /
//--- sell above): wait for a pullback into a better price.
//--- STOP_*xATR - pending STOP that many ATR on the breakout side of bid/ask (buy above /
//--- sell below): enter on continuation.
//--- ENTRY_PREV_SWING - pending order anchored at the recent swing (buy at the lookback swing low /
//--- sell at the swing high) - the one swing-anchored option kept as a choice.
//--- ENTRY_INTELLIGENT - AI-confidence-scaled LIMIT pullback from bid/ask: a deep pullback when
//--- confidence is low, collapsing to a market fill as confidence -> 1 (grab
//--- high-conviction setups, demand a better price on marginal ones).
//--- Non-MARKET results that clear the broker's stop-level distance become a pending order that
//--- auto-expires after Signal_Expiration bars; anything closer just fills at market
//--- (CExpertTrade::Buy/Sell handle the market-vs-limit-vs-stop routing off this price natively).
enum ENTRY_MULTIPLIER
{
ENTRY_INTELLIGENT = -100, // Intelligent (AI-confidence scaled limit pullback)
perf(autotune): replace the genetic search with a filter score - hours to seconds MEASURED COST OF THE GA, which is what retired it. Per generation: rung 0: 8 cand x 3 seeds x 3 eras = 72 eras rung 1: 4 cand x 3 seeds x 8 eras = 96 rung 2: 2 cand x 3 seeds x 20 eras = 120 = 288 eras/generation x 4 generations = 1152 eras BEFORE the winner's real training began. Against the observed era times on SP500 H1: PAI 29.1 s/era -> 9.3 h (matches the observed 00:37 -> 09:22) CONV 41.3 s/era -> 13.2 h LSTM 150.4 s/era -> 48.1 h HYBRID 154.6 s/era -> 49.5 h Two days to tune is not a first-run experience, and it is the phase in which the panel goes quiet, which is what made it look like a hang. It also bought nothing. The space is 90 points (10 MA periods x 9 MA types), so 1152 evaluations revisited each point ~13 times; and rungs of 3 and 8 eras cannot separate two MA periods at all. The 2026-08-01 run proves it: every finalist scored 25.0-25.9% balanced accuracy - below the 33.3% one-class floor, i.e. indistinguishable noise - and the search then "deployed the winner" of that. THE ERROR WAS THE SCORING FUNCTION, not its constants. Using a full training run to choose a feature's period is a wrapper method paying wrapper prices for a decision that does not need one. The reference book does not do this: ch. 3.3 selects inputs by measuring each candidate indicator's CORRELATION with the target and dropping the ones with none, with no network involved. So: rank candidates by the MUTUAL INFORMATION between the resulting feature vector and the triple-barrier label. MI rather than correlation because the label is 3-class categorical and the features are not monotonically related to it. Equal-FREQUENCY binning (rank-based), because these features are ATR-normalised and heavy-tailed - fixed-width bins put nearly everything in one bucket and report ~0 information for a genuinely useful feature. Scoring is arithmetic over the feature cache, so it costs seconds and its cost is independent of topology: LSTM now tunes as fast as the MLP. Coordinate sweep, not product sweep - cost is the SUM of per-parameter candidate counts, so enabling every indicator stays affordable - with a second pass that breaks early once nothing moves. Sampling is IS-ONLY. Letting the OOS window influence which indicator settings ship would mean the holdout had been used for selection and had stopped being a holdout. HONEST LIMIT, recorded because it is the price: MI is marginal, so a parameter that only pays off in combination with another can be missed (Guyon & Elisseeff 2003, filter vs wrapper). Given the wrapper it replaces was ranking pure noise at 48 h a run, this is strictly better. Deleted with it: GaRungEras/GaExtract/GaStore/GaMutate/GaRandomCandidate/ GaBlockCrossover/GaSortAliveByScoreDesc/GaBreedNextGeneration, 14 m_ga* members, the GA_*/TUNE_POP_* constants, and ComputeTuneTrialBudget. AND m_evalMode/m_evalEraBudget, because nothing set them any more - 28 read sites all permanently inert. That is not a tidy-up: the `if (!m_evalMode)` guard on UpdateClassPriors is exactly what silently disabled the imbalance correction for entire runs two commits ago. Dead machinery that still reads like live machinery is this codebase's most expensive recurring bug, and leaving 28 more instances of it would have been indefensible. The panel's tuning-progress state goes too - tuning no longer takes long enough to need one. Both builds compile 0 errors / 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 10:29:31 -04:00
//ENTRY_PREV_SWING = -101, // Pending at previous swing low (buy) / swing high (sell)
LIMIT_3xATR = -3, // Limit 3x ATR from bid/ask
LIMIT_2xATR = -2, // Limit 2x ATR from bid/ask
LIMIT_1xATR = -1, // Limit 1x ATR from bid/ask
MARKET = 0, // Market order
STOP_1xATR = 1, // Stop 1x ATR from bid/ask
STOP_2xATR = 2, // Stop 2x ATR from bid/ask
STOP_3xATR = 3, // Stop 3x ATR from bid/ask
};
enum TRAILING_STRATEGY
{
TRAILING_STRATEGY_NONE, // No Trailing Stop Strategy
TRAILING_STRATEGY_ATR_x1, // ATR * 1 Trailing Strategy
TRAILING_STRATEGY_ATR_x2, // ATR * 2 Trailing Strategy
TRAILING_STRATEGY_ATR_x3, // ATR * 3 Trailing Strategy
//--- Confidence-adaptive ATR trail: widens toward TRAIL_ATR_MAX_MULT when live AI confidence still
//--- backs the position (lets winners run), tightens toward TRAIL_ATR_MIN_MULT as that confidence
//--- weakens or flips against it (locks profit). See Trailing\TrailingIntelligent.mqh.
TRAILING_STRATEGY_INTELLIGENT, // Intelligent (AI-confidence adaptive ATR) Trailing Strategy
};
enum MONEY_MANAGEMENT_STRATEGY
{
FIXED_RISK, // Fixed risk Percent of Account
INTELLIGENT, // Intelligent lot size
FIXED_LOT, // Fixed lot size
};
enum CLOSE_HOUR_OF_DAY
{
CLOSE_HOUR_DISABLED = -1, // Disabled
CH_0 = 0, // 00Hxx
CH_1 = 1, // 1Hxx
CH_2 = 2, // 2Hxx
CH_3 = 3, // 3Hxx
CH_4 = 4, // 4Hxx
CH_5 = 5, // 5Hxx
CH_6 = 6, // 6Hxx
CH_7 = 7, // 7Hxx
CH_8 = 8, // 8Hxx
CH_9 = 9, // 9Hxx
CH_10 = 10, // 10Hxx
CH_11 = 11, // 11Hxx
CH_12 = 12, // 12Hxx
CH_13 = 13, // 13Hxx
CH_14 = 14, // 14Hxx
CH_15 = 15, // 15Hxx
CH_16 = 16, // 16Hxx
CH_17 = 17, // 17Hxx
CH_18 = 18, // 18Hxx
CH_19 = 19, // 19Hxx
CH_20 = 20, // 20Hxx
CH_21 = 21, // 21Hxx
CH_22 = 22, // 22Hxx
CH_23 = 23, // 23Hxx
};
enum CLOSE_MINUTE_OF_HOUR
{
CLOSE_MINUTE_DISABLED = -1,// Disabled
CM_0 = 0, // xxH00
CM_5 = 5, // xxH05
CM_10 = 10, // xxH10
CM_15 = 15, // xxH15
CM_20 = 20, // xxH20
CM_25 = 25, // xxH25
CM_30 = 30, // xxH30
CM_35 = 35, // xxH35
CM_40 = 40, // xxH40
CM_45 = 45, // xxH45
CM_50 = 50, // xxH50
CM_55 = 55, // xxH55
CM_60 = 60, // xxH60
};
enum CLOSE_DAY_OF_WEEK
{
CLOSE_DAY_DISABLED = -1, // Disabled
CLOSE_MONDAY = 1, // Monday
CLOSE_TUESDAY = 2, // Tuesday
CLOSE_WEDNESDAY = 3, // Wednesday
CLOSE_THURSDAY = 4, // Thursday
CLOSE_FRIDAY = 5, // Friday
CLOSE_EVERYDAY, // Every Day
};
enum NF_LOOKBACK_PRESETS
{
NF_DISABLED = -1, // Disabled
M5 = 5, // 5 Minutes
M15 = 15, // 15 Minutes
M30 = 30, // 30 Minutes
M45 = 45, // 45 Minutes
M60 = 60, // 1 Hour
M120 = 120, // 2 Hours
M240 = 240, // 4 Hours
};
enum NF_IMPACT_PRESETS
{
HOLIDAYS = 0, //Holidays
LOW = 1, // Low Impact News
MEDIUM = 2, // Medium Impact News
HIGH = 3, // High Impact News
};
enum TRADING_DIRECTION
{
BOTH, // Allow both long and short trades
LONG_ONLY, // Allow only long (buy) trades
SHORT_ONLY // Allow only short (sell) trades
};
enum PERCENTAGE_PRESETS
{
PCT_10 = 10, // 10%
PCT_20 = 20, // 20%
PCT_30 = 30, // 30%
PCT_40 = 40, // 40%
PCT_50 = 50, // 50%
PCT_60 = 60, // 60%
PCT_70 = 70, // 70%
PCT_80 = 80, // 80%
PCT_90 = 90, // 90%
PCT_100 = 100, // 100%
};
//--- Min_Vote_Close's own scale. Separate from PERCENTAGE_PRESETS above purely so the Disabled entry is
refactor(ai): nine class-imbalance inputs down to two The imbalance section offered nine controls for one job. Audited against the code, five of them did not do what their names said at the shipped defaults: AILogitPriorStrength DEAD - Inference.mqh's post-hoc prior early-returns whenever the adjusted loss is on, which is default. OversampleParity DEAD in training - Training.mqh gated the replay loop on !useLogitAdjustedLoss (correctly, citing Buda et al. 2018). Live only in the online-learning path. EnableMinorityReplay DEAD as replay. It survived ONLY as a focal-gamma damper - "replay minority bars through pass-2 oversampling" was a focal-loss switch. ConstrainReplay DEAD as a cap; it only chose damper 0.125 vs 0.25. UseStaticPrior An exact duplicate of FreezePriorCalibration - the two were OR'd together in the single place either is read. So they were not five mechanisms fighting; they were one mechanism plus eight knobs that mostly described machinery that no longer ran. That is worse than a real conflict, because the log agreed with the names: the label-cache line printed "reps up to 28x (90% parity) (seeding era 0's class-balance oversampling)" on every run, describing an oversampling pass that had been switched off. It is fixed here too - it cost this session a wrong diagnosis. The one genuine redundancy was focal loss, running at gamma*0.125 alongside the adjusted loss: two corrections on the same axis, the exact stacking failure this file already cited Buda et al. for in two other places, damped by a replay flag whose replay path was itself dead. Removed rather than re-tuned. The plateau ladder is unaffected - its escape is the learning-rate warm restart; the gamma anneal beside it only ever stepped toward zero. WHAT REMAINS is logit-adjusted loss (Menon et al. 2021) plus a prior freeze: LogitAdjustTau 0 = off; replaces the separate EnableLogitAdjusted- Loss boolean, since a strength dial where 0 already means off does not need an on/off switch beside it. FreezePriorCalibration unchanged. It is the only one of the six corrections with a consistency guarantee, and it is consistent for exactly the balanced-error metric checkpoint selection already ranks on - so the loss and the deploy decision optimize one thing. The online continual-learning path keeps its own alpha-balanced focal weight, now as constants pinned to the removed inputs' shipped defaults, so its behaviour is unchanged. It legitimately needs its own correction: ApplyLogitAdjustment() only runs inside a training run, so a deployed model that was reloaded carries no logit offsets and would otherwise stream 31:1 data into itself uncorrected. The weights-filename fingerprint is BYTE-IDENTICAL. The focal slot was a double fed to a %d conversion and had always emitted a literal 0; the |MR: segment is written as the constant its shipped defaults produced. Dropping either would have re-keyed every model and forced a from-scratch retrain of the one topology currently converged and trading. Also removed as orphans: FOCAL_GAMMA_PRESET, MAX_OVERSAMPLE_REPLICAS, OVERSAMPLE_PARITY_FRACTION, PLATEAU_GAMMA_STEP, and the now-unreachable "neutralized by prior correction" diagnostic. Both builds compile 0 errors, 0 warnings. No retrain forced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 11:46:57 -04:00
//--- offered ONLY where it means something - it would be nonsense on Min_Vote_Open or MinRecall, which
//--- share that enum.
//--- DISABLED is 101 rather than a flag or a negative sentinel because 101 is unreachable on BOTH scales
//--- this one input drives, with no special-case branch anywhere:
//--- - the rule-based path compares it against an AVERAGE of pattern weights, which cannot exceed 100
//--- (CExpertSignalCustom::CheckClosePosition -> m_threshold_close);
//--- - the AI early-exit path compares Min_Vote_Close/100.0 = 1.01 against a softmax confidence
//--- magnitude, which cannot exceed 1.0 (same function, m_ai_exit_threshold).
//--- So selecting Disabled switches off vote-driven closing entirely - positions then leave only via
//--- stop-loss, take-profit, trailing, or the scheduled close-all - and it does so by arithmetic rather
//--- than by an extra boolean anyone has to keep in sync.
enum VOTE_CLOSE_PRESETS
{
VOTE_CLOSE_10 = 10, // 10%
VOTE_CLOSE_20 = 20, // 20%
VOTE_CLOSE_30 = 30, // 30%
VOTE_CLOSE_40 = 40, // 40%
VOTE_CLOSE_50 = 50, // 50%
VOTE_CLOSE_60 = 60, // 60%
VOTE_CLOSE_70 = 70, // 70%
VOTE_CLOSE_80 = 80, // 80%
VOTE_CLOSE_90 = 90, // 90%
VOTE_CLOSE_100 = 100, // 100%
VOTE_CLOSE_DISABLED = 101, // Disabled (exit only via SL/TP/trailing)
};
//--- Strength (tau) of the post-hoc logit adjustment / prior correction applied to the AI's 3-class
//--- decision at inference (see AdjustedSignalFromSoftmax in ExpertSignalAIBase.mqh). The network is
//--- trained on class-balance-oversampled data, so its raw softmax over-calls the rare Buy/Sell classes;
//--- re-weighting each class by its measured true base rate (prior^tau) pulls the decision back toward the
//--- real distribution. 0 = Off (raw argmax, may over-call), 100 = full Bayesian calibration to the true
//--- base rate. Stored as a percent; divided by 100 to get tau.
enum LOGIT_PRIOR_STRENGTH_PRESETS
{
LOGIT_PRIOR_OFF = 0, // Off (raw argmax - may over-call Buy/Sell)
LOGIT_PRIOR_25 = 25, // 25% (light correction)
LOGIT_PRIOR_50 = 50, // 50% (moderate)
LOGIT_PRIOR_75 = 75, // 75% (strong)
LOGIT_PRIOR_100 = 100, // 100% (full calibration to true base rate)
};
refactor(ai): derive the first dense layer's width instead of asking for it InitialNeurons was an input whose only defensible value depends on two things the user cannot see when picking from a dropdown: how wide the input vector ended up after feature selection, and how much in-sample data the study period actually yields. Left to a hand-picked constant it was badly wrong - 500 units against a 420-wide input is 210,500 weights, 72% of a 292,583-weight model, against ~36,500 training bars of which only ~2,236 are directional. That is 6.6 weights per training bar, and it EXPANDS a set of highly correlated inputs rather than compressing them. The symptom was already in the logs and had been read as a depth problem: the shallowest topology consistently beat the deepest (perceptron 52.7% balanced, hybrid 41.3%). Over-parameterization predicts that ordering just as well as covariate shift does, and only one of the two had been addressed. ComputeFirstLayerWidth() budgets roughly one first-layer weight per in-sample bar. Measured across the configurations in use: M15 10y -> 256 units, 129,071 weights, 0.73 per bar H1 10y -> 64 units, 28,727 weights, 0.65 per bar H4 10y -> 16 units, 7,559 weights, 0.68 per bar Two design points that matter: - It estimates in-sample bars from the STUDY PERIOD and timeframe, not from Bars(). What is downloaded grows over a terminal's lifetime, and a topology that widened as history filled in would re-key its own weights file and discard a trained model. - The result is snapped down to a coarse power-of-two ladder, so the estimate would have to be wrong by ~2x to change the answer. Every field it reads is already part of the weights-filename fingerprint, so the derived value needs no fingerprint entry of its own. The public setter is removed - it could only have been called after construction, and would either be ignored or silently re-key the model mid-run. Where the data cannot support even the floor (D1 over 10 years is under 2,000 bars) it now says so and names the fixes, rather than quietly training a model with more weights than examples. The DB config fingerprint drops the term too, which re-keys existing pattern databases once - correct, since a model an order of magnitude smaller should not inherit the old one's win-rate history. Compiles 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 13:01:16 -04:00
//--- FIRST_LAYER_NEURONS removed 2026-07-29. The first dense layer dominates the parameter count -
//--- it is (inputWidth+1) x width - so its only defensible value is a function of the input width and
//--- the amount of in-sample data, neither of which the user can see when picking from a dropdown. It
//--- is now derived: see CExpertSignalAIBase::ComputeFirstLayerWidth().
//--- Architecture-aware dense-topology presets are now folded directly into AI_CHOICE so the UI shows
//--- one coherent selector instead of separate AI and topology choices. The dense taper that follows each
//--- architecture-specific front-end is chosen by the selected preset itself.
//--- LSTM's own recurrent hidden-unit count - previously silently piggybacked on HiddenLayersCount
//--- (an unrelated dense-taper-depth setting), which meant it could never be tuned independently and
//--- defaulted to a value (4) nobody actually chose on purpose. Decoupled into its own input.
enum LSTM_HIDDEN_SIZE_PRESET
{
LSTM_HIDDEN_8 = 8, // 8 Units
LSTM_HIDDEN_16 = 16, // 16 Units
LSTM_HIDDEN_32 = 32, // 32 Units
LSTM_HIDDEN_64 = 64, // 64 Units
LSTM_HIDDEN_128 = 128, // 128 Units
};
//--- CONV's own output-filter count for its convolutional layer - previously silently piggybacked on
//--- HiddenLayersCount too (same bug class as LstmHiddenSize above), defaulting to a bottleneck of 4
//--- filters/bar. Decoupled into its own input.
enum CONV_FILTER_COUNT_PRESET
{
CONV_FILTERS_8 = 8, // 8 Filters
CONV_FILTERS_16 = 16, // 16 Filters
CONV_FILTERS_32 = 32, // 32 Filters
CONV_FILTERS_64 = 64, // 64 Filters
CONV_FILTERS_128 = 128, // 128 Filters
};
//--- Shared pooling shape for the Conv front-end used by both CONV and HYBRID. Keeping this
//--- separate from ConvFilterCount lets the filter-bank width and the downsampling span be tuned
//--- independently, instead of smuggling one into the other.
fix(ai): drop the conv pooling stage - it reduced across filters, not time FeedForwardConv emits POSITION-MAJOR output, matrix_o[out + window_out * i], so one bar's window_out filter responses are contiguous and consecutive bars sit window_out apart. Both pooling implementations (FeedForwardProof and CPU_FeedForwardProof) slide FLAT over that buffer - pos = i * step, reducing `window` CONSECUTIVE elements. On a position-major layout those neighbours are different FILTERS of the same bar, never one filter across time. At the shipped 3/2 the pool computed max(bar0_f0, bar0_f1, bar0_f2), then max(bar0_f2, bar0_f3, bar0_f4), with every 8th window straddling a bar boundary. So it collapsed unrelated feature detectors into whichever fired hardest, passed gradient to that winner only, and halved the feature map while doing it - all below every learnable layer, where nothing above can recover it. The removed inputs' own labels ("3 Bars") show time-axis pooling was the intent throughout. Measured cost: CONV sat pinned at ~40% balanced accuracy for 510 eras with Sell recall 0%, while plain MLPs on the same data reached 57-61%. HYBRID, which also carried this stage, came second-worst of the batch-norm group. Not fixable in the topology: pooling one filter across time needs a stride of window_out BETWEEN samples within a window, which a consecutive-window kernel cannot express at any window/step. That needs a stride-aware kernel in Network.cl + WarriorCPU.cpp + WarriorDML.cpp and a DLL rebuild, and is only worth doing if a conv front-end earns its place without downsampling first - with 20 sliding positions there is little to gain by halving them. ConvPoolWindow/ConvPoolStep and their enums are removed with it, along with the |CP: fingerprint term added earlier today. Both builds compile 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 19:28:44 -04:00
//--- CONV_POOL_WINDOW_PRESET / CONV_POOL_STEP_PRESET removed 2026-07-29 along with the pooling
//--- stage itself - their "N Bars" labels described time-axis pooling the implementation could
//--- never perform. See AddConvStage() in Expert\ExpertSignalAIBase.mqh.
enum MIN_NEURONS_COUNT
{
MIN_NEURONS_10 = 10, // Min. 10 Neurons per layer
MIN_NEURONS_20 = 20, // Min. 20 Neurons per layer
MIN_NEURONS_30 = 30, // Min. 30 Neurons per layer
MIN_NEURONS_40 = 40, // Min. 40 Neurons per layer
MIN_NEURONS_50 = 50, // Min. 50 Neurons per layer
};
// Value IS the reduction percentage applied per hidden layer (retention = 100-value), consumed
// via BuildFreshTopology()'s n = n*((100-value)*0.01) taper - e.g. RF_70 keeps 30% of the previous
// layer's neurons, i.e. a genuine 70% reduction per layer, matching the label at face value.
enum NEURONS_REDUCTION_FACTOR
{
RF_10 = 10, // 10 % Neurons Reduction Per Layer
RF_20 = 20, // 20 % Neurons Reduction Per Layer
RF_30 = 30, // 30 % Neurons Reduction Per Layer
RF_40 = 40, // 40 % Neurons Reduction Per Layer
RF_50 = 50, // 50 % Neurons Reduction Per Layer
RF_60 = 60, // 60 % Neurons Reduction Per Layer
RF_70 = 70, // 70 % Neurons Reduction Per Layer
RF_80 = 80, // 80 % Neurons Reduction Per Layer
RF_90 = 90, // 90 % Neurons Reduction Per Layer
};
enum OUTPUT_NEURONS_COUNT
{
OUTPUT_REGRESSION = 1, // Regression Algorithm
OUTPUT_CLASSIFICATION = 3, // Classification Algorithm
};
feat(nn): derive dense depth, train on all history, pin the shape in .cfg Completes the derived-topology work. Three inputs removed. AIType loses its depth suffix - AI_MLP/AI_CONV/AI_LSTM/AI_HYBRID, five entries instead of eight. Depth is now derived from the two endpoints the taper already has to connect (derived first-layer width, output-tied final width) at a 2x per-layer compression target, clamped [2..5]. Asking a user to pick a layer count while the code derives the widths those layers taper between was asking for half a decision: at 64 units tapering to 12, four layers compress by 1.4x per step and five by 1.3x, so the extra depth bought no abstraction. On the shipping H1/10y default the derivation lands on 3 layers - the depth that actually won Run 2. StudyPeriods removed. There is no case for training on less data than the broker provides at a ~6% directional base rate; the honest generalization read comes from the OOS holdout, not from withholding history. Training now starts at the earliest available bar, floored by MinTrainYear, which answers a different question (excluding dubious pre-history) and stays. That required closing the hazard the old code documented: the capacity budget now MEASURES the symbol's real bar count, and a topology derived from a measurement would widen as history downloads. Both ends are now pinned. Every derived value left the weights-filename fingerprint - keying a filename on a measured quantity means the EA looks for a file that does not exist, starts from era 0 and orphans a trained model, silently, because a missing cache is the normal first-run state. The shape lives in the .cfg instead, where LoadAndCompare now ADOPTS the four derived fields rather than diffing them; a mismatch there would discard a fully-trained model over nothing the user did. Two fields appended to the .cfg for the conv/LSTM stages, length-guarded on read because FileReadInteger past EOF returns 0 with no error. ForceHiddenLayers, a compile-time constant like DebuggingMode, pins depth for diagnostic comparisons. It joins the fingerprint only when non-zero, so forced depths get their own files - sequential comparisons only, not simultaneous from one .ex5. Derived shape, H1/10y defaults (21 features x 20 bars): first layer 64, 3 dense, 8 conv filters, 16 LSTM units. The LSTM block halves from ~58k to ~28k weights. Both builds compile 0 errors, 0 warnings. Re-keys existing models. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 10:05:40 -04:00
//--- 2026-07-30: the dense-DEPTH suffix is gone from every entry. Depth is now derived alongside the
//--- width and the taper it has to be consistent with (ComputeHiddenLayerCount) - asking a user to pick
//--- "3 layers" while the code derives the width those layers taper between is asking them to make half
//--- a decision with no way to see the other half. This selector now chooses only the thing that is
//--- genuinely a modelling CHOICE: which front-end reads the input sequence.
enum AI_CHOICE
{
AI_NONE = 0, // Disabled (classic signals only)
feat(nn): derive dense depth, train on all history, pin the shape in .cfg Completes the derived-topology work. Three inputs removed. AIType loses its depth suffix - AI_MLP/AI_CONV/AI_LSTM/AI_HYBRID, five entries instead of eight. Depth is now derived from the two endpoints the taper already has to connect (derived first-layer width, output-tied final width) at a 2x per-layer compression target, clamped [2..5]. Asking a user to pick a layer count while the code derives the widths those layers taper between was asking for half a decision: at 64 units tapering to 12, four layers compress by 1.4x per step and five by 1.3x, so the extra depth bought no abstraction. On the shipping H1/10y default the derivation lands on 3 layers - the depth that actually won Run 2. StudyPeriods removed. There is no case for training on less data than the broker provides at a ~6% directional base rate; the honest generalization read comes from the OOS holdout, not from withholding history. Training now starts at the earliest available bar, floored by MinTrainYear, which answers a different question (excluding dubious pre-history) and stays. That required closing the hazard the old code documented: the capacity budget now MEASURES the symbol's real bar count, and a topology derived from a measurement would widen as history downloads. Both ends are now pinned. Every derived value left the weights-filename fingerprint - keying a filename on a measured quantity means the EA looks for a file that does not exist, starts from era 0 and orphans a trained model, silently, because a missing cache is the normal first-run state. The shape lives in the .cfg instead, where LoadAndCompare now ADOPTS the four derived fields rather than diffing them; a mismatch there would discard a fully-trained model over nothing the user did. Two fields appended to the .cfg for the conv/LSTM stages, length-guarded on read because FileReadInteger past EOF returns 0 with no error. ForceHiddenLayers, a compile-time constant like DebuggingMode, pins depth for diagnostic comparisons. It joins the fingerprint only when non-zero, so forced depths get their own files - sequential comparisons only, not simultaneous from one .ex5. Derived shape, H1/10y defaults (21 features x 20 bars): first layer 64, 3 dense, 8 conv filters, 16 LSTM units. The LSTM block halves from ~58k to ~28k weights. Both builds compile 0 errors, 0 warnings. Re-keys existing models. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 10:05:40 -04:00
AI_MLP = 1, // MLP (dense only)
AI_CONV = 2, // CONV (convolutional front-end)
AI_LSTM = 3, // LSTM (recurrent front-end)
AI_HYBRID = 4, // HYBRID (convolutional + recurrent)
feat: S2 meta-labeling head - binary trade-quality model over the classic-candidate corpus The NN now has a target that is not per-bar direction (closed, best-of-999 p=1.0000): P(win | this journaled candidate, at the EA's own SL/TP, net of cost). One net for all 52 pattern-sides, AIType=AI_META. - NetForward.mqh: the host-side softmax+CE gradient generalized total==3 -> 2||3 on both backprop paths; a 2-class softmax IS a logistic head, and no compute backend changes. - SignalMETA.mqh (new): corpus loaded read-only from the LARGEST signal DB on disk (decoupled from the config fingerprint that burned four S1 runs); the GMT->server offset is measured PER ROW against entryPrice vs bar open (DST-immune, histogram logged); a window-span regime filter drops the pre-2017 daily-backfill rows; 31-feature setup descriptor appended at the input (26 one-hot + side + tanh netVote + SL/TP ATR + spread/ATR). - Training.mqh: candidate-queued pass 1, binary-target pass 2, per-candidate calibration (2.5) and OOS (3) walks. Counter mapping win->Buy / loss->Sell lets checkpoint selection, the edge floor, the plateau ladder and the family-wise deploy gate run UNCHANGED: precision reads as win rate among traded candidates, chance as the base win rate, recalls as sensitivity/ specificity. Era-end META line: coverage x (p - break-even) vs the null. - Labels are the side-conditional triple-barrier win caches - never the DB's stop-and-reverse outcome. Logit adjustment deliberately skipped (~40% base rate). Live inference + online learning guarded off until S3. - Fingerprint: conditional |TGT:META1; State\META\ folder + 2-output filename slot keep meta models fully separate from direction models. Compiles clean (0 errors, 0 warnings). S2 run = attach a chart with AIType=AI_META; S3 wires the votes via the per-side hooks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 06:52:31 -04:00
AI_META = 5, // META (trade-quality head over classic candidates - trains, no votes yet)
};
// Confidence used to scale SL/TP, gate early AI exits, and (Intelligent MM) scale lot size.
// AI confidence comes from the signal filter's live prediction (0..1); DB confidence comes
// from the historical time-based win rate of the currently traded patterns (0..1). Blended
// averages both, so a pattern is only sized up when both the model and its track record agree.
enum CONFIDENCE_SOURCE
{
CONF_AI = 0, // AI signal confidence only
CONF_DB = 1, // Database win-rate confidence only
CONF_BLENDED = 2, // Average of AI and database confidence
};
// How many bars to wait, after a candidate ZigZag reversal bar, before trusting the real ADZigZag
// indicator's verdict on it as a training label - see CExpertSignalAIBase's m_swingConfirmationBars
// declaration comment. A ZigZag's most recent 1-3 legs can still repaint as new bars arrive, so this
// must be generous enough to let a leg fully settle (bumped from the old fractal-based system's
// default of 20 to 100 for exactly that reason). A value of 0 is clamped up to a 1-bar minimum
// internally, never used to mean "no delay".
enum SWING_CONFIRMATION_PRESET
{
SC_10 = 10, // 10 Bars
SC_20 = 20, // 20 Bars
SC_30 = 30, // 30 Bars
SC_50 = 50, // 50 Bars
SC_100 = 100, // 100 Bars
SC_200 = 200, // 200 Bars
};
enum MAX_ERAS_PRESET
{
ME_100 = 100, // 100 Eras
ME_200 = 200, // 200 Eras
ME_300 = 300, // 300 Eras
ME_500 = 500, // 500 Eras
ME_1000 = 1000, // 1000 Eras
};
//--- percentage of the study period held back as out-of-sample data never trained on;
//--- value is the OOS share, in-sample share is the remainder (e.g. OOS_30 -> 70% IS / 30% OOS)
enum OOS_SPLIT_PRESET
{
OOS_10 = 10, // 90% IS / 10% OOS
OOS_20 = 20, // 80% IS / 20% OOS
OOS_30 = 30, // 70% IS / 30% OOS
OOS_40 = 40, // 60% IS / 40% OOS
OOS_50 = 50, // 50% IS / 50% OOS
};
//--- RISK_LIMIT_PCT_PRESET REMOVED 2026-08-02. It backed MaxDailyLossPct/MaxDrawdownPct as a dropdown
//--- of eight fixed percentages, which no funded-account programme is obliged to match - 4.5% or 3.75%
//--- were unreachable. Both inputs are now free-entry doubles (Variables\Inputs.mqh) validated at init.
//--- Note for anyone reinstating an enum input here: MT5 does NOT validate a saved enum value against
//--- the current enum, so a .set file holding a deleted member loads as a silent out-of-range int - the
//--- failure mode that trained four topologies on the wrong barrier (project memory: stale enum wrong
//--- target). Changing these two to doubles removes that exposure rather than renaming it.
//+------------------------------------------------------------------+