573 lines
20 KiB
MQL5
573 lines
20 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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) && (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(to<from) return;
|
|
|
|
// Calendar functions use trade-server time. Query both symbol currencies when
|
|
// available; duplicates are harmless because we select the strongest event.
|
|
string currencies[2];
|
|
currencies[0]=x.baseCurrency;
|
|
currencies[1]=x.profitCurrency;
|
|
if(currencies[0]=="" && currencies[1]=="") currencies[0]="USD";
|
|
|
|
double bestShock=0.0;
|
|
for(int pass=0;pass<2;pass++)
|
|
{
|
|
string currency=currencies[pass];
|
|
if(currency=="") continue;
|
|
if(pass==1 && currencies[1]==currencies[0]) continue;
|
|
MqlCalendarValue values[];
|
|
int n=CalendarValueHistory(values,from,to,NULL,currency);
|
|
if(n<=0) continue;
|
|
|
|
for(int i=0;i<n;i++)
|
|
{
|
|
if(values[i].time<from || values[i].time>to) 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.close<x.previousHigh);
|
|
bool sweepDn=(x.low<x.previousLow && x.close>x.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);
|
|
}
|