Warrior_EA/Expert/ADIndicatorTuner.mqh

766 lines
36 KiB
MQL5
Raw Permalink Normal View History

//+------------------------------------------------------------------+
//| ADIndicatorTuner.mqh |
//| AnimateDread |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#include "..\Variables\IndicatorTuneRanges.mqh"
//--- flat count of AutoTuneIndicators-tunable params across all 5 AD indicators plus the MA/RSI/MACD/
//--- Ichimoku feature periods, used to persist/restore the "winning" values in the .nnw file. ATR and
//--- Volume are intentionally NOT included: ATR's only tunable knob is m_periods, the shared bar lookback
//--- (ind_Periods); Volume's applied-price type (tick vs real) is kept as a plain manual input
//--- (VolumeData) since not every symbol has real volume available.
//--- NOTE changing this value invalidates every persisted tuner block - Unflatten() below refuses a
//--- size-mismatched array and falls back to the constructor defaults, so any model trained before the
//--- change loses its previously-tuned indicator params (it says so in the log). That is the accepted
//--- cost of adding a tunable; the network weights themselves are unaffected.
#define AD_TUNE_PARAM_COUNT 42
//+------------------------------------------------------------------+
//| Currently-active tunable input values for each AD indicator (AutoTuneIndicators search space).
//| InpContextMode/InpSessionType/InpSessionCount are session choices, not accuracy knobs, and are
//| hardcoded to the indicators' own defaults in CExpertSignalAIBase::InitAD*() rather than
//| stored/tuned here.
//+------------------------------------------------------------------+
struct SADCumulativeDeltaParams
{
int lookback;
double volClimax, volHigh, rangeClimax, rangeSignificant, stVolRatio, atrMult;
};
struct SADShorteningOfThrustParams
{
int thrustLookback, minImpulses;
double sotThreshold;
};
struct SADWyckoffEventStreamParams
{
int lookback, zigzag;
double volClimax, volHigh, rangeClimax, rangeSignificant, stVolRatio, atr;
//--- range-lifecycle knobs added by the 2026-08-02 ADWyckoffEventStream rewrite: how close price must
//--- come to a boundary to count as revisiting it (touchATR), how far the Automatic Rally/Reaction must
//--- travel off the climax before the range is confirmed and anchored (arMinATR), and the hard age cap
//--- after which a range is abandoned (maxRangeBars). All three move which events fire, so all three
//--- are tuned exactly like the thresholds above.
double touchATR, arMinATR;
int maxRangeBars;
};
struct SADWyckoffFailedStructureParams
{
int lookback, zigzagStrength;
double volClimax, volHigh, rangeClimax, rangeSignificant, stVolRatio, atrMult;
};
struct SADWyckoffSignificantBarInversionParams
{
int lookback;
double rangeSignificant, volumeHigh, atr;
};
//+------------------------------------------------------------------+
//| Class CADIndicatorTuner. |
//| Owns the AutoTuneIndicators search-space state (current + best-known tunable values for all 5 |
//| AD indicators) and its own mutation logic (random perturbation, flatten/unflatten for .nnw |
//| persistence, win/loss bookkeeping) - extracted out of CExpertSignalAIBase (SOLID cleanup) since |
//| this state and behavior is entirely self-contained: it never touches Net, Train()'s state |
//| machine, or anything else in CExpertSignalAIBase. CExpertSignalAIBase::TuneIndicatorsAndTrain() |
//| (which DOES orchestrate Train()/Net/checkpointing around this tuner) stays put - that outer loop |
//| is exactly as tightly coupled to Train()'s resumable state machine as Train() itself, so it's |
//| deliberately NOT pulled in here; see this file's declaration comment in ExpertSignalAIBase.mqh |
//| for the full rationale. |
//+------------------------------------------------------------------+
class CADIndicatorTuner
{
public:
SADCumulativeDeltaParams adCumDelta;
SADShorteningOfThrustParams adSOT;
SADWyckoffEventStreamParams adWES;
SADWyckoffFailedStructureParams adWFS;
SADWyckoffSignificantBarInversionParams adWSBI;
//--- AI-feature MA/RSI periods (see CExpertSignalAIBase::InitMA()/InitRSI()) - searched the same
//--- way as the AD indicators above, snapped to MA_PERIOD_PRESETS/RSI_PERIOD_PRESETS so a search
//--- never lands on a non-standard period. Starts from the Classic Signals PeriodMA/PeriodRSI
//--- input value; only ever diverges from it once AutoTuneIndicators actually runs a trial. The
//--- Classic Signals MA/RSI vote itself keeps using the literal input, untouched by this search -
//--- it needs no training/warm-up, so there's nothing for a tuning trial to validate it against.
int maPeriod;
//--- unified MA TYPE (MA_TYPE_PRESETS 0..8), searched alongside maPeriod when the MA feature is on.
//--- Starts from the MA_Type input; only diverges once a tuning trial runs. Feeds InitMA()'s CiCustom.
int maType;
int rsiPeriod;
//--- MACD feature periods (see CExpertSignalAIBase::InitMACDFeature()) - snapped to
//--- MACD_*_PRESETS, whose preset sets are built so every fast/slow pair stays legal no matter which
//--- one a trial perturbs (see InputEnums.mqh). Same "the classic vote keeps the raw input" split as
//--- maPeriod/rsiPeriod above: Signals\SignalMACD.mqh is untouched by this search.
int macdFast;
int macdSlow;
int macdSignal;
//--- Ichimoku feature periods (see CExpertSignalAIBase::InitIchimoku()) - snapped to ICHIMOKU_*
//--- presets, likewise mutually-legal in any combination. Signals\SignalIchimoku.mqh is untouched.
int ichiTenkan;
int ichiKijun;
int ichiSenkou;
//--- best-known copies of the above, kept during TuneIndicatorsAndTrain() so a losing trial can
//--- restore rather than drift from a bad candidate - see SaveAsBest()/RestoreBest().
SADCumulativeDeltaParams bestAdCumDelta;
SADShorteningOfThrustParams bestAdSOT;
SADWyckoffEventStreamParams bestAdWES;
SADWyckoffFailedStructureParams bestAdWFS;
SADWyckoffSignificantBarInversionParams bestAdWSBI;
int bestMaPeriod;
int bestMaType;
int bestRsiPeriod;
int bestMacdFast;
int bestMacdSlow;
int bestMacdSignal;
int bestIchiTenkan;
int bestIchiKijun;
int bestIchiSenkou;
CADIndicatorTuner(void);
//--- flattens/restores the tunable param structs to/from a fixed-size array so they can be
//--- persisted alongside the network weights (see AI/Network.mqh Save()/Load())
void Flatten(double &arr[]);
void Unflatten(const double &arr[]);
//--- randomly perturbs one tunable param of one enabled AD indicator (or the MA/RSI/MACD/Ichimoku
//--- periods), within IndicatorTuneRanges.mqh bounds / the logical period presets. The use* args
//--- mirror CExpertSignalAIBase's m_useADCumulativeDelta/etc. (and m_useMA/m_useRSI/m_useMACD/
//--- m_useIchimoku) enable flags - no-op if all nine are false.
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
//--- Tunable-parameter count across the ENABLED indicators only.
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
//--- Defined next to PerturbRandom() in the .mqh body; the two must be changed together.
int ActiveDimensions(bool useCumDelta, bool useSOT, bool useWES, bool useWFS, bool useWSBI, bool useMA, bool useRSI, bool useMACD, bool useIchimoku);
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
//--- Candidate VALUES for one flattened parameter index (see Flatten()'s ordering), for the
//--- filter-based tuner's coordinate sweep. Discrete params return their exact preset list; continuous
//--- ones return an evenly-spaced grid across their IndicatorTuneRanges.mqh bounds. Returns the count.
int ParamCandidates(int paramIdx, double &out[]);
//--- Which indicator group owns a flattened parameter index - lets the sweep skip parameters whose
//--- indicator is switched off. 0=CumDelta 1=SOT 2=WES 3=WFS 4=WSBI 5=MA 6=RSI 7=MACD 8=Ichimoku.
int ParamOwner(int paramIdx);
void PerturbRandom(bool useCumDelta, bool useSOT, bool useWES, bool useWFS, bool useWSBI, bool useMA, bool useRSI, bool useMACD, bool useIchimoku);
//--- snapshots current -> best (a winning trial) / restores best -> current (undoing a losing trial)
void SaveAsBest(void);
void RestoreBest(void);
};
//+------------------------------------------------------------------+
//| Defaults mirror each CustomIndicators\AD*.mq5 input's own default. |
//+------------------------------------------------------------------+
CADIndicatorTuner::CADIndicatorTuner(void)
{
adCumDelta.lookback = 50;
adCumDelta.volClimax = 2.5;
adCumDelta.volHigh = 1.5;
adCumDelta.rangeClimax = 1.8;
adCumDelta.rangeSignificant = 1.2;
adCumDelta.stVolRatio = 0.6;
adCumDelta.atrMult = 0.5;
adSOT.thrustLookback = 30;
adSOT.minImpulses = 3;
adSOT.sotThreshold = 0.30;
adWES.lookback = 50;
adWES.zigzag = 3;
adWES.volClimax = 2.5;
adWES.volHigh = 1.5;
adWES.rangeClimax = 1.8;
adWES.rangeSignificant = 1.2;
adWES.stVolRatio = 0.6;
adWES.atr = 0.5;
adWES.touchATR = 0.5;
adWES.arMinATR = 1.0;
adWES.maxRangeBars = 200;
adWFS.lookback = 50;
adWFS.zigzagStrength = 3;
adWFS.volClimax = 2.5;
adWFS.volHigh = 1.5;
adWFS.rangeClimax = 1.8;
adWFS.rangeSignificant = 1.2;
adWFS.stVolRatio = 0.6;
adWFS.atrMult = 0.5;
adWSBI.lookback = 50;
adWSBI.rangeSignificant = 1.2;
adWSBI.volumeHigh = 1.5;
adWSBI.atr = 0.5;
maPeriod = PeriodMA;
maType = MA_Type;
rsiPeriod = PeriodRSI;
macdFast = MACD_PeriodFast;
macdSlow = MACD_PeriodSlow;
macdSignal = MACD_PeriodSignal;
ichiTenkan = Ichimoku_PeriodTenkan;
ichiKijun = Ichimoku_PeriodKijun;
ichiSenkou = Ichimoku_PeriodSenkou;
SaveAsBest();
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CADIndicatorTuner::Flatten(double &arr[])
{
ArrayResize(arr, AD_TUNE_PARAM_COUNT);
int i = 0;
arr[i++] = adCumDelta.lookback;
arr[i++] = adCumDelta.volClimax;
arr[i++] = adCumDelta.volHigh;
arr[i++] = adCumDelta.rangeClimax;
arr[i++] = adCumDelta.rangeSignificant;
arr[i++] = adCumDelta.stVolRatio;
arr[i++] = adCumDelta.atrMult;
arr[i++] = adSOT.thrustLookback;
arr[i++] = adSOT.minImpulses;
arr[i++] = adSOT.sotThreshold;
arr[i++] = adWES.lookback;
arr[i++] = adWES.zigzag;
arr[i++] = adWES.volClimax;
arr[i++] = adWES.volHigh;
arr[i++] = adWES.rangeClimax;
arr[i++] = adWES.rangeSignificant;
arr[i++] = adWES.stVolRatio;
arr[i++] = adWES.atr;
arr[i++] = adWES.touchATR;
arr[i++] = adWES.arMinATR;
arr[i++] = adWES.maxRangeBars;
arr[i++] = adWFS.lookback;
arr[i++] = adWFS.zigzagStrength;
arr[i++] = adWFS.volClimax;
arr[i++] = adWFS.volHigh;
arr[i++] = adWFS.rangeClimax;
arr[i++] = adWFS.rangeSignificant;
arr[i++] = adWFS.stVolRatio;
arr[i++] = adWFS.atrMult;
arr[i++] = adWSBI.lookback;
arr[i++] = adWSBI.rangeSignificant;
arr[i++] = adWSBI.volumeHigh;
arr[i++] = adWSBI.atr;
arr[i++] = maPeriod;
arr[i++] = maType;
arr[i++] = rsiPeriod;
arr[i++] = macdFast;
arr[i++] = macdSlow;
arr[i++] = macdSignal;
arr[i++] = ichiTenkan;
arr[i++] = ichiKijun;
arr[i++] = ichiSenkou;
}
//+------------------------------------------------------------------+
//| Reverse of Flatten(); ints are rounded on the way back in since |
//| everything is carried as double in the flat array. |
//+------------------------------------------------------------------+
void CADIndicatorTuner::Unflatten(const double &arr[])
{
if(ArraySize(arr) != AD_TUNE_PARAM_COUNT)
{
// A size mismatch means AD_TUNE_PARAM_COUNT changed since this array was persisted (e.g. after
// a version upgrade) - silently keeping the constructor defaults instead of the loaded values
// used to discard a previously-tuned indicator's best parameters with zero trace in the log.
Print(__FUNCTION__ + ": persisted tuner param array size (" + IntegerToString(ArraySize(arr)) +
") does not match AD_TUNE_PARAM_COUNT (" + IntegerToString(AD_TUNE_PARAM_COUNT) +
") - discarding it and keeping constructor defaults. This model's previously-tuned indicator parameters are lost.");
return;
}
int i = 0;
adCumDelta.lookback = (int)MathRound(arr[i++]);
adCumDelta.volClimax = arr[i++];
adCumDelta.volHigh = arr[i++];
adCumDelta.rangeClimax = arr[i++];
adCumDelta.rangeSignificant = arr[i++];
adCumDelta.stVolRatio = arr[i++];
adCumDelta.atrMult = arr[i++];
adSOT.thrustLookback = (int)MathRound(arr[i++]);
adSOT.minImpulses = (int)MathRound(arr[i++]);
adSOT.sotThreshold = arr[i++];
adWES.lookback = (int)MathRound(arr[i++]);
adWES.zigzag = (int)MathRound(arr[i++]);
adWES.volClimax = arr[i++];
adWES.volHigh = arr[i++];
adWES.rangeClimax = arr[i++];
adWES.rangeSignificant = arr[i++];
adWES.stVolRatio = arr[i++];
adWES.atr = arr[i++];
adWES.touchATR = arr[i++];
adWES.arMinATR = arr[i++];
adWES.maxRangeBars = (int)MathRound(arr[i++]);
adWFS.lookback = (int)MathRound(arr[i++]);
adWFS.zigzagStrength = (int)MathRound(arr[i++]);
adWFS.volClimax = arr[i++];
adWFS.volHigh = arr[i++];
adWFS.rangeClimax = arr[i++];
adWFS.rangeSignificant = arr[i++];
adWFS.stVolRatio = arr[i++];
adWFS.atrMult = arr[i++];
adWSBI.lookback = (int)MathRound(arr[i++]);
adWSBI.rangeSignificant = arr[i++];
adWSBI.volumeHigh = arr[i++];
adWSBI.atr = arr[i++];
maPeriod = (int)MathRound(arr[i++]);
maType = (int)MathRound(arr[i++]);
rsiPeriod = (int)MathRound(arr[i++]);
macdFast = (int)MathRound(arr[i++]);
macdSlow = (int)MathRound(arr[i++]);
macdSignal = (int)MathRound(arr[i++]);
ichiTenkan = (int)MathRound(arr[i++]);
ichiKijun = (int)MathRound(arr[i++]);
ichiSenkou = (int)MathRound(arr[i++]);
}
//+------------------------------------------------------------------+
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
//| Number of SEARCH DIMENSIONS actually in play - the count of |
//| tunable parameters across the ENABLED indicators only. |
//| |
//| Deliberately defined immediately above PerturbRandom(), because |
//| the per-group counts below must match that function's own |
//| `MathRand() % N` arities exactly. Adding a tunable to a group |
//| there without bumping it here would silently under-size the |
//| search rather than fail, so the two live together and are read |
//| together. |
//| |
//| This is what replaced the IndicatorTuneTrials input (2026-08-01): |
//| the right search budget is a function of how big the space is, |
//| which is knowable here and was never knowable by the user. |
//+------------------------------------------------------------------+
int CADIndicatorTuner::ActiveDimensions(bool useCumDelta, bool useSOT, bool useWES, bool useWFS, bool useWSBI, bool useMA, bool useRSI, bool useMACD, bool useIchimoku)
{
int d = 0;
if(useCumDelta)
d += 7; // lookback, volClimax, + 5 more - see PerturbRandom case 0
if(useSOT)
d += 3; // case 1
if(useWES)
d += 11; // case 2 - 8 thresholds + touchATR/arMinATR/maxRangeBars
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
if(useWFS)
d += 8; // case 3
if(useWSBI)
d += 4; // case 4
if(useMA)
d += 2; // period + type - case 5
if(useRSI)
d += 1; // period - case 6
if(useMACD)
d += 3; // fast, slow, signal - case 7
if(useIchimoku)
d += 3; // tenkan, kijun, senkou - case 8
return d;
}
//+------------------------------------------------------------------+
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
//| Indicator group owning a flattened parameter index. The boundaries|
//| mirror Flatten()'s write order exactly - change one, change both. |
//+------------------------------------------------------------------+
int CADIndicatorTuner::ParamOwner(int paramIdx)
{
if(paramIdx < 7) return 0; // adCumDelta: 0..6
if(paramIdx < 10) return 1; // adSOT: 7..9
if(paramIdx < 21) return 2; // adWES: 10..20
if(paramIdx < 29) return 3; // adWFS: 21..28
if(paramIdx < 33) return 4; // adWSBI: 29..32
if(paramIdx < 35) return 5; // MA: 33..34 (period, type)
if(paramIdx == 35) return 6; // RSI: 35
if(paramIdx < 39) return 7; // MACD: 36..38
return 8; // Ichimoku: 39..41
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
}
//+------------------------------------------------------------------+
//| Candidate values for one flattened parameter - see the header. |
//| |
//| The discrete parameters return their exact preset lists, the same |
//| ones PerturbRandom() samples from, so a filter sweep can never |
//| land on a period the enum does not offer. The continuous AD |
//| parameters get an evenly-spaced grid across their bounds: |
//| TUNE_GRID_STEPS points is enough for a marginal-association score |
//| whose own resolution is a handful of histogram bins, and the cost |
//| of the sweep is linear in this number. |
//+------------------------------------------------------------------+
int CADIndicatorTuner::ParamCandidates(int paramIdx, double &out[])
{
#define TUNE_GRID_STEPS 6
//--- discrete preset lists first (identical to PerturbRandom's)
if(paramIdx == 33)
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
{
int v[] = {MA_PERIOD_5, MA_PERIOD_8, MA_PERIOD_9, MA_PERIOD_10, MA_PERIOD_13,
MA_PERIOD_20, MA_PERIOD_21, MA_PERIOD_50, MA_PERIOD_100, MA_PERIOD_200};
ArrayResize(out, ArraySize(v));
for(int i = 0; i < ArraySize(v); i++) out[i] = v[i];
return ArraySize(out);
}
if(paramIdx == 34)
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
{
int v[] = {MA_TYPE_SMA, MA_TYPE_EMA, MA_TYPE_SMMA, MA_TYPE_LWMA, MA_TYPE_ALMA,
MA_TYPE_DEMA, MA_TYPE_ZLEMA, MA_TYPE_T3, MA_TYPE_KALMAN};
ArrayResize(out, ArraySize(v));
for(int i = 0; i < ArraySize(v); i++) out[i] = v[i];
return ArraySize(out);
}
if(paramIdx == 35)
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
{
int v[] = {RSI_PERIOD_2, RSI_PERIOD_5, RSI_PERIOD_7, RSI_PERIOD_9, RSI_PERIOD_14, RSI_PERIOD_21, RSI_PERIOD_25};
ArrayResize(out, ArraySize(v));
for(int i = 0; i < ArraySize(v); i++) out[i] = v[i];
return ArraySize(out);
}
if(paramIdx == 36)
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
{
int v[] = {MACD_FAST_5, MACD_FAST_8, MACD_FAST_12, MACD_FAST_15};
ArrayResize(out, ArraySize(v));
for(int i = 0; i < ArraySize(v); i++) out[i] = v[i];
return ArraySize(out);
}
if(paramIdx == 37)
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
{
int v[] = {MACD_SLOW_17, MACD_SLOW_21, MACD_SLOW_26, MACD_SLOW_34, MACD_SLOW_50};
ArrayResize(out, ArraySize(v));
for(int i = 0; i < ArraySize(v); i++) out[i] = v[i];
return ArraySize(out);
}
if(paramIdx == 38)
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
{
int v[] = {MACD_SIGNAL_5, MACD_SIGNAL_7, MACD_SIGNAL_9, MACD_SIGNAL_12};
ArrayResize(out, ArraySize(v));
for(int i = 0; i < ArraySize(v); i++) out[i] = v[i];
return ArraySize(out);
}
if(paramIdx == 39)
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
{
int v[] = {ICHI_TENKAN_7, ICHI_TENKAN_9, ICHI_TENKAN_12, ICHI_TENKAN_20};
ArrayResize(out, ArraySize(v));
for(int i = 0; i < ArraySize(v); i++) out[i] = v[i];
return ArraySize(out);
}
if(paramIdx == 40)
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
{
int v[] = {ICHI_KIJUN_22, ICHI_KIJUN_26, ICHI_KIJUN_30, ICHI_KIJUN_40};
ArrayResize(out, ArraySize(v));
for(int i = 0; i < ArraySize(v); i++) out[i] = v[i];
return ArraySize(out);
}
if(paramIdx == 41)
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
{
int v[] = {ICHI_SENKOU_44, ICHI_SENKOU_52, ICHI_SENKOU_60, ICHI_SENKOU_120};
ArrayResize(out, ArraySize(v));
for(int i = 0; i < ArraySize(v); i++) out[i] = v[i];
return ArraySize(out);
}
//--- continuous AD parameters: even grid over the tune-range bounds. `isInt` keeps bar-count
//--- parameters whole, so a candidate is never a fractional lookback.
double lo = 0, hi = 0;
bool isInt = false;
switch(paramIdx)
{
case 0: lo = ADCUMDELTA_LOOKBACK_MIN; hi = ADCUMDELTA_LOOKBACK_MAX; isInt = true; break;
case 1: lo = ADCUMDELTA_VOLCLIMAX_MIN; hi = ADCUMDELTA_VOLCLIMAX_MAX; break;
case 2: lo = ADCUMDELTA_VOLHIGH_MIN; hi = ADCUMDELTA_VOLHIGH_MAX; break;
case 3: lo = ADCUMDELTA_RANGECLIMAX_MIN; hi = ADCUMDELTA_RANGECLIMAX_MAX; break;
case 4: lo = ADCUMDELTA_RANGESIGNIF_MIN; hi = ADCUMDELTA_RANGESIGNIF_MAX; break;
case 5: lo = ADCUMDELTA_STVOLRATIO_MIN; hi = ADCUMDELTA_STVOLRATIO_MAX; break;
case 6: lo = ADCUMDELTA_ATRMULT_MIN; hi = ADCUMDELTA_ATRMULT_MAX; break;
case 7: lo = ADSOT_LOOKBACK_MIN; hi = ADSOT_LOOKBACK_MAX; isInt = true; break;
case 8: lo = ADSOT_MININPULSES_MIN; hi = ADSOT_MININPULSES_MAX; isInt = true; break;
case 9: lo = ADSOT_THRESHOLD_MIN; hi = ADSOT_THRESHOLD_MAX; break;
case 10: lo = ADWES_LOOKBACK_MIN; hi = ADWES_LOOKBACK_MAX; isInt = true; break;
case 11: lo = ADWES_ZIGZAG_MIN; hi = ADWES_ZIGZAG_MAX; isInt = true; break;
case 12: lo = ADWES_VOLCLIMAX_MIN; hi = ADWES_VOLCLIMAX_MAX; break;
case 13: lo = ADWES_VOLHIGH_MIN; hi = ADWES_VOLHIGH_MAX; break;
case 14: lo = ADWES_RANGECLIMAX_MIN; hi = ADWES_RANGECLIMAX_MAX; break;
case 15: lo = ADWES_RANGESIGNIF_MIN; hi = ADWES_RANGESIGNIF_MAX; break;
case 16: lo = ADWES_STVOLRATIO_MIN; hi = ADWES_STVOLRATIO_MAX; break;
case 17: lo = ADWES_ATR_MIN; hi = ADWES_ATR_MAX; break;
case 18: lo = ADWES_TOUCHATR_MIN; hi = ADWES_TOUCHATR_MAX; break;
case 19: lo = ADWES_ARMINATR_MIN; hi = ADWES_ARMINATR_MAX; break;
case 20: lo = ADWES_MAXRANGEBARS_MIN; hi = ADWES_MAXRANGEBARS_MAX; isInt = true; break;
case 21: lo = ADWFS_LOOKBACK_MIN; hi = ADWFS_LOOKBACK_MAX; isInt = true; break;
case 22: lo = ADWFS_ZIGZAGSTRENGTH_MIN; hi = ADWFS_ZIGZAGSTRENGTH_MAX; isInt = true; break;
case 23: lo = ADWFS_VOLCLIMAX_MIN; hi = ADWFS_VOLCLIMAX_MAX; break;
case 24: lo = ADWFS_VOLHIGH_MIN; hi = ADWFS_VOLHIGH_MAX; break;
case 25: lo = ADWFS_RANGECLIMAX_MIN; hi = ADWFS_RANGECLIMAX_MAX; break;
case 26: lo = ADWFS_RANGESIGNIF_MIN; hi = ADWFS_RANGESIGNIF_MAX; break;
case 27: lo = ADWFS_STVOLRATIO_MIN; hi = ADWFS_STVOLRATIO_MAX; break;
case 28: lo = ADWFS_ATRMULT_MIN; hi = ADWFS_ATRMULT_MAX; break;
case 29: lo = ADWSBI_LOOKBACK_MIN; hi = ADWSBI_LOOKBACK_MAX; isInt = true; break;
case 30: lo = ADWSBI_RANGESIGNIF_MIN; hi = ADWSBI_RANGESIGNIF_MAX; break;
case 31: lo = ADWSBI_VOLHIGH_MIN; hi = ADWSBI_VOLHIGH_MAX; break;
case 32: lo = ADWSBI_ATR_MIN; hi = ADWSBI_ATR_MAX; break;
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
default: ArrayResize(out, 0); return 0;
}
ArrayResize(out, TUNE_GRID_STEPS);
for(int i = 0; i < TUNE_GRID_STEPS; i++)
{
double v = lo + (hi - lo) * i / (TUNE_GRID_STEPS - 1.0);
out[i] = isInt ? MathRound(v) : v;
}
return TUNE_GRID_STEPS;
}
//+------------------------------------------------------------------+
//| Randomly perturbs one tunable param of one randomly-chosen |
//| *enabled* AD indicator, within IndicatorTuneRanges.mqh bounds. |
//| No-op if no AD indicator is enabled. |
//+------------------------------------------------------------------+
void CADIndicatorTuner::PerturbRandom(bool useCumDelta, bool useSOT, bool useWES, bool useWFS, bool useWSBI, bool useMA, bool useRSI, bool useMACD, bool useIchimoku)
{
int enabled[9], n = 0;
if(useCumDelta)
enabled[n++] = 0;
if(useSOT)
enabled[n++] = 1;
if(useWES)
enabled[n++] = 2;
if(useWFS)
enabled[n++] = 3;
if(useWSBI)
enabled[n++] = 4;
if(useMA)
enabled[n++] = 5;
if(useRSI)
enabled[n++] = 6;
if(useMACD)
enabled[n++] = 7;
if(useIchimoku)
enabled[n++] = 8;
if(n == 0)
return;
switch(enabled[MathRand() % n])
{
case 0:
{
switch(MathRand() % 7)
{
case 0:
adCumDelta.lookback = ADCUMDELTA_LOOKBACK_MIN + MathRand() % (ADCUMDELTA_LOOKBACK_MAX - ADCUMDELTA_LOOKBACK_MIN + 1);
break;
case 1:
adCumDelta.volClimax = ADCUMDELTA_VOLCLIMAX_MIN + (MathRand() / 32767.0) * (ADCUMDELTA_VOLCLIMAX_MAX - ADCUMDELTA_VOLCLIMAX_MIN);
break;
case 2:
adCumDelta.volHigh = ADCUMDELTA_VOLHIGH_MIN + (MathRand() / 32767.0) * (ADCUMDELTA_VOLHIGH_MAX - ADCUMDELTA_VOLHIGH_MIN);
break;
case 3:
adCumDelta.rangeClimax = ADCUMDELTA_RANGECLIMAX_MIN + (MathRand() / 32767.0) * (ADCUMDELTA_RANGECLIMAX_MAX - ADCUMDELTA_RANGECLIMAX_MIN);
break;
case 4:
adCumDelta.rangeSignificant = ADCUMDELTA_RANGESIGNIF_MIN + (MathRand() / 32767.0) * (ADCUMDELTA_RANGESIGNIF_MAX - ADCUMDELTA_RANGESIGNIF_MIN);
break;
case 5:
adCumDelta.stVolRatio = ADCUMDELTA_STVOLRATIO_MIN + (MathRand() / 32767.0) * (ADCUMDELTA_STVOLRATIO_MAX - ADCUMDELTA_STVOLRATIO_MIN);
break;
case 6:
adCumDelta.atrMult = ADCUMDELTA_ATRMULT_MIN + (MathRand() / 32767.0) * (ADCUMDELTA_ATRMULT_MAX - ADCUMDELTA_ATRMULT_MIN);
break;
}
break;
}
case 1:
{
switch(MathRand() % 3)
{
case 0:
adSOT.thrustLookback = ADSOT_LOOKBACK_MIN + MathRand() % (ADSOT_LOOKBACK_MAX - ADSOT_LOOKBACK_MIN + 1);
break;
case 1:
adSOT.minImpulses = ADSOT_MININPULSES_MIN + MathRand() % (ADSOT_MININPULSES_MAX - ADSOT_MININPULSES_MIN + 1);
break;
case 2:
adSOT.sotThreshold = ADSOT_THRESHOLD_MIN + (MathRand() / 32767.0) * (ADSOT_THRESHOLD_MAX - ADSOT_THRESHOLD_MIN);
break;
}
break;
}
case 2:
{
switch(MathRand() % 11)
{
case 0:
adWES.lookback = ADWES_LOOKBACK_MIN + MathRand() % (ADWES_LOOKBACK_MAX - ADWES_LOOKBACK_MIN + 1);
break;
case 1:
adWES.zigzag = ADWES_ZIGZAG_MIN + MathRand() % (ADWES_ZIGZAG_MAX - ADWES_ZIGZAG_MIN + 1);
break;
case 2:
adWES.volClimax = ADWES_VOLCLIMAX_MIN + (MathRand() / 32767.0) * (ADWES_VOLCLIMAX_MAX - ADWES_VOLCLIMAX_MIN);
break;
case 3:
adWES.volHigh = ADWES_VOLHIGH_MIN + (MathRand() / 32767.0) * (ADWES_VOLHIGH_MAX - ADWES_VOLHIGH_MIN);
break;
case 4:
adWES.rangeClimax = ADWES_RANGECLIMAX_MIN + (MathRand() / 32767.0) * (ADWES_RANGECLIMAX_MAX - ADWES_RANGECLIMAX_MIN);
break;
case 5:
adWES.rangeSignificant = ADWES_RANGESIGNIF_MIN + (MathRand() / 32767.0) * (ADWES_RANGESIGNIF_MAX - ADWES_RANGESIGNIF_MIN);
break;
case 6:
adWES.stVolRatio = ADWES_STVOLRATIO_MIN + (MathRand() / 32767.0) * (ADWES_STVOLRATIO_MAX - ADWES_STVOLRATIO_MIN);
break;
case 7:
adWES.atr = ADWES_ATR_MIN + (MathRand() / 32767.0) * (ADWES_ATR_MAX - ADWES_ATR_MIN);
break;
case 8:
adWES.touchATR = ADWES_TOUCHATR_MIN + (MathRand() / 32767.0) * (ADWES_TOUCHATR_MAX - ADWES_TOUCHATR_MIN);
break;
case 9:
adWES.arMinATR = ADWES_ARMINATR_MIN + (MathRand() / 32767.0) * (ADWES_ARMINATR_MAX - ADWES_ARMINATR_MIN);
break;
case 10:
adWES.maxRangeBars = ADWES_MAXRANGEBARS_MIN + MathRand() % (ADWES_MAXRANGEBARS_MAX - ADWES_MAXRANGEBARS_MIN + 1);
break;
}
break;
}
case 3:
{
switch(MathRand() % 8)
{
case 0:
adWFS.lookback = ADWFS_LOOKBACK_MIN + MathRand() % (ADWFS_LOOKBACK_MAX - ADWFS_LOOKBACK_MIN + 1);
break;
case 1:
adWFS.zigzagStrength = ADWFS_ZIGZAGSTRENGTH_MIN + MathRand() % (ADWFS_ZIGZAGSTRENGTH_MAX - ADWFS_ZIGZAGSTRENGTH_MIN + 1);
break;
case 2:
adWFS.volClimax = ADWFS_VOLCLIMAX_MIN + (MathRand() / 32767.0) * (ADWFS_VOLCLIMAX_MAX - ADWFS_VOLCLIMAX_MIN);
break;
case 3:
adWFS.volHigh = ADWFS_VOLHIGH_MIN + (MathRand() / 32767.0) * (ADWFS_VOLHIGH_MAX - ADWFS_VOLHIGH_MIN);
break;
case 4:
adWFS.rangeClimax = ADWFS_RANGECLIMAX_MIN + (MathRand() / 32767.0) * (ADWFS_RANGECLIMAX_MAX - ADWFS_RANGECLIMAX_MIN);
break;
case 5:
adWFS.rangeSignificant = ADWFS_RANGESIGNIF_MIN + (MathRand() / 32767.0) * (ADWFS_RANGESIGNIF_MAX - ADWFS_RANGESIGNIF_MIN);
break;
case 6:
adWFS.stVolRatio = ADWFS_STVOLRATIO_MIN + (MathRand() / 32767.0) * (ADWFS_STVOLRATIO_MAX - ADWFS_STVOLRATIO_MIN);
break;
case 7:
adWFS.atrMult = ADWFS_ATRMULT_MIN + (MathRand() / 32767.0) * (ADWFS_ATRMULT_MAX - ADWFS_ATRMULT_MIN);
break;
}
break;
}
case 4:
{
switch(MathRand() % 4)
{
case 0:
adWSBI.lookback = ADWSBI_LOOKBACK_MIN + MathRand() % (ADWSBI_LOOKBACK_MAX - ADWSBI_LOOKBACK_MIN + 1);
break;
case 1:
adWSBI.rangeSignificant = ADWSBI_RANGESIGNIF_MIN + (MathRand() / 32767.0) * (ADWSBI_RANGESIGNIF_MAX - ADWSBI_RANGESIGNIF_MIN);
break;
case 2:
adWSBI.volumeHigh = ADWSBI_VOLHIGH_MIN + (MathRand() / 32767.0) * (ADWSBI_VOLHIGH_MAX - ADWSBI_VOLHIGH_MIN);
break;
case 3:
adWSBI.atr = ADWSBI_ATR_MIN + (MathRand() / 32767.0) * (ADWSBI_ATR_MAX - ADWSBI_ATR_MIN);
break;
}
break;
}
case 5:
{
//--- the MA feature has TWO tunables now (period + type); pick one at random to perturb
if(MathRand() % 2 == 0)
{
//--- snapped to MA_PERIOD_PRESETS (InputEnums.mqh) so a search never lands on a non-standard period
int maChoices[] = {MA_PERIOD_5, MA_PERIOD_8, MA_PERIOD_9, MA_PERIOD_10, MA_PERIOD_13, MA_PERIOD_20, MA_PERIOD_21, MA_PERIOD_50, MA_PERIOD_100, MA_PERIOD_200};
maPeriod = maChoices[MathRand() % ArraySize(maChoices)];
}
else
{
//--- unified MA type 0..8 (MA_TYPE_PRESETS): SMA/EMA/SMMA/LWMA/ALMA/DEMA/ZLEMA/T3/Kalman
int maTypeChoices[] = {MA_TYPE_SMA, MA_TYPE_EMA, MA_TYPE_SMMA, MA_TYPE_LWMA, MA_TYPE_ALMA, MA_TYPE_DEMA, MA_TYPE_ZLEMA, MA_TYPE_T3, MA_TYPE_KALMAN};
maType = maTypeChoices[MathRand() % ArraySize(maTypeChoices)];
}
break;
}
case 6:
{
//--- snapped to RSI_PERIOD_PRESETS (InputEnums.mqh)
int rsiChoices[] = {RSI_PERIOD_2, RSI_PERIOD_5, RSI_PERIOD_7, RSI_PERIOD_9, RSI_PERIOD_14, RSI_PERIOD_21, RSI_PERIOD_25};
rsiPeriod = rsiChoices[MathRand() % ArraySize(rsiChoices)];
break;
}
case 7:
{
//--- the MACD feature has THREE tunables (fast/slow/signal); pick one at random. No cross-check
//--- against the others is needed - the MACD_*_PRESETS sets never overlap, so any fast is always
//--- below any slow (see InputEnums.mqh).
switch(MathRand() % 3)
{
case 0:
{
int fastChoices[] = {MACD_FAST_5, MACD_FAST_8, MACD_FAST_12, MACD_FAST_15};
macdFast = fastChoices[MathRand() % ArraySize(fastChoices)];
break;
}
case 1:
{
int slowChoices[] = {MACD_SLOW_17, MACD_SLOW_21, MACD_SLOW_26, MACD_SLOW_34, MACD_SLOW_50};
macdSlow = slowChoices[MathRand() % ArraySize(slowChoices)];
break;
}
case 2:
{
int signalChoices[] = {MACD_SIGNAL_5, MACD_SIGNAL_7, MACD_SIGNAL_9, MACD_SIGNAL_12};
macdSignal = signalChoices[MathRand() % ArraySize(signalChoices)];
break;
}
}
break;
}
case 8:
{
//--- the Ichimoku feature has THREE tunables (Tenkan/Kijun/Senkou B); pick one at random. Same
//--- non-overlapping-presets guarantee as MACD above keeps Tenkan < Kijun < Senkou B holding
//--- whichever single one this trial moves.
switch(MathRand() % 3)
{
case 0:
{
int tenkanChoices[] = {ICHI_TENKAN_7, ICHI_TENKAN_9, ICHI_TENKAN_12, ICHI_TENKAN_20};
ichiTenkan = tenkanChoices[MathRand() % ArraySize(tenkanChoices)];
break;
}
case 1:
{
int kijunChoices[] = {ICHI_KIJUN_22, ICHI_KIJUN_26, ICHI_KIJUN_30, ICHI_KIJUN_40};
ichiKijun = kijunChoices[MathRand() % ArraySize(kijunChoices)];
break;
}
case 2:
{
int senkouChoices[] = {ICHI_SENKOU_44, ICHI_SENKOU_52, ICHI_SENKOU_60, ICHI_SENKOU_120};
ichiSenkou = senkouChoices[MathRand() % ArraySize(senkouChoices)];
break;
}
}
break;
}
}
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CADIndicatorTuner::SaveAsBest(void)
{
bestAdCumDelta = adCumDelta;
bestAdSOT = adSOT;
bestAdWES = adWES;
bestAdWFS = adWFS;
bestAdWSBI = adWSBI;
bestMaPeriod = maPeriod;
bestMaType = maType;
bestRsiPeriod = rsiPeriod;
bestMacdFast = macdFast;
bestMacdSlow = macdSlow;
bestMacdSignal = macdSignal;
bestIchiTenkan = ichiTenkan;
bestIchiKijun = ichiKijun;
bestIchiSenkou = ichiSenkou;
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void CADIndicatorTuner::RestoreBest(void)
{
adCumDelta = bestAdCumDelta;
adSOT = bestAdSOT;
adWES = bestAdWES;
adWFS = bestAdWFS;
adWSBI = bestAdWSBI;
maPeriod = bestMaPeriod;
maType = bestMaType;
rsiPeriod = bestRsiPeriod;
macdFast = bestMacdFast;
macdSlow = bestMacdSlow;
macdSignal = bestMacdSignal;
ichiTenkan = bestIchiTenkan;
ichiKijun = bestIchiKijun;
ichiSenkou = bestIchiSenkou;
}
//+------------------------------------------------------------------+