766 lines
36 KiB
MQL5
766 lines
36 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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;
|
|
}
|
|
//+------------------------------------------------------------------+
|