refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| ADIndicatorTuner.mqh |
|
|
|
|
|
//| AnimateDread |
|
|
|
|
|
//| https://www.mql5.com |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
#include "..\Variables\IndicatorTuneRanges.mqh"
|
2026-07-26 18:33:12 -04:00
|
|
|
//--- 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
|
2026-07-22 22:51:04 -04:00
|
|
|
//--- (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.
|
2026-07-26 18:33:12 -04:00
|
|
|
//--- 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.
|
2026-08-02 17:08:48 -04:00
|
|
|
#define AD_TUNE_PARAM_COUNT 42
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| 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;
|
2026-08-02 17:08:48 -04:00
|
|
|
//--- 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;
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
};
|
|
|
|
|
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;
|
2026-07-22 22:51:04 -04:00
|
|
|
//--- 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;
|
feat: add unified MA type support to indicator tuner
Add `MA_TYPE_PRESETS` enum covering advanced (ALMA, DEMA, ZLEMA, T3, Kalman) and standard (SMA, EMA, SMMA, LWMA) moving averages. Integrate `maType` and `bestMaType` into `CADIndicatorTuner` struct, update flatten/unflatten routines, and bump `AD_TUNE_PARAM_COUNT` to 33. This allows the auto-tuner to search over MA type alongside period, improving feature discovery.
2026-07-23 15:02:09 -04:00
|
|
|
//--- 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;
|
2026-07-22 22:51:04 -04:00
|
|
|
int rsiPeriod;
|
2026-07-26 18:33:12 -04:00
|
|
|
//--- 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;
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
//--- 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;
|
2026-07-22 22:51:04 -04:00
|
|
|
int bestMaPeriod;
|
feat: add unified MA type support to indicator tuner
Add `MA_TYPE_PRESETS` enum covering advanced (ALMA, DEMA, ZLEMA, T3, Kalman) and standard (SMA, EMA, SMMA, LWMA) moving averages. Integrate `maType` and `bestMaType` into `CADIndicatorTuner` struct, update flatten/unflatten routines, and bump `AD_TUNE_PARAM_COUNT` to 33. This allows the auto-tuner to search over MA type alongside period, improving feature discovery.
2026-07-23 15:02:09 -04:00
|
|
|
int bestMaType;
|
2026-07-22 22:51:04 -04:00
|
|
|
int bestRsiPeriod;
|
2026-07-26 18:33:12 -04:00
|
|
|
int bestMacdFast;
|
|
|
|
|
int bestMacdSlow;
|
|
|
|
|
int bestMacdSignal;
|
|
|
|
|
int bestIchiTenkan;
|
|
|
|
|
int bestIchiKijun;
|
|
|
|
|
int bestIchiSenkou;
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
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[]);
|
2026-07-26 18:33:12 -04:00
|
|
|
//--- 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);
|
2026-07-26 18:33:12 -04:00
|
|
|
void PerturbRandom(bool useCumDelta, bool useSOT, bool useWES, bool useWFS, bool useWSBI, bool useMA, bool useRSI, bool useMACD, bool useIchimoku);
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
//--- 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;
|
2026-08-02 17:08:48 -04:00
|
|
|
adWES.touchATR = 0.5;
|
|
|
|
|
adWES.arMinATR = 1.0;
|
|
|
|
|
adWES.maxRangeBars = 200;
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
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;
|
2026-07-22 22:51:04 -04:00
|
|
|
maPeriod = PeriodMA;
|
feat: add unified MA type support to indicator tuner
Add `MA_TYPE_PRESETS` enum covering advanced (ALMA, DEMA, ZLEMA, T3, Kalman) and standard (SMA, EMA, SMMA, LWMA) moving averages. Integrate `maType` and `bestMaType` into `CADIndicatorTuner` struct, update flatten/unflatten routines, and bump `AD_TUNE_PARAM_COUNT` to 33. This allows the auto-tuner to search over MA type alongside period, improving feature discovery.
2026-07-23 15:02:09 -04:00
|
|
|
maType = MA_Type;
|
2026-07-22 22:51:04 -04:00
|
|
|
rsiPeriod = PeriodRSI;
|
2026-07-26 18:33:12 -04:00
|
|
|
macdFast = MACD_PeriodFast;
|
|
|
|
|
macdSlow = MACD_PeriodSlow;
|
|
|
|
|
macdSignal = MACD_PeriodSignal;
|
|
|
|
|
ichiTenkan = Ichimoku_PeriodTenkan;
|
|
|
|
|
ichiKijun = Ichimoku_PeriodKijun;
|
|
|
|
|
ichiSenkou = Ichimoku_PeriodSenkou;
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
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;
|
2026-08-02 17:08:48 -04:00
|
|
|
arr[i++] = adWES.touchATR;
|
|
|
|
|
arr[i++] = adWES.arMinATR;
|
|
|
|
|
arr[i++] = adWES.maxRangeBars;
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
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;
|
2026-07-22 22:51:04 -04:00
|
|
|
arr[i++] = maPeriod;
|
feat: add unified MA type support to indicator tuner
Add `MA_TYPE_PRESETS` enum covering advanced (ALMA, DEMA, ZLEMA, T3, Kalman) and standard (SMA, EMA, SMMA, LWMA) moving averages. Integrate `maType` and `bestMaType` into `CADIndicatorTuner` struct, update flatten/unflatten routines, and bump `AD_TUNE_PARAM_COUNT` to 33. This allows the auto-tuner to search over MA type alongside period, improving feature discovery.
2026-07-23 15:02:09 -04:00
|
|
|
arr[i++] = maType;
|
2026-07-22 22:51:04 -04:00
|
|
|
arr[i++] = rsiPeriod;
|
2026-07-26 18:33:12 -04:00
|
|
|
arr[i++] = macdFast;
|
|
|
|
|
arr[i++] = macdSlow;
|
|
|
|
|
arr[i++] = macdSignal;
|
|
|
|
|
arr[i++] = ichiTenkan;
|
|
|
|
|
arr[i++] = ichiKijun;
|
|
|
|
|
arr[i++] = ichiSenkou;
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| 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)
|
2026-07-26 12:12:14 -04:00
|
|
|
{
|
|
|
|
|
// 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.");
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
return;
|
2026-07-26 12:12:14 -04:00
|
|
|
}
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
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++];
|
2026-08-02 17:08:48 -04:00
|
|
|
adWES.touchATR = arr[i++];
|
|
|
|
|
adWES.arMinATR = arr[i++];
|
|
|
|
|
adWES.maxRangeBars = (int)MathRound(arr[i++]);
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
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++];
|
2026-07-22 22:51:04 -04:00
|
|
|
maPeriod = (int)MathRound(arr[i++]);
|
feat: add unified MA type support to indicator tuner
Add `MA_TYPE_PRESETS` enum covering advanced (ALMA, DEMA, ZLEMA, T3, Kalman) and standard (SMA, EMA, SMMA, LWMA) moving averages. Integrate `maType` and `bestMaType` into `CADIndicatorTuner` struct, update flatten/unflatten routines, and bump `AD_TUNE_PARAM_COUNT` to 33. This allows the auto-tuner to search over MA type alongside period, improving feature discovery.
2026-07-23 15:02:09 -04:00
|
|
|
maType = (int)MathRound(arr[i++]);
|
2026-07-22 22:51:04 -04:00
|
|
|
rsiPeriod = (int)MathRound(arr[i++]);
|
2026-07-26 18:33:12 -04:00
|
|
|
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(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
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)
|
2026-08-02 17:08:48 -04:00
|
|
|
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
|
2026-08-02 17:08:48 -04:00
|
|
|
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)
|
2026-08-02 17:08:48 -04:00
|
|
|
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);
|
|
|
|
|
}
|
2026-08-02 17:08:48 -04:00
|
|
|
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);
|
|
|
|
|
}
|
2026-08-02 17:08:48 -04:00
|
|
|
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);
|
|
|
|
|
}
|
2026-08-02 17:08:48 -04:00
|
|
|
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);
|
|
|
|
|
}
|
2026-08-02 17:08:48 -04:00
|
|
|
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);
|
|
|
|
|
}
|
2026-08-02 17:08:48 -04:00
|
|
|
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);
|
|
|
|
|
}
|
2026-08-02 17:08:48 -04:00
|
|
|
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);
|
|
|
|
|
}
|
2026-08-02 17:08:48 -04:00
|
|
|
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);
|
|
|
|
|
}
|
2026-08-02 17:08:48 -04:00
|
|
|
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;
|
2026-08-02 17:08:48 -04:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
//| Randomly perturbs one tunable param of one randomly-chosen |
|
|
|
|
|
//| *enabled* AD indicator, within IndicatorTuneRanges.mqh bounds. |
|
|
|
|
|
//| No-op if no AD indicator is enabled. |
|
|
|
|
|
//+------------------------------------------------------------------+
|
2026-07-26 18:33:12 -04:00
|
|
|
void CADIndicatorTuner::PerturbRandom(bool useCumDelta, bool useSOT, bool useWES, bool useWFS, bool useWSBI, bool useMA, bool useRSI, bool useMACD, bool useIchimoku)
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
{
|
2026-07-26 18:33:12 -04:00
|
|
|
int enabled[9], n = 0;
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
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;
|
2026-07-22 22:51:04 -04:00
|
|
|
if(useMA)
|
|
|
|
|
enabled[n++] = 5;
|
|
|
|
|
if(useRSI)
|
|
|
|
|
enabled[n++] = 6;
|
2026-07-26 18:33:12 -04:00
|
|
|
if(useMACD)
|
|
|
|
|
enabled[n++] = 7;
|
|
|
|
|
if(useIchimoku)
|
|
|
|
|
enabled[n++] = 8;
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
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:
|
|
|
|
|
{
|
2026-08-02 17:08:48 -04:00
|
|
|
switch(MathRand() % 11)
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
{
|
|
|
|
|
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;
|
2026-08-02 17:08:48 -04:00
|
|
|
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;
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
2026-07-22 22:51:04 -04:00
|
|
|
case 5:
|
|
|
|
|
{
|
feat: add unified MA type support to indicator tuner
Add `MA_TYPE_PRESETS` enum covering advanced (ALMA, DEMA, ZLEMA, T3, Kalman) and standard (SMA, EMA, SMMA, LWMA) moving averages. Integrate `maType` and `bestMaType` into `CADIndicatorTuner` struct, update flatten/unflatten routines, and bump `AD_TUNE_PARAM_COUNT` to 33. This allows the auto-tuner to search over MA type alongside period, improving feature discovery.
2026-07-23 15:02:09 -04:00
|
|
|
//--- 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)];
|
|
|
|
|
}
|
2026-07-22 22:51:04 -04:00
|
|
|
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;
|
|
|
|
|
}
|
2026-07-26 18:33:12 -04:00
|
|
|
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;
|
|
|
|
|
}
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void CADIndicatorTuner::SaveAsBest(void)
|
|
|
|
|
{
|
|
|
|
|
bestAdCumDelta = adCumDelta;
|
|
|
|
|
bestAdSOT = adSOT;
|
|
|
|
|
bestAdWES = adWES;
|
|
|
|
|
bestAdWFS = adWFS;
|
|
|
|
|
bestAdWSBI = adWSBI;
|
2026-07-22 22:51:04 -04:00
|
|
|
bestMaPeriod = maPeriod;
|
feat: add unified MA type support to indicator tuner
Add `MA_TYPE_PRESETS` enum covering advanced (ALMA, DEMA, ZLEMA, T3, Kalman) and standard (SMA, EMA, SMMA, LWMA) moving averages. Integrate `maType` and `bestMaType` into `CADIndicatorTuner` struct, update flatten/unflatten routines, and bump `AD_TUNE_PARAM_COUNT` to 33. This allows the auto-tuner to search over MA type alongside period, improving feature discovery.
2026-07-23 15:02:09 -04:00
|
|
|
bestMaType = maType;
|
2026-07-22 22:51:04 -04:00
|
|
|
bestRsiPeriod = rsiPeriod;
|
2026-07-26 18:33:12 -04:00
|
|
|
bestMacdFast = macdFast;
|
|
|
|
|
bestMacdSlow = macdSlow;
|
|
|
|
|
bestMacdSignal = macdSignal;
|
|
|
|
|
bestIchiTenkan = ichiTenkan;
|
|
|
|
|
bestIchiKijun = ichiKijun;
|
|
|
|
|
bestIchiSenkou = ichiSenkou;
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
void CADIndicatorTuner::RestoreBest(void)
|
|
|
|
|
{
|
|
|
|
|
adCumDelta = bestAdCumDelta;
|
|
|
|
|
adSOT = bestAdSOT;
|
|
|
|
|
adWES = bestAdWES;
|
|
|
|
|
adWFS = bestAdWFS;
|
|
|
|
|
adWSBI = bestAdWSBI;
|
2026-07-22 22:51:04 -04:00
|
|
|
maPeriod = bestMaPeriod;
|
feat: add unified MA type support to indicator tuner
Add `MA_TYPE_PRESETS` enum covering advanced (ALMA, DEMA, ZLEMA, T3, Kalman) and standard (SMA, EMA, SMMA, LWMA) moving averages. Integrate `maType` and `bestMaType` into `CADIndicatorTuner` struct, update flatten/unflatten routines, and bump `AD_TUNE_PARAM_COUNT` to 33. This allows the auto-tuner to search over MA type alongside period, improving feature discovery.
2026-07-23 15:02:09 -04:00
|
|
|
maType = bestMaType;
|
2026-07-22 22:51:04 -04:00
|
|
|
rsiPeriod = bestRsiPeriod;
|
2026-07-26 18:33:12 -04:00
|
|
|
macdFast = bestMacdFast;
|
|
|
|
|
macdSlow = bestMacdSlow;
|
|
|
|
|
macdSignal = bestMacdSignal;
|
|
|
|
|
ichiTenkan = bestIchiTenkan;
|
|
|
|
|
ichiKijun = bestIchiKijun;
|
|
|
|
|
ichiSenkou = bestIchiSenkou;
|
refactor(ExpertSignalAIBase): extract AutoTune param state into CADIndicatorTuner
CExpertSignalAIBase (4,326 lines, one class) carried 5 struct
definitions, 10 member fields, and 3 methods (Flatten/Unflatten/
PerturbRandom) purely for the AutoTuneIndicators search-space state -
entirely self-contained (never touches Net, Train()'s resumable state
machine, or anything else in the class). Extracted into a new
Expert/ADIndicatorTuner.mqh (CADIndicatorTuner), held as a single
m_indicatorTuner member.
TuneIndicatorsAndTrain() itself - the outer loop that actually
orchestrates Train()/Net/checkpointing around this tuner - turned out
to be exactly as tightly coupled to Train()'s resumable state machine
as Train() itself, so per the same caution already applied to Train()
in this refactor pass, it stays in CExpertSignalAIBase rather than
being pulled into the collaborator; it now calls the tuner's public
Flatten()/Unflatten()/PerturbRandom()/SaveAsBest()/RestoreBest()
instead of manipulating the structs inline.
All internal field-access renames (m_adCumDeltaParams.lookback ->
m_indicatorTuner.adCumDelta.lookback, etc., ~40 sites across the 5
InitAD*() indicator-setup methods) verified against a full grep sweep
- no leftover references to the old field/method names. Compiled
clean (MetaEditor, 0 errors/0 warnings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 16:22:09 -04:00
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|