| //+------------------------------------------------------------------+
|
| //| PrgRules.mqh |
|
| //| Copyright 2026, Allan Niñal |
|
| //| https://www.allanninal.dev/mql5 |
|
| //+------------------------------------------------------------------+
|
| //| Rule engine of Prop Rules Monitor. It only measures: day start, |
|
| //| daily and overall loss floors, trading days. It never trades. |
|
| //| |
|
| //| Daily floor = day-start reference - daily limit |
|
| //| reference: balance, equity or the higher of the two at the |
|
| //| reset instant (a time of day in a chosen time zone); |
|
| //| limit: % of the initial balance or of that reference. |
|
| //| Overall floor = initial - max% (static), or the peak (equity, |
|
| //| balance, or end-of-day balance) - max% of the initial, which |
|
| //| can stop rising once it reaches the initial balance. |
|
| //| Breach = equity at or below a floor (open trades count). |
|
| //| Times: deals and resets are compared in broker server time; the |
|
| //| reset zone is converted with built-in EU/US daylight-saving |
|
| //| rules. Nothing here uses the network. |
|
| //| MT4 edition: the same measurements. MT4 keeps orders, not deals: |
|
| //| the history scan reads balance operations and closed orders (P&L |
|
| //| at the close, commission included; a day is a trading day when a |
|
| //| trade was opened in it) and is rebuilt when the orders change; |
|
| //| the server clock is GMT + an offset learnt from a fresh tick. |
|
| //+------------------------------------------------------------------+
|
| // SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE)
|
| #ifndef PRG_RULES_MQH
|
| #define PRG_RULES_MQH
|
|
|
| //--- MT5 shows each enum choice by the comment on its own line, so every member needs one
|
| enum ENUM_PRG_REF
|
| {
|
| PRG_REF_BALANCE=0, // Balance at the reset
|
| PRG_REF_EQUITY=1, // Equity at the reset
|
| PRG_REF_HIGHER=2 // Higher of balance and equity at the reset
|
| };
|
| enum ENUM_PRG_OF
|
| {
|
| PRG_OF_INITIAL=0, // % of the initial balance (account size)
|
| PRG_OF_DAYSTART=1 // % of the day-start reference
|
| };
|
| enum ENUM_PRG_ZONE
|
| {
|
| PRG_ZONE_BROKER=0, // Broker server time
|
| PRG_ZONE_PRAGUE=1, // Central Europe (Prague, CET/CEST)
|
| PRG_ZONE_UTC=2, // UTC
|
| PRG_ZONE_NEWYORK=3, // New York (EST/EDT)
|
| PRG_ZONE_LONDON=4, // London (GMT/BST)
|
| PRG_ZONE_UTC3=5 // UTC+3 all year
|
| };
|
| enum ENUM_PRG_MAX
|
| {
|
| PRG_MAX_STATIC=0, // Static: initial balance - max %
|
| PRG_MAX_TRAIL_EQ=1, // Trailing from the highest equity
|
| PRG_MAX_TRAIL_BAL=2, // Trailing from the highest closed balance
|
| PRG_MAX_EOD=3, // Trailing from the highest end-of-day balance
|
| PRG_MAX_SMART=4 // Two-stage: max % until a gain, then a smaller fixed %
|
| };
|
| enum ENUM_PRG_CLOCK
|
| {
|
| PRG_CLOCK_AUTO=0, // Auto: detect from the live server time
|
| PRG_CLOCK_NY=1, // New York close: UTC+2/+3 with US daylight saving
|
| PRG_CLOCK_EET=2, // UTC+2/+3 with European daylight saving
|
| PRG_CLOCK_FIXED=3 // Fixed offset from UTC, no daylight saving
|
| };
|
|
|
| //--- one set of rules (the user's own)
|
| struct PrgRules
|
| {
|
| string name;
|
| double daily_pct; // 0 = no daily limit
|
| ENUM_PRG_REF daily_ref;
|
| ENUM_PRG_OF daily_of;
|
| ENUM_PRG_ZONE reset_zone;
|
| int reset_hour; // hour of the reset in that zone (0-23)
|
| double max_pct; // 0 = no overall limit
|
| ENUM_PRG_MAX max_mode;
|
| bool max_stop_at_initial; // a trailing floor stops rising at the initial balance
|
| double smart_gain_pct; // PRG_MAX_SMART: closed-balance gain that switches the floor ...
|
| double smart_after_pct; // ... to account size - this %
|
| double target_pct; // profit target, 0 = none
|
| int min_days; // minimum trading days, 0 = none
|
| };
|
|
|
| //--- what the engine measured, for the panel
|
| struct PrgState
|
| {
|
| double initial; // account size the % limits use
|
| bool initial_auto; // taken from the first deposit
|
| datetime day_start; // current reset instant, server time
|
| datetime next_reset;
|
| double ref_balance; // balance at the reset
|
| double ref_equity; // equity at the reset (= balance when not measured)
|
| bool ref_measured; // true when the program was running at the reset
|
| double daily_floor; // equity at or below this breaks the daily limit (-DBL_MAX = off)
|
| double daily_limit; // money
|
| double max_floor; // (-DBL_MAX = off)
|
| double peak_equity;
|
| double peak_balance;
|
| double peak_eod;
|
| double balance,equity;
|
| double today_closed; // closed P&L since the reset
|
| int trading_days; // days with at least one new trade
|
| double daily_used_pct; // 0..100+ of the daily limit
|
| double max_used_pct;
|
| };
|
|
|
| PrgRules g_rules;
|
| PrgState g_st;
|
|
|
| //--- clock of the broker server, resolved once
|
| ENUM_PRG_CLOCK g_prg_clock=PRG_CLOCK_NY;
|
| int g_prg_fixed=0; // seconds, PRG_CLOCK_FIXED
|
|
|
| //+------------------------------------------------------------------+
|
| //| Time zones |
|
| //+------------------------------------------------------------------+
|
| //--- Sunday on or after `day` of `month` in year `y`, at `hour` UTC
|
| datetime PrgSundayFrom(const int y,const int month,const int day,const int hour)
|
| {
|
| MqlDateTime t;
|
| ZeroMemory(t);
|
| t.year=y;
|
| t.mon=month;
|
| t.day=day;
|
| datetime d=StructToTime(t);
|
| TimeToStruct(d,t);
|
| return(d+(datetime)(((7-t.day_of_week)%7)*86400+hour*3600));
|
| }
|
|
|
| //--- US daylight saving: second Sunday of March 07:00 UTC to first Sunday of November 06:00 UTC
|
| bool PrgUsDst(const datetime utc)
|
| {
|
| MqlDateTime t;
|
| TimeToStruct(utc,t);
|
| return(utc>=PrgSundayFrom(t.year,3,8,7) && utc<PrgSundayFrom(t.year,11,1,6));
|
| }
|
|
|
| //--- EU summer time: last Sunday of March to last Sunday of October, 01:00 UTC
|
| bool PrgEuDst(const datetime utc)
|
| {
|
| MqlDateTime t;
|
| TimeToStruct(utc,t);
|
| return(utc>=PrgSundayFrom(t.year,3,25,1) && utc<PrgSundayFrom(t.year,10,25,1));
|
| }
|
|
|
| //--- offset of a named zone from UTC at UTC time `utc`, seconds
|
| int PrgZoneOffset(const ENUM_PRG_ZONE zone,const datetime utc)
|
| {
|
| switch(zone)
|
| {
|
| case PRG_ZONE_PRAGUE: return(PrgEuDst(utc) ? 2*3600 : 3600);
|
| case PRG_ZONE_LONDON: return(PrgEuDst(utc) ? 3600 : 0);
|
| case PRG_ZONE_NEWYORK: return(PrgUsDst(utc) ? -4*3600 : -5*3600);
|
| case PRG_ZONE_UTC3: return(3*3600);
|
| default: return(0);
|
| }
|
| }
|
|
|
| //--- the broker's offset from UTC at server time `srv`
|
| int PrgBrokerOffset(const datetime srv)
|
| {
|
| if(g_prg_clock==PRG_CLOCK_FIXED)
|
| return(g_prg_fixed);
|
| if(g_prg_clock==PRG_CLOCK_EET)
|
| return(PrgEuDst(srv-2*3600) ? 3*3600 : 2*3600);
|
| return(PrgUsDst(srv-2*3600) ? 3*3600 : 2*3600);
|
| }
|
|
|
| //--- decide the server clock: the input, or the live server time against UTC (the tester can't tell)
|
| void PrgResolveClock(const ENUM_PRG_CLOCK input_clock,const int fixed_hours)
|
| {
|
| g_prg_clock=input_clock;
|
| g_prg_fixed=fixed_hours*3600;
|
| if(input_clock!=PRG_CLOCK_AUTO)
|
| return;
|
| g_prg_clock=PRG_CLOCK_NY;
|
| if(IsTesting())
|
| return;
|
| datetime gmt=TimeGMT();
|
| long off=(long)TimeCurrent()-(long)gmt; // the last tick's server time (MQL4 has no TimeTradeServer)
|
| off=(long)MathRound(off/1800.0)*1800;
|
| if(MathAbs(off)>14*3600) // a frozen weekend server time: keep New York close
|
| return;
|
| int ny=PrgUsDst(gmt) ? 3*3600 : 2*3600;
|
| int eu=PrgEuDst(gmt) ? 3*3600 : 2*3600;
|
| if(off==ny)
|
| return;
|
| if(off==eu)
|
| {
|
| g_prg_clock=PRG_CLOCK_EET;
|
| return;
|
| }
|
| g_prg_clock=PRG_CLOCK_FIXED;
|
| g_prg_fixed=(int)off;
|
| }
|
|
|
| string PrgClockName(void)
|
| {
|
| switch(g_prg_clock)
|
| {
|
| case PRG_CLOCK_EET: return("UTC+2/+3 EU");
|
| case PRG_CLOCK_FIXED: return(StringFormat("UTC%+.1f",g_prg_fixed/3600.0));
|
| default: return("NY close");
|
| }
|
| }
|
|
|
| //--- floor `t` to the last instant whose time of day is `hour`:00 (all in one clock)
|
| datetime PrgFloorToHour(const datetime t,const int hour)
|
| {
|
| long k=(long)t-hour*3600;
|
| long m=k%86400;
|
| if(m<0)
|
| m+=86400;
|
| return((datetime)((long)t-m));
|
| }
|
|
|
| //--- the reset instant (server time) of the trading day that contains server time `srv`
|
| datetime PrgDayStart(const datetime srv)
|
| {
|
| if(g_rules.reset_zone==PRG_ZONE_BROKER)
|
| return(PrgFloorToHour(srv,g_rules.reset_hour));
|
| datetime utc=srv-PrgBrokerOffset(srv);
|
| datetime loc=utc+PrgZoneOffset(g_rules.reset_zone,utc);
|
| datetime rl=PrgFloorToHour(loc,g_rules.reset_hour); // reset, zone local time
|
| datetime ru=rl-PrgZoneOffset(g_rules.reset_zone,rl-PrgZoneOffset(g_rules.reset_zone,utc));
|
| return(ru+PrgBrokerOffset(ru+2*3600));
|
| }
|
|
|
| string PrgZoneName(const ENUM_PRG_ZONE z)
|
| {
|
| switch(z)
|
| {
|
| case PRG_ZONE_PRAGUE: return("Prague");
|
| case PRG_ZONE_UTC: return("UTC");
|
| case PRG_ZONE_NEWYORK: return("New York");
|
| case PRG_ZONE_LONDON: return("London");
|
| case PRG_ZONE_UTC3: return("UTC+3");
|
| default: return("server");
|
| }
|
| }
|
|
|
| //--- current server time: TimeCurrent() in the tester; live, MQL4 has no TimeTradeServer and TimeCurrent stops between
|
| //--- ticks (all weekend), so GMT + the server's offset, learnt from a fresh tick (a whole half hour, -12 h .. +14 h)
|
| datetime PrgNow(void)
|
| {
|
| static long off=LONG_MIN;
|
| if(IsTesting())
|
| return(TimeCurrent());
|
| long d=(long)TimeCurrent()-(long)TimeGMT();
|
| long r=(long)MathRound(d/1800.0)*1800;
|
| if(MathAbs(d-r)<=120 && r>=-12*3600 && r<=14*3600)
|
| off=r;
|
| return(off==LONG_MIN ? TimeCurrent() : (datetime)((long)TimeGMT()+off));
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Persistence: terminal global variables keyed by the login, so a |
|
| //| restart, re-attach or timeframe change keeps peaks and the day |
|
| //| start: PRG.<login>.MON.<field>. |
|
| //+------------------------------------------------------------------+
|
| string g_prg_ns=""; // "MON." (set before PrgInit)
|
|
|
| string PrgKey(const string field)
|
| {
|
| return("PRG."+IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN))+"."+g_prg_ns+field);
|
| }
|
|
|
| double PrgGet(const string field,const double def)
|
| {
|
| string k=PrgKey(field);
|
| return(GlobalVariableCheck(k) ? GlobalVariableGet(k) : def);
|
| }
|
|
|
| void PrgSet(const string field,const double v)
|
| {
|
| if(IsTesting() && IsOptimization())
|
| return;
|
| GlobalVariableSet(PrgKey(field),v);
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| History scan: initial deposit, closed P&L per trading day, peak |
|
| //| balance, peak end-of-day balance, trading days. Called at start |
|
| //| and after every new deal; prop accounts have short histories. |
|
| //+------------------------------------------------------------------+
|
| double g_day_pnl[]; // closed P&L per trading day ...
|
| datetime g_day_key[]; // ... keyed by its reset instant
|
| bool g_day_traded[]; // a new position was opened that day
|
| double g_initial_input=0; // 0 = first deposit
|
|
|
| int PrgDaySlot(const datetime key)
|
| {
|
| int n=ArraySize(g_day_key);
|
| if(n>0 && g_day_key[n-1]==key)
|
| return(n-1);
|
| for(int i=n-1; i>=0; i--)
|
| if(g_day_key[i]==key)
|
| return(i);
|
| ArrayResize(g_day_key,n+1,64);
|
| ArrayResize(g_day_pnl,n+1,64);
|
| ArrayResize(g_day_traded,n+1,64);
|
| g_day_key[n]=key;
|
| g_day_pnl[n]=0;
|
| g_day_traded[n]=false;
|
| return(n);
|
| }
|
|
|
| //--- running totals of the scan, so each call reads only the deals added since the last one
|
| double g_scan_run=0,g_scan_peak=0,g_scan_eod=0,g_scan_day_end=0,g_scan_first=0;
|
| datetime g_scan_day=0,g_scan_time=0;
|
| double g_scan_base=0; // MT4: added to the running sums when no deposit is in the history
|
|
|
| void PrgScanReset(void)
|
| {
|
| ArrayResize(g_day_key,0);
|
| ArrayResize(g_day_pnl,0);
|
| ArrayResize(g_day_traded,0);
|
| g_scan_run=g_scan_peak=g_scan_eod=g_scan_day_end=g_scan_first=0;
|
| g_scan_day=g_scan_time=0;
|
| g_scan_base=0;
|
| }
|
|
|
| //--- the account's history as events in time order (MT4 keeps orders): a balance operation (kind 0), a closed trade's
|
| //--- P&L at its close (kind 1, profit + swap + commission), and the opening of a trade (kind 2, value 0), history and
|
| //--- open orders; in one second, balance first, then closes, then opens, then reading order
|
| int PrgEvents(datetime &t[],int &kind[],double &v[])
|
| {
|
| int nh=OrdersHistoryTotal(),no=OrdersTotal(),n=0;
|
| ArrayResize(t,2*(nh+no)); ArrayResize(kind,2*(nh+no)); ArrayResize(v,2*(nh+no));
|
| for(int pass=0; pass<2; pass++)
|
| for(int i=0; i<(pass==0 ? nh : no); i++)
|
| {
|
| if(!OrderSelect(i,SELECT_BY_POS,pass==0 ? MODE_HISTORY : MODE_TRADES))
|
| continue;
|
| int ty=OrderType();
|
| if(pass==0 && ty==6) // balance operation (deposit, withdrawal); 7 = credit: not balance
|
| { t[n]=OrderCloseTime()>0 ? OrderCloseTime() : OrderOpenTime(); kind[n]=0; v[n]=OrderProfit(); n++; continue; }
|
| if(ty>OP_SELL)
|
| continue;
|
| if(StringFind(OrderComment(),"from #")!=0) // the rest of a partly closed order is not a new trade
|
| { t[n]=OrderOpenTime(); kind[n]=2; v[n]=0; n++; }
|
| if(pass==0)
|
| { t[n]=OrderCloseTime(); kind[n]=1; v[n]=OrderProfit()+OrderSwap()+OrderCommission(); n++; }
|
| }
|
| double key[][2];
|
| ArrayResize(key,n);
|
| for(int i=0; i<n; i++)
|
| { key[i][0]=((double)t[i]*4.0+kind[i])*1048576.0+i; key[i][1]=i; }
|
| if(n>1)
|
| ArraySort(key);
|
| datetime t2[];
|
| int k2[];
|
| double v2[];
|
| ArrayResize(t2,n); ArrayResize(k2,n); ArrayResize(v2,n);
|
| for(int i=0; i<n; i++)
|
| { int j=(int)key[i][1]; t2[i]=t[j]; k2[i]=kind[j]; v2[i]=v[j]; }
|
| ArrayCopy(t,t2); ArrayCopy(kind,k2); ArrayCopy(v,v2);
|
| ArrayResize(t,n); ArrayResize(kind,n); ArrayResize(v,n);
|
| return(n);
|
| }
|
|
|
| void PrgScanHistory(void)
|
| {
|
| datetime now=PrgNow();
|
| PrgScanReset(); // MT4: no deal list to continue from - read it all again
|
| datetime et[];
|
| int ek[];
|
| double ev[];
|
| int n=PrgEvents(et,ek,ev);
|
| for(int i=0; i<n; i++)
|
| {
|
| datetime t=et[i];
|
| double v=ev[i];
|
| g_scan_time=MathMax(g_scan_time,t);
|
| datetime key=PrgDayStart(t);
|
| if(ek[i]==2) // an opening: a trading day, no money moved
|
| {
|
| g_day_traded[PrgDaySlot(key)]=true;
|
| continue;
|
| }
|
| if(g_scan_day!=0 && key!=g_scan_day) // a day ended: its closing balance
|
| g_scan_eod=MathMax(g_scan_eod,g_scan_day_end);
|
| g_scan_day=key;
|
| g_scan_run+=v;
|
| g_scan_day_end=g_scan_run;
|
| if(ek[i]==0)
|
| {
|
| if(g_scan_first==0 && v>0)
|
| g_scan_first=v;
|
| g_scan_peak=MathMax(g_scan_peak,g_scan_run);
|
| g_scan_eod=MathMax(g_scan_eod,g_scan_run);
|
| continue;
|
| }
|
| int sl=PrgDaySlot(key);
|
| g_day_pnl[sl]+=v;
|
| g_scan_peak=MathMax(g_scan_peak,g_scan_run);
|
| }
|
| //--- no deposit in the history (the Account History tab starts later, or the tester): the running sums are relative;
|
| //--- anchor them to the balance now, so balance peaks and end-of-day balances are real balances
|
| if(g_scan_first==0 && n>0)
|
| {
|
| double base=AccountInfoDouble(ACCOUNT_BALANCE)-g_scan_run;
|
| g_scan_run+=base; g_scan_peak+=base; g_scan_eod+=base; g_scan_day_end+=base;
|
| g_scan_base=base;
|
| }
|
| double bal=AccountInfoDouble(ACCOUNT_BALANCE);
|
| double first_dep=g_scan_first>0 ? g_scan_first : bal-(g_scan_run-g_scan_base); // tester or no deposit in history
|
| g_st.initial_auto=(g_initial_input<=0);
|
| g_st.initial=g_initial_input>0 ? g_initial_input : (first_dep>0 ? first_dep : bal);
|
| g_st.peak_balance=MathMax(MathMax(g_scan_peak,g_st.initial),bal);
|
| //--- end-of-day peak: days that closed before today's reset, plus the stored live value
|
| datetime today=PrgDayStart(now);
|
| double peak_eod=g_scan_eod;
|
| if(g_scan_day!=0 && g_scan_day<today)
|
| peak_eod=MathMax(peak_eod,g_scan_day_end);
|
| g_st.peak_eod=MathMax(MathMax(peak_eod,g_st.initial),MathMax(g_st.peak_eod,PrgGet("peak_eod",0)));
|
| PrgTotals(today);
|
| }
|
|
|
| //--- trading-day totals from the per-day table
|
| void PrgTotals(const datetime today)
|
| {
|
| g_st.trading_days=0;
|
| g_st.today_closed=0;
|
| for(int i=0; i<ArraySize(g_day_key); i++)
|
| {
|
| if(g_day_traded[i])
|
| g_st.trading_days++;
|
| if(g_day_key[i]==today)
|
| g_st.today_closed=g_day_pnl[i];
|
| }
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Day start: measured when the program runs through the reset, |
|
| //| otherwise rebuilt as the balance minus every deal since then. |
|
| //| Equity at a reset the program missed cannot be known: it then |
|
| //| equals the rebuilt balance and the panel says "rebuilt". |
|
| //+------------------------------------------------------------------+
|
| //--- money moved in the history at or after a time (closed P&L and balance operations)
|
| double PrgMovedSince(const datetime start)
|
| {
|
| double since=0;
|
| datetime et[];
|
| int ek[];
|
| double ev[];
|
| int n=PrgEvents(et,ek,ev);
|
| for(int i=0; i<n; i++)
|
| if(et[i]>=start)
|
| since+=ev[i];
|
| return(since);
|
| }
|
|
|
| void PrgRebuildDayStart(const datetime start)
|
| {
|
| g_st.ref_balance=AccountInfoDouble(ACCOUNT_BALANCE)-PrgMovedSince(start);
|
| g_st.ref_equity=g_st.ref_balance;
|
| g_st.ref_measured=false;
|
| }
|
|
|
| //--- start (or restart) the engine
|
| void PrgInit(void)
|
| {
|
| ZeroMemory(g_st);
|
| g_st.day_start=0;
|
| PrgScanReset();
|
| PrgScanHistory();
|
| g_st.peak_equity=MathMax(PrgGet("peak_eq",0),MathMax(g_st.peak_balance,AccountInfoDouble(ACCOUNT_EQUITY)));
|
| datetime now=PrgNow();
|
| datetime start=PrgDayStart(now);
|
| //--- a day start stored by a program that was running at the reset is kept
|
| if((datetime)PrgGet("day",0)==start)
|
| {
|
| g_st.ref_balance=PrgGet("day_bal",0);
|
| g_st.ref_equity=PrgGet("day_eq",0);
|
| g_st.ref_measured=PrgGet("day_meas",0)>0;
|
| }
|
| else
|
| {
|
| PrgRebuildDayStart(start);
|
| PrgSet("day",(double)start);
|
| PrgSet("day_bal",g_st.ref_balance);
|
| PrgSet("day_eq",g_st.ref_equity);
|
| PrgSet("day_meas",0);
|
| }
|
| g_st.day_start=start;
|
| g_st.next_reset=PrgDayStart(start+26*3600);
|
| if(g_st.next_reset<=start)
|
| g_st.next_reset=start+86400;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Per tick / timer update. Returns true when a new day started. |
|
| //+------------------------------------------------------------------+
|
| bool PrgUpdate(void)
|
| {
|
| bool new_day=false;
|
| datetime now=PrgNow();
|
| g_st.balance=AccountInfoDouble(ACCOUNT_BALANCE);
|
| g_st.equity=AccountInfoDouble(ACCOUNT_EQUITY);
|
| if(now>=g_st.next_reset)
|
| {
|
| //--- running through the reset: today's reference is measured, yesterday's close is an end-of-day value;
|
| //--- the first tick of the day can come late (a gap) with stops already filled after the reset: those belong
|
| //--- to the new day, so they are taken out of both
|
| g_st.day_start=PrgDayStart(now);
|
| g_st.next_reset=PrgDayStart(g_st.day_start+26*3600);
|
| if(g_st.next_reset<=g_st.day_start)
|
| g_st.next_reset=g_st.day_start+86400;
|
| double since=PrgMovedSince(g_st.day_start);
|
| g_st.peak_eod=MathMax(g_st.peak_eod,g_st.balance-since);
|
| PrgSet("peak_eod",g_st.peak_eod);
|
| g_st.ref_balance=g_st.balance-since;
|
| g_st.ref_equity=g_st.equity-since;
|
| g_st.ref_measured=true;
|
| g_st.today_closed=0;
|
| PrgSet("day",(double)g_st.day_start);
|
| PrgSet("day_bal",g_st.ref_balance);
|
| PrgSet("day_eq",g_st.ref_equity);
|
| PrgSet("day_meas",1);
|
| PrgTotals(g_st.day_start);
|
| new_day=true;
|
| }
|
| if(g_st.equity>g_st.peak_equity)
|
| {
|
| g_st.peak_equity=g_st.equity;
|
| PrgSet("peak_eq",g_st.peak_equity);
|
| }
|
| g_st.peak_balance=MathMax(g_st.peak_balance,g_st.balance);
|
| //--- daily floor
|
| g_st.daily_floor=-DBL_MAX;
|
| g_st.daily_limit=0;
|
| if(g_rules.daily_pct>0)
|
| {
|
| double ref=g_st.ref_balance;
|
| if(g_rules.daily_ref==PRG_REF_EQUITY)
|
| ref=g_st.ref_equity;
|
| else
|
| if(g_rules.daily_ref==PRG_REF_HIGHER)
|
| ref=MathMax(g_st.ref_balance,g_st.ref_equity);
|
| g_st.daily_limit=g_rules.daily_pct/100.0*(g_rules.daily_of==PRG_OF_INITIAL ? g_st.initial : ref);
|
| g_st.daily_floor=ref-g_st.daily_limit;
|
| g_st.daily_used_pct=g_st.daily_limit>0 ? 100.0*(ref-g_st.equity)/g_st.daily_limit : 0;
|
| }
|
| //--- overall floor
|
| g_st.max_floor=-DBL_MAX;
|
| if(g_rules.max_pct>0)
|
| {
|
| double amount=g_rules.max_pct/100.0*g_st.initial;
|
| double peak=g_st.initial;
|
| switch(g_rules.max_mode)
|
| {
|
| case PRG_MAX_TRAIL_EQ: peak=g_st.peak_equity; break;
|
| case PRG_MAX_TRAIL_BAL: peak=g_st.peak_balance; break;
|
| case PRG_MAX_EOD: peak=g_st.peak_eod; break;
|
| default: break;
|
| }
|
| g_st.max_floor=peak-amount;
|
| if(g_rules.max_mode==PRG_MAX_SMART && g_rules.smart_gain_pct>0 &&
|
| g_st.peak_balance>=g_st.initial*(1.0+g_rules.smart_gain_pct/100.0))
|
| {
|
| amount=g_rules.smart_after_pct/100.0*g_st.initial;
|
| g_st.max_floor=g_st.initial-amount;
|
| }
|
| if(g_rules.max_mode!=PRG_MAX_STATIC && g_rules.max_mode!=PRG_MAX_SMART && g_rules.max_stop_at_initial)
|
| g_st.max_floor=MathMin(g_st.max_floor,g_st.initial);
|
| //--- share of the loss budget used: 0 with the full amount of room, 100 at the floor
|
| g_st.max_used_pct=amount>0 ? 100.0*(amount-(g_st.equity-g_st.max_floor))/amount : 0;
|
| }
|
| return(new_day);
|
| }
|
|
|
| //--- equity at or below the daily floor
|
| bool PrgDailyBreached(void) { return(g_st.daily_floor>-DBL_MAX && g_st.equity<=g_st.daily_floor); }
|
| bool PrgMaxBreached(void) { return(g_st.max_floor>-DBL_MAX && g_st.equity<=g_st.max_floor); }
|
|
|
| //--- money left before a floor (DBL_MAX when the rule is off)
|
| double PrgDailyRoom(void) { return(g_st.daily_floor>-DBL_MAX ? g_st.equity-g_st.daily_floor : DBL_MAX); }
|
| double PrgMaxRoom(void) { return(g_st.max_floor>-DBL_MAX ? g_st.equity-g_st.max_floor : DBL_MAX); }
|
|
|
| //--- the smaller of the two rooms
|
| double PrgRoom(void) { return(MathMin(PrgDailyRoom(),PrgMaxRoom())); }
|
|
|
| #endif
|
| //+------------------------------------------------------------------+
|