//+------------------------------------------------------------------+ //| MarketStateBoard_MDM.mqh | //| PATCH-19 - MDM News Dynamic | //+------------------------------------------------------------------+ // Post-Base analytical layer: Base State -> Market Dynamics -> Final State. // Types: CONTINUATION / DAMPENING / REVERSAL / UNRESOLVED. // Strength: WEAK / STRONG / VERY STRONG / EXTREME. // Numeric modifier is internal; it is not a probability/confidence/signal. // FINAL invariants: Response > Event; session is context only; conflicts are // unresolved rather than averaged away; weak/strong reversal cannot zero-cross. // PATCH-19 News Dynamic: calendar information includes Actual/Forecast/Previous/ // Revised Previous, event importance/sector/type, surprise/revision/expectation // shift and response phase. For CLOSED bars the full evaluated candle window is // eligible; CURRENT bars are clamped to server-now so no future event is read. // This include uses only primitive trend-direction codes (+1/-1/0) and does not // access g_state[]. enum MSB_MDM_Type { MDM_CONTINUATION, MDM_DAMPENING, MDM_REVERSAL, MDM_UNRESOLVED, MDM_DATA_UNAVAILABLE }; enum MSB_MDM_Strength { MDM_STRENGTH_NONE, MDM_WEAK, MDM_STRONG, MDM_VERY_STRONG, MDM_EXTREME }; enum MSB_MDM_NewsPhase { MDM_NEWS_NONE, MDM_NEWS_RELEASE_SHOCK, MDM_NEWS_INITIAL_RESPONSE, MDM_NEWS_CONFIRMED_RESPONSE }; enum MSB_MDM_Authority { MDM_AUTH_NONE, MDM_AUTH_DAMPEN_ONLY, MDM_AUTH_MAGNITUDE_ADJUST, MDM_AUTH_ZERO_CROSS_ALLOWED }; struct MSB_MDM_Input { bool valid; ENUM_TIMEFRAMES tf; datetime selectedTime; datetime evaluationEnd; bool currentBar; string symbol; string baseCurrency; string profitCurrency; double open, high, low, close; double previousOpen, previousHigh, previousLow, previousClose; double range, bodyRatio, closeLocation, rangeAtrRatio, volumeRatio; double compositeScore; bool compositeValid; int trendDirection; // +1 UP, -1 DOWN, 0 FLAT/other double trendQuality; bool trendValid; }; struct MSB_MDM_State { bool valid; MSB_MDM_Type type; MSB_MDM_Strength strength; MSB_MDM_Authority authority; int direction; double modifier; int evidenceCount; int continuationEvidence; int dampeningEvidence; int reversalEvidence; bool sessionActive; bool liquidityEvent; bool participationEvent; bool volatilityEvent; bool newsEvent; MSB_MDM_NewsPhase newsPhase; int newsImportance; int newsEventCount; double newsSurprise; double newsRevision; double newsExpectationShift; double newsShock; string newsEventName; string newsEventCode; datetime newsEventTime; bool newsHighImpact; int newsEventType; int newsSector; int newsImpact; bool responseConfirmed; bool conflict; string diagnostic; }; MSB_MDM_Input MDM_InvalidInput() { MSB_MDM_Input x; x.valid=false; x.tf=PERIOD_CURRENT; x.selectedTime=0; x.evaluationEnd=0; x.currentBar=false; x.symbol=""; x.baseCurrency=""; x.profitCurrency=""; x.open=x.high=x.low=x.close=0.0; x.previousOpen=x.previousHigh=x.previousLow=x.previousClose=0.0; x.range=x.bodyRatio=x.closeLocation=x.rangeAtrRatio=x.volumeRatio=0.0; x.compositeScore=0.0; x.compositeValid=false; x.trendDirection=0; x.trendQuality=0.0; x.trendValid=false; return x; } MSB_MDM_State MDM_InvalidState() { MSB_MDM_State m; m.valid=false; m.type=MDM_DATA_UNAVAILABLE; m.strength=MDM_STRENGTH_NONE; m.authority=MDM_AUTH_NONE; m.direction=0; m.modifier=0.0; m.evidenceCount=0; m.continuationEvidence=0; m.dampeningEvidence=0; m.reversalEvidence=0; m.sessionActive=false; m.liquidityEvent=false; m.participationEvent=false; m.volatilityEvent=false; m.newsEvent=false; m.newsPhase=MDM_NEWS_NONE; m.newsImportance=0; m.newsEventCount=0; m.newsSurprise=0.0; m.newsRevision=0.0; m.newsExpectationShift=0.0; m.newsShock=0.0; m.newsEventName=""; m.newsEventCode=""; m.newsEventTime=0; m.newsHighImpact=false; m.newsEventType=0; m.newsSector=0; m.newsImpact=0; m.responseConfirmed=false; m.conflict=false; m.diagnostic="DATA UNAVAILABLE"; return m; } bool MDM_Finite(double v) { return (v==v) && (v-DBL_MAX); } double MDM_Clamp(double v,double lo,double hi) { return MathMax(lo,MathMin(hi,v)); } int MDM_StrengthRank(MSB_MDM_Strength s) { switch(s) { case MDM_WEAK: return 1; case MDM_STRONG: return 2; case MDM_VERY_STRONG: return 3; case MDM_EXTREME: return 4; default: return 0; } } int MDM_Sign(double v) { if(v>0.0) return 1; if(v<0.0) return -1; return 0; } bool MDM_IsActiveSession(datetime t) { MqlDateTime dt; TimeToStruct(t,dt); // Trade-server-time activity windows are context only; they do not create // bullish/bearish direction. return ((dt.hour>=7 && dt.hour<11) || (dt.hour>=13 && dt.hour<17) || (dt.hour>=17 && dt.hour<21)); } int MDM_PriceResponse(const MSB_MDM_Input &x) { if(!x.valid || x.range<=0.0) return 0; int priorDir=MDM_Sign(x.previousClose-x.previousOpen); int curDir=MDM_Sign(x.close-x.open); if(curDir==0) return 0; if(curDir>0 && x.closeLocation>=0.70) return 1; if(curDir<0 && x.closeLocation<=0.30) return -1; if(priorDir!=0 && curDir==-priorDir) return curDir; return curDir; } int MDM_ImportanceRank(ENUM_CALENDAR_EVENT_IMPORTANCE importance) { switch(importance) { case CALENDAR_IMPORTANCE_HIGH: return 3; case CALENDAR_IMPORTANCE_MODERATE: return 2; case CALENDAR_IMPORTANCE_LOW: return 1; default: return 0; } } double MDM_NewsRelativeDiff(double a,double b) { if(!MDM_Finite(a) || !MDM_Finite(b)) return 0.0; double denom=MathMax(MathMax(MathAbs(a),MathAbs(b)),1.0); return MDM_Clamp(MathAbs(a-b)/denom,0.0,1.0); } bool MDM_CalendarCurrencyRelevant(const MqlCalendarEvent &event,const string &base,const string &profit) { MqlCalendarCountry country; if(!CalendarCountryById(event.country_id,country)) return false; if(base!="" && StringFind(country.currency,base)>=0) return true; if(profit!="" && StringFind(country.currency,profit)>=0) return true; // Preserve the project's XAUUSD focus even if symbol currency metadata is unavailable. if(base=="" && profit=="" && StringFind("USD","USD")>=0) return (StringFind(country.currency,"USD")>=0); return false; } int MDM_NewsCacheSlot(ENUM_TIMEFRAMES tf) { switch(tf) { case PERIOD_M1: return 0; case PERIOD_M5: return 1; case PERIOD_M15: return 2; case PERIOD_M30: return 3; case PERIOD_H1: return 4; case PERIOD_H4: return 5; case PERIOD_D1: return 6; case PERIOD_W1: return 7; case PERIOD_MN1: return 8; default: return -1; } } void MDM_CalendarShock(const MSB_MDM_Input &x,MSB_MDM_State &m) { static datetime cacheSelected[9]; static datetime cacheEnd[9]; static bool cacheCurrent[9]; static string cacheBase[9]; static string cacheProfit[9]; static MSB_MDM_State cacheState[9]; int slot=MDM_NewsCacheSlot(x.tf); if(slot>=0 && cacheSelected[slot]==x.selectedTime && cacheEnd[slot]==x.evaluationEnd && cacheCurrent[slot]==x.currentBar && cacheBase[slot]==x.baseCurrency && cacheProfit[slot]==x.profitCurrency) { m.newsEvent=cacheState[slot].newsEvent; m.newsPhase=cacheState[slot].newsPhase; m.newsImportance=cacheState[slot].newsImportance; m.newsEventCount=cacheState[slot].newsEventCount; m.newsSurprise=cacheState[slot].newsSurprise; m.newsRevision=cacheState[slot].newsRevision; m.newsExpectationShift=cacheState[slot].newsExpectationShift; m.newsShock=cacheState[slot].newsShock; m.newsEventName=cacheState[slot].newsEventName; m.newsEventCode=cacheState[slot].newsEventCode; m.newsEventTime=cacheState[slot].newsEventTime; m.newsHighImpact=cacheState[slot].newsHighImpact; m.newsEventType=cacheState[slot].newsEventType; m.newsSector=cacheState[slot].newsSector; m.newsImpact=cacheState[slot].newsImpact; return; } m.newsEvent=false; m.newsPhase=MDM_NEWS_NONE; m.newsImportance=0; m.newsEventCount=0; m.newsSurprise=0.0; m.newsRevision=0.0; m.newsExpectationShift=0.0; m.newsShock=0.0; m.newsEventName=""; m.newsEventCode=""; m.newsEventTime=0; m.newsHighImpact=false; m.newsEventType=0; m.newsSector=0; m.newsImpact=0; if(x.selectedTime<=0) return; datetime from=x.selectedTime-30*60; datetime to=x.evaluationEnd; if(to<=x.selectedTime) to=x.selectedTime; if(x.currentBar) { datetime now=TimeTradeServer(); if(now<=0) now=TimeCurrent(); if(now>0) to=MathMin(to,now); } if(toto) continue; if(!values[i].HasActualValue()) continue; MqlCalendarEvent event; if(!CalendarEventById(values[i].event_id,event)) continue; if(!MDM_CalendarCurrencyRelevant(event,x.baseCurrency,x.profitCurrency) && !(x.baseCurrency=="" && x.profitCurrency=="" && currency=="USD")) continue; double actual=values[i].GetActualValue(); if(!MDM_Finite(actual)) continue; bool hasForecast=values[i].HasForecastValue(); bool hasPrevious=values[i].HasPreviousValue(); bool hasRevised=values[i].HasRevisedValue(); double forecast=hasForecast ? values[i].GetForecastValue() : 0.0; double previous=hasPrevious ? values[i].GetPreviousValue() : 0.0; double revised=hasRevised ? values[i].GetRevisedValue() : 0.0; double surprise=hasForecast ? MDM_NewsRelativeDiff(actual,forecast) : 0.0; double change=hasPrevious ? MDM_NewsRelativeDiff(actual,previous) : 0.0; double revision=(hasRevised && hasPrevious) ? MDM_NewsRelativeDiff(revised,previous) : 0.0; double expectation=(hasForecast && hasPrevious) ? MDM_NewsRelativeDiff(forecast,previous) : 0.0; int importance=MDM_ImportanceRank(event.importance); if(importance<=0) continue; // Materiality is information-content based, not a directional forecast. // A high-importance event remains relevant even when its surprise is small. double material=MathMax(surprise,revision); if(material<=0.0 && expectation<=0.0) material=MathMin(change,0.25); // Importance determines relevance, but does not fabricate surprise. // A high-impact event with no measurable information change is retained // only as context when Actual/Forecast/Previous are present, not as shock. double shock=(double)importance/3.0 * MDM_Clamp(material*2.0,0.0,1.0); if(shock<=0.05) continue; datetime eventEnd=x.evaluationEnd; if(eventEnd<=values[i].time) eventEnd=values[i].time+1; int age=(int)(eventEnd-values[i].time); MSB_MDM_NewsPhase phase=MDM_NEWS_CONFIRMED_RESPONSE; if(age<=5*60) phase=MDM_NEWS_RELEASE_SHOCK; else if(age<=30*60) phase=MDM_NEWS_INITIAL_RESPONSE; m.newsEventCount++; if(shock>bestShock) { bestShock=shock; m.newsEvent=true; m.newsImportance=importance; m.newsSurprise=surprise; m.newsRevision=revision; m.newsExpectationShift=expectation; m.newsShock=shock; m.newsEventName=event.name; m.newsEventCode=event.event_code; m.newsEventTime=values[i].time; m.newsPhase=phase; m.newsHighImpact=(importance>=3); m.newsEventType=(int)event.type; m.newsSector=(int)event.sector; m.newsImpact=(int)values[i].impact_type; } } } if(slot>=0) { cacheSelected[slot]=x.selectedTime; cacheEnd[slot]=x.evaluationEnd; cacheCurrent[slot]=x.currentBar; cacheBase[slot]=x.baseCurrency; cacheProfit[slot]=x.profitCurrency; cacheState[slot]=MDM_InvalidState(); cacheState[slot].newsEvent=m.newsEvent; cacheState[slot].newsPhase=m.newsPhase; cacheState[slot].newsImportance=m.newsImportance; cacheState[slot].newsEventCount=m.newsEventCount; cacheState[slot].newsSurprise=m.newsSurprise; cacheState[slot].newsRevision=m.newsRevision; cacheState[slot].newsExpectationShift=m.newsExpectationShift; cacheState[slot].newsShock=m.newsShock; cacheState[slot].newsEventName=m.newsEventName; cacheState[slot].newsEventCode=m.newsEventCode; cacheState[slot].newsEventTime=m.newsEventTime; cacheState[slot].newsHighImpact=m.newsHighImpact; cacheState[slot].newsEventType=m.newsEventType; cacheState[slot].newsSector=m.newsSector; cacheState[slot].newsImpact=m.newsImpact; } } MSB_MDM_State MDM_Compute(const MSB_MDM_Input &x) { MSB_MDM_State m=MDM_InvalidState(); if(!x.valid || !x.compositeValid || !MDM_Finite(x.compositeScore) || x.compositeScore<-1.0 || x.compositeScore>1.0 || x.range<=0.0 || !MDM_Finite(x.closeLocation) || !MDM_Finite(x.rangeAtrRatio) || !MDM_Finite(x.volumeRatio)) return m; m.valid=true; m.sessionActive=MDM_IsActiveSession(x.selectedTime); int baseDir=MDM_Sign(x.compositeScore); int responseDir=MDM_PriceResponse(x); MDM_CalendarShock(x,m); // Liquidity-response proxy: sweep previous extreme and close back inside. bool sweepUp=(x.high>x.previousHigh && x.closex.previousLow); m.liquidityEvent=(sweepUp || sweepDn); int sweepDir=0; if(sweepUp && !sweepDn) sweepDir=-1; if(sweepDn && !sweepUp) sweepDir=1; m.participationEvent=(x.volumeRatio>=1.30); m.volatilityEvent=(x.rangeAtrRatio>=1.30); // Response > event. The candle response establishes the primary dynamic // direction; event families can only add one reinforcing point each. if(baseDir!=0 && responseDir==baseDir) m.continuationEvidence++; if(baseDir!=0 && responseDir==-baseDir) m.reversalEvidence++; if(baseDir!=0 && responseDir==0) m.dampeningEvidence++; if(sweepDir!=0 && baseDir!=0) { if(sweepDir==-baseDir) m.reversalEvidence++; else m.continuationEvidence++; } if(m.participationEvent && responseDir==baseDir && baseDir!=0) m.continuationEvidence++; if(m.volatilityEvent && responseDir==-baseDir && baseDir!=0) m.reversalEvidence++; if(m.newsEvent && responseDir==baseDir && baseDir!=0) m.continuationEvidence++; if(m.newsEvent && responseDir==-baseDir && baseDir!=0) m.reversalEvidence++; // Trend is confirmation only and never establishes direction. if(x.trendValid && x.trendQuality>=0.70 && baseDir!=0) { if((baseDir>0 && x.trendDirection>0) || (baseDir<0 && x.trendDirection<0)) m.continuationEvidence++; else if((baseDir>0 && x.trendDirection<0) || (baseDir<0 && x.trendDirection>0)) m.reversalEvidence++; } // Weak base + simultaneous compression/low participation = dampening. if(baseDir!=0 && MathAbs(x.compositeScore)<0.30 && x.volumeRatio<0.70 && x.rangeAtrRatio<0.70) m.dampeningEvidence++; int c=m.continuationEvidence, r=m.reversalEvidence, d=m.dampeningEvidence; m.evidenceCount=c+r+d; m.responseConfirmed=(responseDir!=0); // Contradictory branches are not averaged away. if(c>0 && r>0 && MathAbs(c-r)<2) { m.conflict=true; m.type=MDM_UNRESOLVED; m.strength=MDM_WEAK; m.authority=MDM_AUTH_DAMPEN_ONLY; m.direction=baseDir; m.modifier=0.0; m.diagnostic="CONFLICT / UNRESOLVED"; return m; } if(r>0 && r>c && r>=d) { m.type=MDM_REVERSAL; m.direction=-baseDir; if(r>=4 && (m.liquidityEvent || m.newsEvent || m.volatilityEvent)) m.strength=MDM_EXTREME; else if(r>=3) m.strength=MDM_VERY_STRONG; else if(r>=2) m.strength=MDM_STRONG; else m.strength=MDM_WEAK; } else if(c>0 && c>r && c>=d) { m.type=MDM_CONTINUATION; m.direction=baseDir; if(c>=4 && (m.participationEvent || m.volatilityEvent || m.newsEvent)) m.strength=MDM_EXTREME; else if(c>=3) m.strength=MDM_VERY_STRONG; else if(c>=2) m.strength=MDM_STRONG; else m.strength=MDM_WEAK; } else if(d>0) { m.type=MDM_DAMPENING; m.direction=baseDir; if(d>=3) m.strength=MDM_VERY_STRONG; else if(d>=2) m.strength=MDM_STRONG; else m.strength=MDM_WEAK; } else { m.type=MDM_UNRESOLVED; m.strength=MDM_WEAK; m.authority=MDM_AUTH_NONE; m.direction=baseDir; m.modifier=0.0; m.diagnostic="NO COHERENT DYNAMIC"; return m; } if(m.type==MDM_REVERSAL) { if(m.strength==MDM_WEAK) m.authority=MDM_AUTH_DAMPEN_ONLY; else if(m.strength==MDM_STRONG) m.authority=MDM_AUTH_MAGNITUDE_ADJUST; else m.authority=MDM_AUTH_ZERO_CROSS_ALLOWED; } else if(m.type==MDM_CONTINUATION) m.authority=MDM_AUTH_MAGNITUDE_ADJUST; else if(m.type==MDM_DAMPENING) m.authority=MDM_AUTH_DAMPEN_ONLY; else m.authority=MDM_AUTH_NONE; if(m.type==MDM_CONTINUATION) { if(m.strength==MDM_WEAK) m.modifier=0.15*baseDir; else if(m.strength==MDM_STRONG) m.modifier=0.30*baseDir; else if(m.strength==MDM_VERY_STRONG) m.modifier=0.45*baseDir; else m.modifier=0.60*baseDir; } else if(m.type==MDM_DAMPENING) { if(m.strength==MDM_WEAK) m.modifier=-0.15*baseDir; else if(m.strength==MDM_STRONG) m.modifier=-0.30*baseDir; else if(m.strength==MDM_VERY_STRONG) m.modifier=-0.45*baseDir; else m.modifier=-0.60*baseDir; } else if(m.type==MDM_REVERSAL) { if(m.strength==MDM_WEAK) m.modifier=-0.15*baseDir; else if(m.strength==MDM_STRONG) m.modifier=-0.35*baseDir; else if(m.strength==MDM_VERY_STRONG) m.modifier=-0.70*baseDir; else m.modifier=-1.00*baseDir; } m.modifier=MDM_Clamp(m.modifier,-1.0,1.0); m.diagnostic="PATCH-19 NEWS DYNAMIC / MDM"; return m; } double MDM_ApplyToBase(const MSB_MDM_State &m,double baseScore) { if(!m.valid || !MDM_Finite(baseScore) || baseScore<-1.0 || baseScore>1.0) return baseScore; if(m.type==MDM_UNRESOLVED || m.type==MDM_DATA_UNAVAILABLE) return baseScore; if(m.type==MDM_CONTINUATION) { double mag=MathAbs(baseScore); mag=MDM_Clamp(mag+(1.0-mag)*MathAbs(m.modifier),0.0,1.0); return (baseScore>=0.0 ? mag : -mag); } if(m.type==MDM_DAMPENING) { double mag=MathAbs(baseScore); mag=MDM_Clamp(mag*(1.0-MathAbs(m.modifier)),0.0,1.0); return (baseScore>=0.0 ? mag : -mag); } // Weak/strong reversal may damp only. Very-strong/extreme may cross zero. if(m.authority!=MDM_AUTH_ZERO_CROSS_ALLOWED) { double mag=MathAbs(baseScore)*(1.0-MathAbs(m.modifier)); return (baseScore>=0.0 ? mag : -mag); } return MDM_Clamp(baseScore+m.modifier,-1.0,1.0); }