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);
| |||
}
|