//+------------------------------------------------------------------+ //| MarketStateBoard_Fuzzy.mqh | //| PATCH-09 - FUZZY MEMBERSHIP LAYER | //+------------------------------------------------------------------+ // PATCH-09 semantics (frozen by specification): // * Membership is a mathematical degree of membership. It is NOT a // probability, confidence, forecast accuracy or signal-strength // probability. // * The four directional oscillators (RSI, Stochastic, CCI, MACD) each // produce a bullish/neutral/bearish membership. For valid data: // bullish + neutral + bearish == 1. // * This layer is independent from PATCH-05 evidence, PATCH-06 agreement, // PATCH-07 alignment and PATCH-08 panel representation. It computes no // dynamic weights, no composite score, no confidence and no trading // signal. // // CALIBRATION GOVERNANCE: // The sensitivity/reference-scale constants in this file are PLACEHOLDERS // required for compilation only. None of them is an authorized calibration. // They must NOT be optimized, backtested, swept, or tuned to market // observations. Replacement values require a separate authorization. // Exceptions (fixed by the PATCH-09 specification itself): // - FUZZY_EPS validation tolerance // - FUZZY_RSI_PIVOT RSI neutral center (equals PATCH-05 OSC_RSI_NEUTRAL) // - FUZZY_RSI_DENOM RSI domain mapping [0,100] -> [-1,+1] // Invariant-check tolerance for the sum-to-one rule (spec section 6). const double FUZZY_EPS = 1.0e-12; // - unvalidated placeholders, NOT authorized calibration. const double K_RSI = 1.0; // RSI sensitivity k (spec sections 9/10) const double STOCH_REFERENCE_SCALE = 20.0; // stochastic reference scale (spec section 11) const double CCI_REFERENCE_SCALE = 100.0; // CCI reference scale (spec section 12) const double MACD_ATR_SCALE = 1.0; // MACD/ATR sensitivity (spec section 13) // const double FUZZY_RSI_PIVOT = 50.0; // RSI neutral center (PATCH-05: OSC_RSI_NEUTRAL) const double FUZZY_RSI_DENOM = 50.0; // (RSI - 50) / 50 maps [0,100] to [-1,+1] //============================================================== // 1. STRUCTURES //============================================================== struct MSB_FuzzyMembership { double bullish; // degree of bullish membership in [0,1] double neutral; // degree of neutral membership in [0,1] double bearish; // degree of bearish membership in [0,1] bool valid; // false when the source oscillator is unavailable/invalid }; struct MSB_FuzzyOscillatorSet { MSB_FuzzyMembership rsi; MSB_FuzzyMembership stoch; MSB_FuzzyMembership cci; MSB_FuzzyMembership macd; }; //============================================================== // 2. HELPERS //============================================================== bool FuzzyFinite(double v) { return (v == v) && (v < DBL_MAX) && (v > -DBL_MAX); } // Deterministic invalid-data storage (spec section 7): // valid = false with zeroed memberships. The storage is never interpreted // as neutral (valid=false never implies neutral=1). MSB_FuzzyMembership InvalidMembership() { MSB_FuzzyMembership m; m.bullish = 0.0; m.neutral = 0.0; m.bearish = 0.0; m.valid = false; return m; } // Numerically stable tanh: tanh(x) = 1 - 2/(exp(2x)+1). // x -> -inf : exp(2x) -> 0 => result -> -1 // x -> +inf : exp(2x) -> inf => result -> +1 double FuzzyTanh(double x) { double e = MathExp(2.0 * x); return 1.0 - 2.0 / (e + 1.0); } // Membership construction from a signed signal (spec section 14): // 1) validate input, 2) reject NaN/invalid values, // 3) construct memberships, 4) clamp to [0,1] (numerical safety only), // 5) verify the sum-to-one invariant. // An unexpected invariant failure yields valid=false and is NEVER silently // renormalized. MSB_FuzzyMembership MembershipFromSignedSignal(double s) { if(!FuzzyFinite(s)) return InvalidMembership(); // The signed signal must stay inside the fuzzy domain [-1,+1]. The small // tolerance covers only floating-point representation edge cases such as // tanh(x) rounding exactly to 1.0. if(s < -1.0 - FUZZY_EPS || s > 1.0 + FUZZY_EPS) return InvalidMembership(); double bull = MathMax(0.0, s); double bear = MathMax(0.0, -s); double neut = 1.0 - MathAbs(s); bull = MathMin(1.0, MathMax(0.0, bull)); neut = MathMin(1.0, MathMax(0.0, neut)); bear = MathMin(1.0, MathMax(0.0, bear)); if(MathAbs((bull + neut + bear) - 1.0) > FUZZY_EPS) return InvalidMembership(); MSB_FuzzyMembership m; m.bullish = bull; m.neutral = neut; m.bearish = bear; m.valid = true; return m; } //============================================================== // 3. PER-OSCILLATOR MEMBERSHIP //============================================================== // RSI (spec section 10): // z_RSI = clamp((RSI - 50.0) / 50.0, -1, +1) // clamp is defensive only // s_RSI = tanh(K_RSI * z_RSI) // RSI == 50 is the neutral center (PATCH-05: EV_NEUTRAL). MSB_FuzzyMembership ComputeRsiMembership(double rsi) { if(!FuzzyFinite(rsi)) return InvalidMembership(); double z = (rsi - FUZZY_RSI_PIVOT) / FUZZY_RSI_DENOM; z = MathMin( 1.0, MathMax(-1.0, z)); return MembershipFromSignedSignal(FuzzyTanh(K_RSI * z)); } // Stochastic (spec section 11): // d_Stoch = K - D // z_Stoch = clamp(d_Stoch / STOCH_REFERENCE_SCALE, -1, +1) // s_Stoch = tanh(z_Stoch) // common fuzzy transformation // K == D is the neutral center (PATCH-05: EV_NEUTRAL). // No 80/20 thresholds and no crossover events. MSB_FuzzyMembership ComputeStochasticMembership(double k, double d) { if(!FuzzyFinite(k) || !FuzzyFinite(d)) return InvalidMembership(); double dStoch = k - d; double z = dStoch / STOCH_REFERENCE_SCALE; z = MathMin( 1.0, MathMax(-1.0, z)); return MembershipFromSignedSignal(FuzzyTanh(z)); } // CCI (spec section 12): // s_CCI = tanh(CCI / CCI_REFERENCE_SCALE) // CCI == 0 is the neutral center (PATCH-05: EV_NEUTRAL). // No +/-100 direction thresholds and no slope/crossover logic. MSB_FuzzyMembership ComputeCciMembership(double cci) { if(!FuzzyFinite(cci)) return InvalidMembership(); return MembershipFromSignedSignal(FuzzyTanh(cci / CCI_REFERENCE_SCALE)); } // MACD (spec section 13): // MACD_DELTA = MAIN - SIGNAL // s_MACD = tanh(MACD_DELTA / (MACD_ATR_SCALE * ATR)) // MAIN == SIGNAL is the neutral center (PATCH-05: EV_NEUTRAL). // ATR must be finite and strictly positive (same timeframe, same selected // candle, same ATR handle already used by the indicator). Invalid ATR yields // valid=false; it is NEVER converted to neutral. No MAIN > 0 condition. MSB_FuzzyMembership ComputeMacdMembership(double macdMain, double macdSignal, double atr) { if(!FuzzyFinite(macdMain) || !FuzzyFinite(macdSignal) || !FuzzyFinite(atr)) return InvalidMembership(); if(atr <= 0.0) return InvalidMembership(); double delta = macdMain - macdSignal; return MembershipFromSignedSignal(FuzzyTanh(delta / (MACD_ATR_SCALE * atr))); }