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