//+------------------------------------------------------------------+ //| ADIndicatorTuner.mqh | //| AnimateDread | //| https://www.mql5.com | //+------------------------------------------------------------------+ #include "..\Variables\IndicatorTuneRanges.mqh" //--- flat count of AutoTuneIndicators-tunable params across all 5 AD indicators, 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. #define AD_TUNE_PARAM_COUNT 30 //+------------------------------------------------------------------+ //| 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; }; 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; //--- 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; 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, within IndicatorTuneRanges.mqh //--- bounds. useCumDelta/useSOT/useWES/useWFS/useWSBI mirror CExpertSignalAIBase's //--- m_useADCumulativeDelta/etc. enable flags - no-op if all five are false. void PerturbRandom(bool useCumDelta, bool useSOT, bool useWES, bool useWFS, bool useWSBI); //--- 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; 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; 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++] = 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; } //+------------------------------------------------------------------+ //| 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) 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++]; 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++]; } //+------------------------------------------------------------------+ //| 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) { int enabled[5], 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(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() % 8) { 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; } 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; } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CADIndicatorTuner::SaveAsBest(void) { bestAdCumDelta = adCumDelta; bestAdSOT = adSOT; bestAdWES = adWES; bestAdWFS = adWFS; bestAdWSBI = adWSBI; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CADIndicatorTuner::RestoreBest(void) { adCumDelta = bestAdCumDelta; adSOT = bestAdSOT; adWES = bestAdWES; adWFS = bestAdWFS; adWSBI = bestAdWSBI; } //+------------------------------------------------------------------+