//+------------------------------------------------------------------+ //| MarketStateBoard_Weights.mqh | //| PATCH-11 - DYNAMIC WEIGHT ENGINE | //+------------------------------------------------------------------+ // PATCH-11 semantics (frozen by specification): // * Converts the direction-neutral PATCH-10 Context Quality components // (atrQuality, volumeQuality, bodyQuality, closeLocationQuality) into // deterministic, per-timeframe dynamic weights for the four fuzzy // oscillator channels: RSI, Stochastic, CCI, MACD. // * A dynamic weight is the contextual influence assigned to an evidence // channel under the current market context. It is NOT a confidence, a // probability, statistical importance proven by history, predictive // quality, expected return or trade-success probability. // * This layer consumes ONLY MSB_ContextQuality. It never reads barDir, // rsiState/stochState/cciState/macdState, oscillatorAgreement or // alignState, so the same context produces the same channel weights // regardless of whether the current market direction is bullish or // bearish (direction-neutrality invariant). // * No score, probability, confidence, trade-outcome feedback, optimizer, // temporal smoothing, recursive weight carry-over or cross-timeframe // input exists in this layer. // * Exactly one authoritative implementation: the indicator and the static // validation harness include this file verbatim. // // INCLUDE CONTRACT: // MSB_ContextQuality (PATCH-10) must be declared BEFORE including this file // (the indicator declares it in section 5; the validation harness declares // an identical fixture struct). // // CALIBRATION GOVERNANCE (spec section 36): // The base weights, WEIGHT_EPS and the channel profiles below are // provisional structural placeholders marked WEIGHT_CALIBRATION_PENDING. // None of them is an authorized calibration and none may be claimed // empirically validated or predictively superior. They must NOT be // optimized, backtested, swept or tuned to market observations; // replacement values require a separate authorization. // WEIGHT_CALIBRATION_PENDING // Base structural weights (spec section 8): equal structural baselines that // express the absence of an empirically established preference before // contextual modulation. Not probabilities. Not tuned from market outcomes. const double BASE_RSI = 1.0; const double BASE_STOCH = 1.0; const double BASE_CCI = 1.0; const double BASE_MACD = 1.0; // Weight-normalization tolerance (spec section 17). Must not be made large // enough to hide numerical errors. const double WEIGHT_EPS = 1.0e-12; // //============================================================== // 1. STRUCTURES //============================================================== struct MSB_DynamicWeights { double rsi; // normalized dynamic weight for the RSI channel [0,1] double stoch; // normalized dynamic weight for the Stochastic channel [0,1] double cci; // normalized dynamic weight for the CCI channel [0,1] double macd; // normalized dynamic weight for the MACD channel [0,1] bool valid; // false when context is unusable; weight fields are storage only }; // Fixed structural context profile of one oscillator channel (spec section 11). // Contract per channel: every coefficient >= 0 and // atr + volume + body + closeLocation == 1. The profile states how compatible // the current market context is with the structural characteristics of that // oscillator; it is not directional and not an accuracy statement. struct MSB_ContextProfile { double atr; double volume; double body; double closeLocation; }; //============================================================== // 2. CHANNEL CONTEXT PROFILES //============================================================== // WEIGHT_CALIBRATION_PENDING // Provisional channel-specific context profiles (spec section 12). These // values are structural placeholders - an explicit, deterministic research // starting specification. The coding agent must NOT change them because of // observed runtime behavior; they are not statistically validated. // RSI : 0.25 + 0.15 + 0.25 + 0.35 = 1.00 // STOCH: 0.20 + 0.20 + 0.25 + 0.35 = 1.00 // CCI : 0.25 + 0.15 + 0.30 + 0.30 = 1.00 // MACD : 0.40 + 0.15 + 0.35 + 0.10 = 1.00 const MSB_ContextProfile MSB_PROFILE_RSI = {0.25, 0.15, 0.25, 0.35}; const MSB_ContextProfile MSB_PROFILE_STOCH = {0.20, 0.20, 0.25, 0.35}; const MSB_ContextProfile MSB_PROFILE_CCI = {0.25, 0.15, 0.30, 0.30}; const MSB_ContextProfile MSB_PROFILE_MACD = {0.40, 0.15, 0.35, 0.10}; // //============================================================== // 3. HELPERS //============================================================== bool WeightFinite(double v) { return (v == v) && (v < DBL_MAX) && (v > -DBL_MAX); } // Defensive domain clamp to [0,1]. The PATCH-10 contract already guarantees // qualities in [0,1]; the clamp only guards floating-point edge cases. double Clamp01(double v) { return MathMin(1.0, MathMax(0.0, v)); } // True when at least one context component is usable (valid flag AND finite // quality). An all-unusable context can never produce weights (spec section // 15: do not invent default weights from unavailable context). bool HasUsableContext(const MSB_ContextQuality &ctx) { return (ctx.atrValid && WeightFinite(ctx.atrQuality)) || (ctx.volumeValid && WeightFinite(ctx.volumeQuality)) || (ctx.bodyValid && WeightFinite(ctx.bodyQuality)) || (ctx.closeLocationValid && WeightFinite(ctx.closeLocationQuality)); } //============================================================== // 4. SUITABILITY //============================================================== // Contextual suitability of one channel (spec sections 13/14/19): // s_i = (sum over USABLE k of p_i,k * Q_k) / (sum over USABLE k of p_i,k) // The denominator renormalizes the used profile coefficients so their sum // equals 1. Missing context components are never substituted with NORMAL or // 0.5 and never given a zero influence automatically: they are excluded and // the remaining used coefficients are renormalized. A profile as a whole is // never silently renormalized here - a malformed profile must be caught by // the profile sanity verification, not hidden by this function. // A non-finite quality inside a valid-flagged component makes only that // component unusable; the remaining usable components still define the // context. // Returns 0.0 when no component is usable. ComputeDynamicWeights already // rejects that case via HasUsableContext; this guards the public API only. double ComputeChannelSuitability(const MSB_ContextQuality &ctx, const MSB_ContextProfile &profile) { bool atrOk = ctx.atrValid && WeightFinite(ctx.atrQuality); bool volOk = ctx.volumeValid && WeightFinite(ctx.volumeQuality); bool bodyOk = ctx.bodyValid && WeightFinite(ctx.bodyQuality); bool closeOk = ctx.closeLocationValid && WeightFinite(ctx.closeLocationQuality); double num = 0.0; double den = 0.0; if(atrOk) { num += profile.atr * Clamp01(ctx.atrQuality); den += profile.atr; } if(volOk) { num += profile.volume * Clamp01(ctx.volumeQuality); den += profile.volume; } if(bodyOk) { num += profile.body * Clamp01(ctx.bodyQuality); den += profile.body; } if(closeOk) { num += profile.closeLocation * Clamp01(ctx.closeLocationQuality); den += profile.closeLocation; } if(den <= 0.0) return 0.0; // no usable component -> no suitability evidence double s = num / den; // Defensive clamp (spec section 29): with qualities in [0,1] and // non-negative coefficients the weighted average is already inside // [0,1]; gross corruption is rejected by ComputeDynamicWeights, never // silently renormalized. return MathMin(1.0, MathMax(0.0, s)); } //============================================================== // 5. DYNAMIC WEIGHTS //============================================================== // Deterministic invalid storage (spec section 7): zeroed weights, valid=false. MSB_DynamicWeights InvalidDynamicWeights() { MSB_DynamicWeights w; w.rsi = 0.0; w.stoch = 0.0; w.cci = 0.0; w.macd = 0.0; w.valid = false; return w; } // Per-timeframe dynamic weight vector (spec sections 16/17/18/29): // r_i = BASE_i * s_i (raw weights, explicit baseline) // w_i = r_i / sum(r_j) when sum(r_j) > WEIGHT_EPS // Invariants enforced before returning valid=true: // * every suitability finite and inside [0,1] // * sum(raw) > WEIGHT_EPS (never divide by zero) // * every weight inside [0,1] and sum(w) == 1 within WEIGHT_EPS // Any unexpected violation yields valid=false - never silent renormalization. // Stateless by design (spec section 27): current context -> current weights. MSB_DynamicWeights ComputeDynamicWeights(const MSB_ContextQuality &ctx) { MSB_DynamicWeights out = InvalidDynamicWeights(); // All context invalid: weights are unavailable, no invented defaults // (spec section 15). if(!HasUsableContext(ctx)) return out; double sRsi = ComputeChannelSuitability(ctx, MSB_PROFILE_RSI); double sStoch = ComputeChannelSuitability(ctx, MSB_PROFILE_STOCH); double sCci = ComputeChannelSuitability(ctx, MSB_PROFILE_CCI); double sMacd = ComputeChannelSuitability(ctx, MSB_PROFILE_MACD); // Numerical safety (spec section 29): suitability finite and in [0,1]. if(!WeightFinite(sRsi) || sRsi < 0.0 || sRsi > 1.0 || !WeightFinite(sStoch) || sStoch < 0.0 || sStoch > 1.0 || !WeightFinite(sCci) || sCci < 0.0 || sCci > 1.0 || !WeightFinite(sMacd) || sMacd < 0.0 || sMacd > 1.0) return out; // Raw structural weights (spec section 16): the baseline expression is // kept explicit so future research can distinguish the baseline from the // contextual modulation. double rRsi = BASE_RSI * sRsi; double rStoch = BASE_STOCH * sStoch; double rCci = BASE_CCI * sCci; double rMacd = BASE_MACD * sMacd; double sumRaw = rRsi + rStoch + rCci + rMacd; // Zero raw-weight condition (spec section 18): valid=false; no division // by zero, no equal fallback weights. if(sumRaw <= WEIGHT_EPS) return out; // Normalization (spec section 17): w_i = r_i / sum(r_j). double wRsi = rRsi / sumRaw; double wStoch = rStoch / sumRaw; double wCci = rCci / sumRaw; double wMacd = rMacd / sumRaw; // Numerical safety (spec section 29): defensive clamp after the division. wRsi = MathMin(1.0, MathMax(0.0, wRsi)); wStoch = MathMin(1.0, MathMax(0.0, wStoch)); wCci = MathMin(1.0, MathMax(0.0, wCci)); wMacd = MathMin(1.0, MathMax(0.0, wMacd)); // Weight invariant: sum(w) == 1 within WEIGHT_EPS or the state is invalid. double sumW = wRsi + wStoch + wCci + wMacd; if(MathAbs(sumW - 1.0) > WEIGHT_EPS) return InvalidDynamicWeights(); out.rsi = wRsi; out.stoch = wStoch; out.cci = wCci; out.macd = wMacd; out.valid = true; return out; }