188 lines
7.1 KiB
MQL5
188 lines
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)));
| |||
}
|