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