mql5/Experts/mt5-fx-gold-ea/CascadeGreepMasterEA.mq5

309 lines
12 KiB
MQL5
Raw Permalink Normal View History

2025-08-14 19:25:09 -04:00
//+------------------------------------------------------------------+
//| CascadeGreepMaster |
//| Copyright 2025 Cascade (AI) |
//| Purpose: Publish per-symbol risk/SL/cooldown multipliers |
//| based on News, Volatility, and Equity regimes. |
//+------------------------------------------------------------------+
#property copyright "Cascade AI"
#property link "https://windsurf.ai"
#property version "1.0"
#property strict
#include <Trade/Trade.mqh>
//==================== Inputs ====================
input group "Symbols"
input string InpSymbols = "EURUSD;AUDUSD;USDCAD;USDJPY;XAUUSD"; // semicolon-separated
input string InpGVNamespace = "CGreep"; // base for global variables
input int InpUpdateSeconds = 60; // update frequency
input group "News Regime"
input bool InpUseNews = true;
input int InpNewsWindowBeforeMin = 60; // minutes before event
input int InpNewsWindowAfterMin = 15; // minutes after event
input double InpNewsWeightLow = 0.85; // min risk factor near low-impact
input double InpNewsWeightMedium = 0.60; // min risk factor near medium-impact
input double InpNewsWeightHigh = 0.30; // min risk factor near high-impact
input group "Volatility Regime (ATR)"
input bool InpUseVolatility = true;
input ENUM_TIMEFRAMES InpVolTF = PERIOD_H1;
input int InpATRPeriod = 14;
input int InpATRAvgLookback = 100; // bars for average ATR baseline
input double InpVolMinRiskFactor = 0.50; // floor when volatility is extreme
input bool InpVolAllowUpsize = false; // allow >1.0 risk factor in very low vol
input group "Equity Regime"
input bool InpUseEquity = true;
input double InpDailyLossSoftPercent = 1.0; // when daily DD >= this, reduce risk
input double InpEquityMinRiskFactor = 0.50; // floor under soft daily DD
input bool InpEquityAllowUpsize = false; // allow >1.0 on strong daily profit
input double InpDailyWinBoostCap = 1.10; // max upsize if allowed
input group "Composition & Publishing"
input double InpMinRiskFactor = 0.20; // global floor
input double InpMaxRiskFactor = 1.00; // global cap
input double InpSLMultGainOnCut = 1.00; // sl_mult = 1 + gain*(1-risk_factor)
input double InpCooldownGainOnCut = 2.00; // cooldown_mult = 1 + gain*(1-risk_factor)
input group "XAU Overrides"
input double InpXAU_NewsWeightLow = 0.80; // XAU: min risk factor near low-impact
input double InpXAU_NewsWeightMedium = 0.50; // XAU: medium-impact
input double InpXAU_NewsWeightHigh = 0.25; // XAU: high-impact
input int InpXAU_NewsWindowBeforeMin = 90; // XAU: minutes before
input int InpXAU_NewsWindowAfterMin = 30; // XAU: minutes after
input double InpXAU_VolMinRiskFactor = 0.35; // XAU: stronger cut under high vol
input bool InpXAU_VolAllowUpsize = false; // XAU: no upsize on low vol
input double InpXAU_MinRiskFactor = 0.30; // XAU: global floor
input double InpXAU_SLMultGainOnCut = 1.50; // XAU: widen SL more when cut
input double InpXAU_CooldownGainOnCut = 3.00; // XAU: longer cooldown when cut
//==================== Globals ====================
string g_symbols[];
int g_day_key=0;
double g_day_equity_start=0.0;
//==================== Utils ====================
void TrimString(string &s)
{
int start=0; while(start<StringLen(s) && StringGetCharacter(s,start)<=32) start++;
int end=StringLen(s)-1; while(end>=0 && StringGetCharacter(s,end)<=32) end--;
if(end<start) { s=""; return; }
s=StringSubstr(s,start,end-start+1);
}
void Split(const string src, const string delim, string &out[])
{
ArrayResize(out,0);
int pos=0, last=0; int dlen=StringLen(delim);
while((pos=StringFind(src,delim,last))!=-1)
{
string token=StringSubstr(src,last,pos-last);
TrimString(token);
if(StringLen(token)>0) {
int n=ArraySize(out); ArrayResize(out,n+1); out[n]=token;
}
last=pos+dlen;
}
string tail=StringSubstr(src,last);
TrimString(tail);
if(StringLen(tail)>0) { int n=ArraySize(out); ArrayResize(out,n+1); out[n]=tail; }
}
int TodayKey()
{
MqlDateTime dt; TimeToStruct(TimeTradeServer(), dt);
return dt.year*10000+dt.mon*100+dt.day;
}
void DailyResetIfNeeded()
{
int key=TodayKey();
if(key!=g_day_key)
{
g_day_key=key;
g_day_equity_start=AccountInfoDouble(ACCOUNT_EQUITY);
Print("[MASTER] New day baseline equity=", g_day_equity_start);
}
}
string Ccy1(const string sym)
{
if(StringLen(sym)>=3) return StringSubstr(sym,0,3);
return "";
}
string Ccy2(const string sym)
{
if(StringLen(sym)>=6) return StringSubstr(sym,3,3);
return "";
}
// clamp helper
double Clamp(const double v, const double lo, const double hi) { return MathMax(lo, MathMin(hi, v)); }
bool SymbolIsXAU(const string sym)
{
return (StringFind(sym, "XAU", 0) >= 0 || StringFind(sym, "GOLD", 0) >= 0);
}
//==================== Calendar & News ====================
double ImpactWeightWeighted(const int importance, const double wLow, const double wMed, const double wHigh)
{
// 0=low,1=medium,2=high in most terminals; fallback to medium otherwise
if(importance<=0) return wLow;
if(importance==1) return wMed;
return wHigh;
}
double NewsFactorForCurrencyWeighted(const string ccy,
const int beforeMin,
const int afterMin,
const double wLow,
const double wMed,
const double wHigh)
{
if(!InpUseNews || StringLen(ccy)==0) return 1.0;
datetime now = TimeTradeServer();
datetime from = now - afterMin*60;
datetime to = now + beforeMin*60;
MqlCalendarValue vals[]; ArrayFree(vals);
ResetLastError();
int n = CalendarValueHistory(vals, from, to, NULL, ccy);
if(n<=0)
{
int err=GetLastError();
if(err!=0) PrintFormat("[MASTER] CalendarValueHistory returned %d (err=%d) for %s", n, err, ccy);
return 1.0; // neutral if not available
}
double factor=1.0;
for(int i=0;i<n;i++)
{
MqlCalendarValue v = vals[i];
ulong eid = v.event_id;
MqlCalendarEvent evt; if(!CalendarEventById((long)eid, evt)) continue;
double w = ImpactWeightWeighted((int)evt.importance, wLow, wMed, wHigh);
// proximity scaling
int minutes = (int)MathRound((double)(v.time - now) / 60.0);
double prox=0.0; // 0 outside windows, 1 at event time
if(minutes>=0 && minutes<=beforeMin)
prox = (double)(beforeMin - minutes)/beforeMin;
else if(minutes<0 && -minutes<=afterMin)
prox = (double)(afterMin - (-minutes))/afterMin;
else
continue;
double event_factor = 1.0 - (1.0 - w) * prox; // ramps from 1 -> w as prox->1
factor = MathMin(factor, event_factor);
}
return factor;
}
//==================== Volatility (ATR) ====================
bool CopyOne(const int handle, const int buffer, const int shift, double &out)
{
double tmp[]; int r=CopyBuffer(handle,buffer,shift,1,tmp);
if(r!=1) return false; out=tmp[0]; return true;
}
double VolatilityFactorForSymbolParams(const string sym, const double volMinRiskFactor, const bool allowUpsize)
{
if(!InpUseVolatility) return 1.0;
int hATR = iATR(sym, InpVolTF, InpATRPeriod);
if(hATR==INVALID_HANDLE) return 1.0;
double currATR; if(!CopyOne(hATR,0,1,currATR)) { IndicatorRelease(hATR); return 1.0; }
int n = InpATRAvgLookback;
double arr[]; int got = CopyBuffer(hATR,0,1,n,arr);
IndicatorRelease(hATR);
if(got<=10) return 1.0;
double sum=0; int cnt=0; for(int i=0;i<got;i++){ if(arr[i]>0){ sum+=arr[i]; cnt++; }}
if(cnt==0) return 1.0;
double avg = sum/cnt;
double ratio = (avg>0 ? currATR/avg : 1.0);
// higher ATR -> reduce risk; lower ATR -> optionally allow slight upsize
double f = 1.0/MathSqrt(MathMax(0.0001, ratio));
if(!allowUpsize) f = MathMin(1.0, f);
f = Clamp(f, volMinRiskFactor, InpMaxRiskFactor);
return f;
}
//==================== Equity Regime ====================
double EquityFactor()
{
if(!InpUseEquity) return 1.0;
if(g_day_equity_start<=0) return 1.0;
double eq = AccountInfoDouble(ACCOUNT_EQUITY);
double ddpct = (g_day_equity_start - eq)/g_day_equity_start * 100.0;
if(ddpct >= InpDailyLossSoftPercent)
return Clamp(InpEquityMinRiskFactor, InpMinRiskFactor, InpMaxRiskFactor);
// optional upsize on strong day
double profitpct = (eq - g_day_equity_start)/g_day_equity_start * 100.0;
if(InpEquityAllowUpsize && profitpct>=1.0)
return Clamp(InpDailyWinBoostCap, InpMinRiskFactor, InpMaxRiskFactor);
return 1.0;
}
//==================== Compose & Publish ====================
void PublishForSymbol(const string sym)
{
string c1=Ccy1(sym), c2=Ccy2(sym);
bool isXAU = SymbolIsXAU(sym);
double rf=1.0;
if(InpUseNews)
{
if(isXAU)
{
// Use USD-side news primarily for XAUUSD
double nf_usd = NewsFactorForCurrencyWeighted("USD",
InpXAU_NewsWindowBeforeMin,
InpXAU_NewsWindowAfterMin,
InpXAU_NewsWeightLow,
InpXAU_NewsWeightMedium,
InpXAU_NewsWeightHigh);
rf = MathMin(rf, nf_usd);
}
else
{
double nf1 = NewsFactorForCurrencyWeighted(c1, InpNewsWindowBeforeMin, InpNewsWindowAfterMin,
InpNewsWeightLow, InpNewsWeightMedium, InpNewsWeightHigh);
double nf2 = NewsFactorForCurrencyWeighted(c2, InpNewsWindowBeforeMin, InpNewsWindowAfterMin,
InpNewsWeightLow, InpNewsWeightMedium, InpNewsWeightHigh);
rf = MathMin(rf, nf1);
rf = MathMin(rf, nf2);
}
}
double vf = VolatilityFactorForSymbolParams(sym, isXAU ? InpXAU_VolMinRiskFactor : InpVolMinRiskFactor,
isXAU ? InpXAU_VolAllowUpsize : InpVolAllowUpsize);
rf = MathMin(rf, vf);
double ef = EquityFactor(); rf = MathMin(rf, ef);
double floor_rf = isXAU ? InpXAU_MinRiskFactor : InpMinRiskFactor;
rf = Clamp(rf, floor_rf, InpMaxRiskFactor);
double sl_gain = isXAU ? InpXAU_SLMultGainOnCut : InpSLMultGainOnCut;
double cd_gain = isXAU ? InpXAU_CooldownGainOnCut : InpCooldownGainOnCut;
double sl_mult = 1.0 + sl_gain * (1.0 - rf);
double cd_mult = 1.0 + cd_gain * (1.0 - rf);
string base = InpGVNamespace + "/";
GlobalVariableSet(base+"risk_mult/"+sym, rf);
GlobalVariableSet(base+"sl_mult/"+sym, sl_mult);
GlobalVariableSet(base+"cooldown_mult/"+sym, cd_mult);
GlobalVariableSet(base+"updated_ts", (double)TimeTradeServer());
// optional: debug
PrintFormat("[MASTER] %s rf=%.2f sl_mult=%.2f cd_mult=%.2f (news/vol/equity)%s", sym, rf, sl_mult, cd_mult, isXAU?" [XAU]":"");
}
void UpdateAll()
{
DailyResetIfNeeded();
for(int i=0;i<ArraySize(g_symbols);++i)
PublishForSymbol(g_symbols[i]);
}
//==================== Events ====================
int OnInit()
{
g_day_key = TodayKey();
g_day_equity_start = AccountInfoDouble(ACCOUNT_EQUITY);
Split(InpSymbols, ";", g_symbols);
EventSetTimer(InpUpdateSeconds);
Print("[MASTER] Initialized for symbols: ", InpSymbols);
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
EventKillTimer();
}
void OnTimer()
{
UpdateAll();
}
void OnTick()
{
// Also update opportunistically on tick
static datetime last=0; datetime now=TimeCurrent();
if(now-last >= InpUpdateSeconds) { UpdateAll(); last=now; }
}
//+------------------------------------------------------------------+