309 lines
12 KiB
MQL5
309 lines
12 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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; }
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|