268 lines
No EOL
11 KiB
MQL5
268 lines
No EOL
11 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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;
|
|
// </WEIGHT_CALIBRATION_PENDING>
|
|
|
|
//==============================================================
|
|
// 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};
|
|
// </WEIGHT_CALIBRATION_PENDING>
|
|
|
|
//==============================================================
|
|
// 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;
|
|
} |