268 lines
11 KiB
MQL5
268 lines
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;
| |||
}
|