Market_State_Board/Include/MarketStateBoard_Fuzzy.mqh

188 lines
No EOL
7.1 KiB
MQL5

//+------------------------------------------------------------------+
//| 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;
// <CALIBRATION_PENDING> - 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)
// </CALIBRATION_PENDING>
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)));
}