| //+------------------------------------------------------------------+
|
| //| SessionLevels.mq5 |
|
| //| Copyright 2026, Allan Niñal |
|
| //| https://www.allanninal.dev/mql5 |
|
| //+------------------------------------------------------------------+
|
| //| Trading sessions (Asia, London, New York) as translucent boxes |
|
| //| with their high and low, plus the previous day and previous week |
|
| //| high/low and today's open as lines. Session times are entered in |
|
| //| New York time and converted per bar from the broker's clock, so |
|
| //| daylight saving is right on any broker. A trading day ends at |
|
| //| the 17:00 New York close. |
|
| //| Alerts (closed bars only, each at most once per trading day): |
|
| //| close above the previous day high / below the previous day low, |
|
| //| and after the Asia session, close above its high / below its low.|
|
| //+------------------------------------------------------------------+
|
| // SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE)
|
| #property copyright "Copyright 2026, Allan Niñal <https://www.allanninal.dev/mql5>"
|
| #property link "https://www.allanninal.dev/mql5"
|
| #property version "1.2"
|
| #property description "Asia / London / New York session boxes, previous day and week high/low, today's open. Times in New York time, DST-safe."
|
| #property description "Alerts on closed bars: break of the previous day high/low and of the Asia range. Non-repainting."
|
| #property description "Optional fakeout marker: a break of the overnight range that closes back inside it (a marker and an alert, not a trade signal)."
|
| #property indicator_chart_window
|
| #property indicator_buffers 28
|
| #property indicator_plots 8
|
| #property indicator_label1 "Prev day high"
|
| #property indicator_type1 DRAW_NONE
|
| #property indicator_label2 "Prev day low"
|
| #property indicator_type2 DRAW_NONE
|
| #property indicator_label3 "Prev week high"
|
| #property indicator_type3 DRAW_NONE
|
| #property indicator_label4 "Prev week low"
|
| #property indicator_type4 DRAW_NONE
|
| #property indicator_label5 "Asia high (so far)"
|
| #property indicator_type5 DRAW_NONE
|
| #property indicator_label6 "Asia low (so far)"
|
| #property indicator_type6 DRAW_NONE
|
| #property indicator_label7 "Signal"
|
| #property indicator_type7 DRAW_NONE
|
| #property indicator_label8 "Fakeout"
|
| #property indicator_type8 DRAW_NONE
|
|
|
| enum ENUM_SL_CLOCK
|
| {
|
| SL_CLOCK_AUTO=0, // Auto: detect from the live server time
|
| SL_CLOCK_NY=1, // New York close: UTC+2/+3 with US daylight saving
|
| SL_CLOCK_EET=2, // UTC+2/+3 with European daylight saving
|
| SL_CLOCK_FIXED=3 // Fixed offset from UTC (set below), no daylight saving
|
| };
|
|
|
| input int InpDays = 5; // Days to draw
|
| input group "Asia session (New York time)"
|
| input bool InpShowAsia = true; // Draw Asia
|
| input string InpAsiaStart = "19:00"; // Asia start HH:MM
|
| input string InpAsiaEnd = "04:00"; // Asia end HH:MM
|
| input color InpAsiaColor = 16748574; // Asia colour
|
| input group "London session (New York time)"
|
| input bool InpShowLondon = true; // Draw London
|
| input string InpLondonStart = "03:00"; // London start HH:MM
|
| input string InpLondonEnd = "12:00"; // London end HH:MM
|
| input color InpLondonColor = 42495; // London colour
|
| input group "New York session (New York time)"
|
| input bool InpShowNY = true; // Draw New York
|
| input string InpNYStart = "08:00"; // New York start HH:MM
|
| input string InpNYEnd = "17:00"; // New York end HH:MM
|
| input color InpNYColor = 7451452; // New York colour
|
| input group "Session boxes"
|
| input bool InpSessionLabels = true; // Label each box
|
| input bool InpFillBoxes = true; // Fill the boxes (translucent)
|
| input group "Levels"
|
| input bool InpShowPD = true; // Previous day high/low
|
| input bool InpShowPW = true; // Previous week high/low
|
| input bool InpShowOpen = true; // Today's open
|
| input color InpPDColor = 55295; // Previous day colour
|
| input color InpPWColor = 14053594; // Previous week colour
|
| input color InpOpenColor = 12632256; // Today's open colour
|
| input bool InpLevelLabels = true; // Price labels on the levels
|
| input int InpLevelWidth = 1; // Line width
|
| input group "Broker clock"
|
| input ENUM_SL_CLOCK InpClock = SL_CLOCK_AUTO; // Broker server clock
|
| input int InpFixedUTC = 0; // Broker offset from UTC, hours (Fixed offset only)
|
| input group "Alert kinds"
|
| input bool InpAlertPDH = true; // Close above previous day high
|
| input bool InpAlertPDL = true; // Close below previous day low
|
| input bool InpAlertAsiaH = true; // Close above Asia high (after Asia)
|
| input bool InpAlertAsiaL = true; // Close below Asia low (after Asia)
|
|
|
| #include "SignalAlerts.mqh"
|
| #include "ObjSync.mqh"
|
| #include <ChartKit/CkDebug.mqh>
|
|
|
| //--- new in 1.2, declared after the alert inputs so the order of every earlier input is unchanged for iCustom callers
|
| input group "Fakeout marker (overnight range, New York time)"
|
| input bool InpFoOn = true; // Mark fakeouts of the overnight range
|
| input string InpFoRangeStart= "17:00"; // Range start HH:MM
|
| input string InpFoRangeEnd = "02:00"; // Range end HH:MM
|
| input string InpFoWinStart = "02:00"; // Fakeout window start HH:MM
|
| input string InpFoWinEnd = "11:00"; // Fakeout window end HH:MM
|
| input int InpFoBars = 2; // Bars to close back inside (1 = break bar only, 2 = break bar or the next)
|
| input bool InpFoTrend = false; // Daily-structure filter (lower highs and lows: bearish only; higher: bullish only)
|
| input bool InpFoMarkers = true; // Draw the marker (arrow and label)
|
| input color InpFoBearColor = clrTomato; // Bearish fakeout colour (above the high)
|
| input color InpFoBullColor = clrDodgerBlue; // Bullish fakeout colour (below the low)
|
| input bool InpAlertFakeout= true; // Alert on a fakeout
|
| input group "Support"
|
| input bool InpDebug = false; // Debug log for bug reports (Experts tab)
|
|
|
| //--- buffers shown to the outside
|
| double g_pdh[],g_pdl[],g_pwh[],g_pwl[],g_ash[],g_asl[],g_sig[],g_fo[];
|
| //--- internal state, one value per bar so a recalculation can restart from any bar
|
| double g_nyw[]; // New York wall time of the bar (seconds)
|
| double g_dk[]; // trading-day number (New York close clock)
|
| double g_wk[]; // trading-week number
|
| double g_dhi[],g_dlo[],g_dop[]; // the day so far
|
| double g_whi[],g_wlo[]; // the week so far
|
| double g_aown[]; // owner date of the running Asia instance
|
| double g_adk[]; // trading day the running Asia instance belongs to
|
| double g_ahi[],g_alo[];
|
| double g_flag[]; // signals already given today: 1 PDH, 2 PDL, 4 Asia high, 8 Asia low
|
| double g_frh[],g_frl[],g_frc[]; // fakeout range so far today: high, low, bar count
|
| double g_fsh[],g_fsl[]; // fakeout state per side: 0 no break yet, n>0 broke n bars ago and waiting, -1 finished
|
| double g_ftr[]; // daily structure of today: +1 higher highs and lows, -1 lower, 0 mixed
|
| double g_dord[]; // trading-day ordinal (0 = first day on the chart)
|
| double g_dayhi[],g_daylo[]; // per trading-day ordinal: final high and low (indexed by ordinal, not by bar)
|
|
|
| const string PRODUCT="Session Levels";
|
| const int NY_CLOSE_SHIFT=7*3600; // New York-close clock minus New York wall clock
|
|
|
| ENUM_SL_CLOCK g_clock=SL_CLOCK_NY;
|
| int g_fixed_off=0;
|
| bool g_native=true;
|
| long g_sh_hour=-1;
|
| int g_sh_val=0;
|
| int g_s_start[3],g_s_end[3];
|
| int g_fo_rs=-1,g_fo_re=-1,g_fo_ws=-1,g_fo_we=-1,g_fo_need=1;
|
| bool g_fo_ok=false;
|
| bool g_s_on[3];
|
| color g_s_clr[3];
|
| string g_s_name[3]={"Asia","London","New York"};
|
| string g_pfx="";
|
| int g_obj_fail=0,g_dbg_fail=0; // objects the chart refused in the last pass / last reported
|
| datetime g_draw_bar=0;
|
| uint g_draw_tick=0;
|
| double g_draw_hi=0,g_draw_lo=0;
|
| int g_secs=60;
|
| bool g_no_draw=false; // set in OnInit: tester without visual mode (no chart to draw on)
|
|
|
| //+------------------------------------------------------------------+
|
| //| Broker clock -> New York time (ported from Gold Trend Sentinel) |
|
| //+------------------------------------------------------------------+
|
| //--- Sunday on or after `day` of `month` in year `y`, at `hour` UTC
|
| datetime SundayFrom(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 UsDst(const datetime utc)
|
| {
|
| MqlDateTime t;
|
| TimeToStruct(utc,t);
|
| return(utc>=SundayFrom(t.year,3,8,7) && utc<SundayFrom(t.year,11,1,6));
|
| }
|
|
|
| //--- EU summer time (last Sunday of March to last Sunday of October, 01:00 UTC)
|
| bool EuDst(const datetime utc)
|
| {
|
| MqlDateTime t;
|
| TimeToStruct(utc,t);
|
| return(utc>=SundayFrom(t.year,3,25,1) && utc<SundayFrom(t.year,10,25,1));
|
| }
|
|
|
| int NyOffset(const datetime utc) { return(UsDst(utc) ? 3*3600 : 2*3600); }
|
|
|
| //--- the broker's offset from UTC at server time `srv`
|
| int BrokerOffset(const datetime srv)
|
| {
|
| if(g_clock==SL_CLOCK_FIXED)
|
| return(g_fixed_off);
|
| if(g_clock==SL_CLOCK_EET)
|
| return(EuDst(srv-2*3600) ? 3*3600 : 2*3600);
|
| return(NyOffset(srv-2*3600));
|
| }
|
|
|
| //--- seconds to add to server time to get New York-close time (cached per hour: offsets change on the hour)
|
| int Shift(const datetime srv)
|
| {
|
| if(g_native)
|
| return(0);
|
| long h=(long)srv/3600;
|
| if(h!=g_sh_hour)
|
| {
|
| int b=BrokerOffset(srv);
|
| g_sh_val=NyOffset(srv-b)-b;
|
| g_sh_hour=h;
|
| }
|
| return(g_sh_val);
|
| }
|
|
|
| //--- decide the clock once: the inputs, or the live server time against UTC (the tester can't tell)
|
| void ResolveClock(void)
|
| {
|
| g_clock=InpClock;
|
| g_fixed_off=InpFixedUTC*3600;
|
| if(InpClock==SL_CLOCK_AUTO)
|
| {
|
| g_clock=SL_CLOCK_NY;
|
| if(!MQLInfoInteger(MQL_TESTER))
|
| {
|
| datetime gmt=TimeGMT();
|
| int off=(int)MathRound((double)(TimeTradeServer()-gmt)/1800.0)*1800;
|
| if(off==NyOffset(gmt))
|
| g_clock=SL_CLOCK_NY;
|
| else
|
| if(off==(EuDst(gmt) ? 3*3600 : 2*3600))
|
| g_clock=SL_CLOCK_EET;
|
| else
|
| {
|
| g_clock=SL_CLOCK_FIXED;
|
| g_fixed_off=off;
|
| }
|
| }
|
| }
|
| g_native=(g_clock==SL_CLOCK_NY);
|
| g_sh_hour=-1;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Helpers |
|
| //+------------------------------------------------------------------+
|
| //--- "HH:MM" -> minutes since midnight, -1 when invalid
|
| int ParseHM(const string s)
|
| {
|
| string p[];
|
| if(StringSplit(s,':',p)!=2)
|
| return(-1);
|
| int h=(int)StringToInteger(p[0]),m=(int)StringToInteger(p[1]);
|
| if(h<0 || h>23 || m<0 || m>59)
|
| return(-1);
|
| return(h*60+m);
|
| }
|
|
|
| //--- is minute-of-day `m` inside [s,e) ? A session with s>e crosses midnight
|
| bool InSession(const int m,const int s,const int e)
|
| {
|
| if(s==e)
|
| return(false);
|
| if(s<e)
|
| return(m>=s && m<e);
|
| return(m>=s || m<e);
|
| }
|
|
|
| //--- owner date of a session bar: a session that crosses midnight belongs to the day it ends on
|
| long OwnerDate(const long nyw,const int m,const int s,const int e)
|
| {
|
| long d=nyw/86400;
|
| return((s>e && m>=s) ? d+1 : d);
|
| }
|
|
|
| //--- daily structure for trading-day ordinal `ord` from the days before it: the last two confirmed swing highs and
|
| //--- lows (3 days each side; a swing is confirmed 3 days later). +1 both rose, -1 both fell, 0 otherwise.
|
| int DailyStructure(const int ord)
|
| {
|
| double hs[2],ls[2];
|
| int nh=0,nl=0;
|
| for(int j=ord-4; j>=3 && (nh<2 || nl<2); j--)
|
| {
|
| double mh=g_dayhi[j],ml=g_daylo[j];
|
| for(int q=j-3; q<=j+3; q++)
|
| {
|
| mh=MathMax(mh,g_dayhi[q]);
|
| ml=MathMin(ml,g_daylo[q]);
|
| }
|
| if(nh<2 && g_dayhi[j]==mh)
|
| hs[nh++]=g_dayhi[j];
|
| if(nl<2 && g_daylo[j]==ml)
|
| ls[nl++]=g_daylo[j];
|
| }
|
| if(nh<2 || nl<2)
|
| return(0);
|
| //--- hs[0] is the latest swing high, hs[1] the one before
|
| if(hs[0]>hs[1] && ls[0]>ls[1])
|
| return(1);
|
| if(hs[0]<hs[1] && ls[0]<ls[1])
|
| return(-1);
|
| return(0);
|
| }
|
|
|
| int OnInit()
|
| {
|
| CkDebugStart(PRODUCT,"1.2",InpDebug,StringFormat("days %d, sessions Asia %d %s-%s, London %d %s-%s, NY %d %s-%s, PD %d, PW %d, open %d, clock %d (UTC %d), fakeout %d (range %s-%s, window %s-%s, %d bars, trend %d)",
|
| InpDays,InpShowAsia,InpAsiaStart,InpAsiaEnd,InpShowLondon,InpLondonStart,InpLondonEnd,InpShowNY,InpNYStart,InpNYEnd,
|
| InpShowPD,InpShowPW,InpShowOpen,(int)InpClock,InpFixedUTC,
|
| InpFoOn,InpFoRangeStart,InpFoRangeEnd,InpFoWinStart,InpFoWinEnd,InpFoBars,InpFoTrend));
|
| g_s_start[0]=ParseHM(InpAsiaStart); g_s_end[0]=ParseHM(InpAsiaEnd);
|
| g_s_start[1]=ParseHM(InpLondonStart); g_s_end[1]=ParseHM(InpLondonEnd);
|
| g_s_start[2]=ParseHM(InpNYStart); g_s_end[2]=ParseHM(InpNYEnd);
|
| for(int s=0; s<3; s++)
|
| if(g_s_start[s]<0 || g_s_end[s]<0)
|
| {
|
| Print("Session Levels: session times must be HH:MM (00:00 to 23:59)");
|
| CkDbg("init refused: session times");
|
| return(INIT_PARAMETERS_INCORRECT);
|
| }
|
| g_s_on[0]=InpShowAsia; g_s_clr[0]=InpAsiaColor;
|
| g_s_on[1]=InpShowLondon; g_s_clr[1]=InpLondonColor;
|
| g_s_on[2]=InpShowNY; g_s_clr[2]=InpNYColor;
|
| if(InpDays<1 || InpLevelWidth<1 || InpLevelWidth>5 || InpFixedUTC<-12 || InpFixedUTC>14)
|
| {
|
| Print("Session Levels: days to draw at least 1, line width 1-5, broker offset from UTC -12 to 14 hours");
|
| CkDbg("init refused: days, line width or UTC offset");
|
| return(INIT_PARAMETERS_INCORRECT);
|
| }
|
| SetIndexBuffer(0,g_pdh,INDICATOR_DATA);
|
| SetIndexBuffer(1,g_pdl,INDICATOR_DATA);
|
| SetIndexBuffer(2,g_pwh,INDICATOR_DATA);
|
| SetIndexBuffer(3,g_pwl,INDICATOR_DATA);
|
| SetIndexBuffer(4,g_ash,INDICATOR_DATA);
|
| SetIndexBuffer(5,g_asl,INDICATOR_DATA);
|
| SetIndexBuffer(6,g_sig,INDICATOR_DATA);
|
| SetIndexBuffer(7,g_fo,INDICATOR_DATA);
|
| SetIndexBuffer(8,g_nyw,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(9,g_dk,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(10,g_wk,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(11,g_dhi,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(12,g_dlo,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(13,g_dop,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(14,g_whi,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(15,g_wlo,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(16,g_aown,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(17,g_adk,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(18,g_ahi,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(19,g_alo,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(20,g_flag,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(21,g_frh,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(22,g_frl,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(23,g_frc,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(24,g_fsh,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(25,g_fsl,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(26,g_ftr,INDICATOR_CALCULATIONS);
|
| SetIndexBuffer(27,g_dord,INDICATOR_CALCULATIONS);
|
| for(int p=0; p<8; p++)
|
| PlotIndexSetDouble(p,PLOT_EMPTY_VALUE,EMPTY_VALUE);
|
| IndicatorSetString(INDICATOR_SHORTNAME,"Session Levels");
|
| IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
|
| g_fo_rs=ParseHM(InpFoRangeStart); g_fo_re=ParseHM(InpFoRangeEnd);
|
| g_fo_ws=ParseHM(InpFoWinStart); g_fo_we=ParseHM(InpFoWinEnd);
|
| if(g_fo_rs<0 || g_fo_re<0 || g_fo_ws<0 || g_fo_we<0 || InpFoBars<1 || InpFoBars>5)
|
| {
|
| Print("Session Levels: fakeout times must be HH:MM and the bars to close back inside 1 to 5");
|
| CkDbg("init refused: fakeout times or bars");
|
| return(INIT_PARAMETERS_INCORRECT);
|
| }
|
| g_secs=PeriodSeconds();
|
| //--- fakeout: M1 to H1 only; the range must hold at least 5/6 of its bars (30 of 36 on M15 for a 9-hour range)
|
| int rmin=((g_fo_re-g_fo_rs)%1440+1440)%1440;
|
| g_fo_ok=(InpFoOn && g_secs<=3600 && rmin>0 && g_fo_ws!=g_fo_we);
|
| g_fo_need=MathMax(1,(int)MathCeil((double)rmin*60.0/(double)g_secs*30.0/36.0));
|
| ResolveClock();
|
| CkDbg(StringFormat("clock resolved: mode %d, offset from UTC %d s, %s",(int)g_clock,g_fixed_off,g_native ? "server time is New York close" : "converted per bar"));
|
| if(MQLInfoInteger(MQL_TESTER) && InpClock==SL_CLOCK_AUTO)
|
| Print("Session Levels: the Strategy Tester can't detect the broker's clock; New York close is assumed. ",
|
| "Set 'Broker server clock' for a broker on another clock.");
|
| g_secs=PeriodSeconds();
|
| g_pfx="SLV"+IntegerToString((int)(GetMicrosecondCount()%1000000))+"_";
|
| g_draw_bar=0;
|
| g_obj_fail=0;
|
| g_dbg_fail=0;
|
| g_no_draw=(MQLInfoInteger(MQL_TESTER) && !MQLInfoInteger(MQL_VISUAL_MODE));
|
| return(INIT_SUCCEEDED);
|
| }
|
|
|
| void OnDeinit(const int reason)
|
| {
|
| if(g_pfx!="")
|
| {
|
| ObjectsDeleteAll(0,g_pfx);
|
| ChartRedraw();
|
| }
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Drawing |
|
| //+------------------------------------------------------------------+
|
| void PutObj(const string name,const ENUM_OBJECT type,const datetime t1,const double p1,const datetime t2,const double p2)
|
| {
|
| if(!OsPlace(name,type,2,t1,p1,t2,p2))
|
| {
|
| g_obj_fail++;
|
| return;
|
| }
|
| OsInt(name,OBJPROP_BACK,true);
|
| }
|
|
|
| void PutText(const string name,const datetime t,const double price,const string text,const color clr,const ENUM_ANCHOR_POINT anchor)
|
| {
|
| if(!OsPlace(name,OBJ_TEXT,1,t,price,0,0))
|
| {
|
| g_obj_fail++;
|
| return;
|
| }
|
| OsStr(name,OBJPROP_TEXT,text);
|
| OsInt(name,OBJPROP_COLOR,clr);
|
| OsInt(name,OBJPROP_ANCHOR,anchor);
|
| OsInt(name,OBJPROP_FONTSIZE,8);
|
| }
|
|
|
| void DrawBox(const int s,const long owner,const datetime t1,const datetime t2,const double hi,const double lo)
|
| {
|
| string nm=g_pfx+"B"+IntegerToString(s)+"_"+IntegerToString(owner);
|
| PutObj(nm,OBJ_RECTANGLE,t1,hi,t2,lo);
|
| OsInt(nm,OBJPROP_COLOR,InpFillBoxes ? ColorToARGB(g_s_clr[s],60) : g_s_clr[s]);
|
| OsInt(nm,OBJPROP_FILL,InpFillBoxes);
|
| OsInt(nm,OBJPROP_WIDTH,1);
|
| if(InpSessionLabels)
|
| PutText(g_pfx+"T"+IntegerToString(s)+"_"+IntegerToString(owner),t1,hi,g_s_name[s],g_s_clr[s],ANCHOR_LEFT_LOWER);
|
| }
|
|
|
| void DrawLevel(const string tag,const string label,const double price,const color clr,const int a,const int b,
|
| const bool current,const datetime &time[],const int last,const ENUM_LINE_STYLE style)
|
| {
|
| if(price==EMPTY_VALUE || price<=0)
|
| return;
|
| datetime t1=time[a],t2=(current ? time[last]+g_secs : time[b]+g_secs);
|
| string nm=g_pfx+"L"+tag+"_"+IntegerToString((long)time[a]);
|
| PutObj(nm,OBJ_TREND,t1,price,t2,price);
|
| OsInt(nm,OBJPROP_COLOR,clr);
|
| OsInt(nm,OBJPROP_WIDTH,InpLevelWidth);
|
| OsInt(nm,OBJPROP_STYLE,style);
|
| OsInt(nm,OBJPROP_RAY_RIGHT,current);
|
| if(current && InpLevelLabels)
|
| PutText(g_pfx+"P"+tag,time[last]+3*g_secs,price,label+" "+DoubleToString(price,_Digits),clr,ANCHOR_LEFT);
|
| }
|
|
|
| //--- redraw everything for the last InpDays days (objects are deleted first, so old days disappear)
|
| void Draw(const int rates_total,const datetime &time[],const double &high[],const double &low[])
|
| {
|
| int last=rates_total-1;
|
| bool intraday=(g_secs<=PeriodSeconds(PERIOD_H4));
|
| int days=(intraday ? MathMin(InpDays,60) : 1);
|
| OsBegin();
|
| g_obj_fail=0;
|
| //--- first bar of the oldest day to draw
|
| int first=last,cnt=1;
|
| for(int k=last; k>0; k--)
|
| {
|
| if(g_dk[k-1]!=g_dk[k])
|
| {
|
| if(cnt>=days)
|
| break;
|
| cnt++;
|
| }
|
| first=k-1;
|
| }
|
| //--- session boxes
|
| if(intraday)
|
| for(int s=0; s<3; s++)
|
| {
|
| if(!g_s_on[s])
|
| continue;
|
| bool open=false;
|
| long owner=0;
|
| int st=0,en=0;
|
| double hi=0,lo=0;
|
| for(int j=first; j<=last; j++)
|
| {
|
| int m=(int)(((long)g_nyw[j]%86400)/60);
|
| if(InSession(m,g_s_start[s],g_s_end[s]))
|
| {
|
| long ow=OwnerDate((long)g_nyw[j],m,g_s_start[s],g_s_end[s]);
|
| if(open && ow==owner)
|
| {
|
| en=j;
|
| hi=MathMax(hi,high[j]);
|
| lo=MathMin(lo,low[j]);
|
| }
|
| else
|
| {
|
| if(open)
|
| DrawBox(s,owner,time[st],time[en]+g_secs,hi,lo);
|
| open=true;
|
| owner=ow;
|
| st=j;
|
| en=j;
|
| hi=high[j];
|
| lo=low[j];
|
| }
|
| }
|
| else
|
| if(open)
|
| {
|
| DrawBox(s,owner,time[st],time[en]+g_secs,hi,lo);
|
| open=false;
|
| }
|
| }
|
| if(open)
|
| DrawBox(s,owner,time[st],time[en]+g_secs,hi,lo);
|
| }
|
| //--- fakeout markers: an arrow and the label in one text object at the confirming bar
|
| if(InpFoMarkers && g_fo_ok)
|
| for(int j=first; j<last; j++)
|
| {
|
| if(g_fo[j]==EMPTY_VALUE)
|
| continue;
|
| string tag=g_pfx+"F"+IntegerToString((long)time[j]);
|
| int v=(int)g_fo[j];
|
| if(v==-1 || v==2)
|
| PutText(tag+"A",time[j],high[j],"▼ Fakeout",InpFoBearColor,ANCHOR_LOWER);
|
| if(v==1 || v==2)
|
| PutText(tag+"B",time[j],low[j],"▲ Fakeout",InpFoBullColor,ANCHOR_UPPER);
|
| }
|
| //--- level lines, one segment per day; the current day is a ray to the right
|
| int a=first;
|
| while(a<=last)
|
| {
|
| int b=a;
|
| while(b<last && g_dk[b+1]==g_dk[a])
|
| b++;
|
| bool cur=(b==last);
|
| if(InpShowPD)
|
| {
|
| DrawLevel("PDH","PDH",g_pdh[a],InpPDColor,a,b,cur,time,last,STYLE_SOLID);
|
| DrawLevel("PDL","PDL",g_pdl[a],InpPDColor,a,b,cur,time,last,STYLE_SOLID);
|
| }
|
| if(InpShowPW)
|
| {
|
| DrawLevel("PWH","PWH",g_pwh[a],InpPWColor,a,b,cur,time,last,STYLE_DASH);
|
| DrawLevel("PWL","PWL",g_pwl[a],InpPWColor,a,b,cur,time,last,STYLE_DASH);
|
| }
|
| if(InpShowOpen && intraday)
|
| DrawLevel("OP","Open",g_dop[a],InpOpenColor,a,b,cur,time,last,STYLE_DOT);
|
| a=b+1;
|
| }
|
| OsEnd(g_pfx);
|
| if(g_obj_fail!=g_dbg_fail)
|
| {
|
| g_dbg_fail=g_obj_fail;
|
| CkDbg(StringFormat("the chart refused %d object(s), error %d",g_obj_fail,GetLastError()));
|
| }
|
| g_draw_bar=time[last];
|
| g_draw_tick=GetTickCount();
|
| g_draw_hi=high[last];
|
| g_draw_lo=low[last];
|
| ChartRedraw();
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| int OnCalculate(const int rates_total,const int prev_calculated,const datetime &time[],
|
| const double &open[],const double &high[],const double &low[],const double &close[],
|
| const long &tick_volume[],const long &volume[],const int &spread[])
|
| {
|
| if(rates_total<2)
|
| return(0);
|
| int start=(prev_calculated>0 ? prev_calculated-1 : 0);
|
| for(int i=start; i<rates_total; i++)
|
| {
|
| long nyc=(long)time[i]+Shift(time[i]);
|
| long nyw=nyc-NY_CLOSE_SHIFT;
|
| long dk=nyc/86400,wk=(dk+3)/7;
|
| int m=(int)((nyw%86400)/60);
|
| g_nyw[i]=(double)nyw;
|
| g_dk[i]=(double)dk;
|
| g_wk[i]=(double)wk;
|
| bool new_day=(i==0 || (long)g_dk[i-1]!=dk);
|
| bool new_week=(i==0 || (long)g_wk[i-1]!=wk);
|
| int fl=0;
|
| //--- day: previous day levels and today's range
|
| if(new_day)
|
| {
|
| g_pdh[i]=(i>0 ? g_dhi[i-1] : EMPTY_VALUE);
|
| g_pdl[i]=(i>0 ? g_dlo[i-1] : EMPTY_VALUE);
|
| g_dhi[i]=high[i];
|
| g_dlo[i]=low[i];
|
| g_dop[i]=open[i];
|
| }
|
| else
|
| {
|
| g_pdh[i]=g_pdh[i-1];
|
| g_pdl[i]=g_pdl[i-1];
|
| g_dhi[i]=MathMax(g_dhi[i-1],high[i]);
|
| g_dlo[i]=MathMin(g_dlo[i-1],low[i]);
|
| g_dop[i]=g_dop[i-1];
|
| fl=(int)g_flag[i-1];
|
| }
|
| //--- trading-day ordinal and the daily structure (built from the chart's own bars)
|
| if(new_day)
|
| {
|
| int ord=(i>0 ? (int)g_dord[i-1]+1 : 0);
|
| if(ArraySize(g_dayhi)<ord+1)
|
| {
|
| ArrayResize(g_dayhi,ord+1+512);
|
| ArrayResize(g_daylo,ord+1+512);
|
| }
|
| if(i>0)
|
| {
|
| g_dayhi[ord-1]=g_dhi[i-1];
|
| g_daylo[ord-1]=g_dlo[i-1];
|
| }
|
| g_dord[i]=ord;
|
| g_ftr[i]=(g_fo_ok && InpFoTrend ? DailyStructure(ord) : 0);
|
| }
|
| else
|
| {
|
| g_dord[i]=g_dord[i-1];
|
| g_ftr[i]=g_ftr[i-1];
|
| }
|
| //--- week
|
| if(new_week)
|
| {
|
| g_pwh[i]=(i>0 ? g_whi[i-1] : EMPTY_VALUE);
|
| g_pwl[i]=(i>0 ? g_wlo[i-1] : EMPTY_VALUE);
|
| g_whi[i]=high[i];
|
| g_wlo[i]=low[i];
|
| }
|
| else
|
| {
|
| g_pwh[i]=g_pwh[i-1];
|
| g_pwl[i]=g_pwl[i-1];
|
| g_whi[i]=MathMax(g_whi[i-1],high[i]);
|
| g_wlo[i]=MathMin(g_wlo[i-1],low[i]);
|
| }
|
| //--- Asia range of the current trading day
|
| if(i>0)
|
| {
|
| g_aown[i]=g_aown[i-1];
|
| g_adk[i]=g_adk[i-1];
|
| g_ahi[i]=g_ahi[i-1];
|
| g_alo[i]=g_alo[i-1];
|
| }
|
| else
|
| {
|
| g_aown[i]=-1;
|
| g_adk[i]=-1;
|
| g_ahi[i]=0;
|
| g_alo[i]=0;
|
| }
|
| bool in_asia=InSession(m,g_s_start[0],g_s_end[0]);
|
| if(in_asia)
|
| {
|
| long ow=OwnerDate(nyw,m,g_s_start[0],g_s_end[0]);
|
| if((long)g_aown[i]!=ow)
|
| {
|
| g_aown[i]=(double)ow;
|
| g_adk[i]=(double)dk;
|
| g_ahi[i]=high[i];
|
| g_alo[i]=low[i];
|
| }
|
| else
|
| {
|
| g_ahi[i]=MathMax(g_ahi[i],high[i]);
|
| g_alo[i]=MathMin(g_alo[i],low[i]);
|
| }
|
| }
|
| bool asia_ok=((long)g_adk[i]==dk && g_aown[i]!=-1);
|
| g_ash[i]=(asia_ok ? g_ahi[i] : EMPTY_VALUE);
|
| g_asl[i]=(asia_ok ? g_alo[i] : EMPTY_VALUE);
|
| //--- fakeout of the overnight range: a bar trades beyond one side inside the window and that bar (or one of the
|
| //--- next InpFoBars-1) closes back inside the range. Decided on the bar's close; once per side per day.
|
| int fo=0;
|
| double fo_hi=0,fo_lo=0;
|
| if(g_fo_ok)
|
| {
|
| if(new_day)
|
| {
|
| g_frh[i]=0;
|
| g_frl[i]=0;
|
| g_frc[i]=0;
|
| g_fsh[i]=0;
|
| g_fsl[i]=0;
|
| }
|
| else
|
| {
|
| g_frh[i]=g_frh[i-1];
|
| g_frl[i]=g_frl[i-1];
|
| g_frc[i]=g_frc[i-1];
|
| g_fsh[i]=g_fsh[i-1];
|
| g_fsl[i]=g_fsl[i-1];
|
| }
|
| if(InSession(m,g_fo_rs,g_fo_re))
|
| {
|
| g_frh[i]=(g_frc[i]==0 ? high[i] : MathMax(g_frh[i],high[i]));
|
| g_frl[i]=(g_frc[i]==0 ? low[i] : MathMin(g_frl[i],low[i]));
|
| g_frc[i]+=1;
|
| }
|
| else
|
| if(InSession(m,g_fo_ws,g_fo_we) && (int)g_frc[i]>=g_fo_need)
|
| {
|
| double rh=g_frh[i],rl=g_frl[i];
|
| bool inside=(close[i]<rh && close[i]>rl);
|
| //--- high side: bearish fakeout
|
| int st=(int)g_fsh[i];
|
| if(st>=0 && (!InpFoTrend || (int)g_ftr[i]==-1))
|
| {
|
| if(st>0 || high[i]>rh)
|
| {
|
| st++;
|
| if(inside)
|
| {
|
| fo|=1;
|
| fo_hi=rh;
|
| st=-1;
|
| }
|
| else
|
| if(st>=InpFoBars)
|
| st=-1;
|
| }
|
| g_fsh[i]=st;
|
| }
|
| //--- low side: bullish fakeout
|
| st=(int)g_fsl[i];
|
| if(st>=0 && (!InpFoTrend || (int)g_ftr[i]==1))
|
| {
|
| if(st>0 || low[i]<rl)
|
| {
|
| st++;
|
| if(inside)
|
| {
|
| fo|=2;
|
| fo_lo=rl;
|
| st=-1;
|
| }
|
| else
|
| if(st>=InpFoBars)
|
| st=-1;
|
| }
|
| g_fsl[i]=st;
|
| }
|
| }
|
| else
|
| {
|
| //--- outside the window a pending break can no longer be confirmed
|
| if(g_fsh[i]>0)
|
| g_fsh[i]=-1;
|
| if(g_fsl[i]>0)
|
| g_fsl[i]=-1;
|
| }
|
| }
|
| g_fo[i]=EMPTY_VALUE;
|
| //--- signals: closed bars only, each kind once per trading day
|
| g_sig[i]=EMPTY_VALUE;
|
| if(i<=rates_total-2)
|
| {
|
| int kinds=0;
|
| if(g_pdh[i]!=EMPTY_VALUE)
|
| {
|
| if((fl&1)==0 && close[i]>g_pdh[i])
|
| kinds|=1;
|
| if((fl&2)==0 && close[i]<g_pdl[i])
|
| kinds|=2;
|
| }
|
| if(asia_ok && !in_asia)
|
| {
|
| if((fl&4)==0 && close[i]>g_ahi[i])
|
| kinds|=4;
|
| if((fl&8)==0 && close[i]<g_alo[i])
|
| kinds|=8;
|
| }
|
| fl|=kinds;
|
| if(fo!=0)
|
| g_fo[i]=(fo==3 ? 2 : (fo==1 ? -1 : 1));
|
| if((kinds&1)!=0)
|
| g_sig[i]=1;
|
| else
|
| if((kinds&2)!=0)
|
| g_sig[i]=-1;
|
| else
|
| if((kinds&4)!=0)
|
| g_sig[i]=2;
|
| else
|
| if((kinds&8)!=0)
|
| g_sig[i]=-2;
|
| if((kinds!=0 || fo!=0) && i==rates_total-2)
|
| {
|
| string txt="";
|
| if((kinds&1)!=0 && InpAlertPDH)
|
| txt+="closed above previous day high "+DoubleToString(g_pdh[i],_Digits)+"; ";
|
| if((kinds&2)!=0 && InpAlertPDL)
|
| txt+="closed below previous day low "+DoubleToString(g_pdl[i],_Digits)+"; ";
|
| if((kinds&4)!=0 && InpAlertAsiaH)
|
| txt+="closed above Asia high "+DoubleToString(g_ahi[i],_Digits)+"; ";
|
| if((kinds&8)!=0 && InpAlertAsiaL)
|
| txt+="closed below Asia low "+DoubleToString(g_alo[i],_Digits)+"; ";
|
| if((fo&1)!=0 && InpAlertFakeout)
|
| txt+="fakeout above the range high "+DoubleToString(fo_hi,_Digits)+", closed back inside (bearish); ";
|
| if((fo&2)!=0 && InpAlertFakeout)
|
| txt+="fakeout below the range low "+DoubleToString(fo_lo,_Digits)+", closed back inside (bullish); ";
|
| if(txt!="")
|
| {
|
| CkDbg("alert for the closed bar "+TimeToString(time[i])+": "+StringSubstr(txt,0,StringLen(txt)-2));
|
| SignalAlert(PRODUCT,StringSubstr(txt,0,StringLen(txt)-2),time[i],prev_calculated>0);
|
| }
|
| }
|
| }
|
| g_flag[i]=fl;
|
| }
|
| if(g_no_draw)
|
| return(rates_total);
|
| //--- objects: on a new bar, or (throttled) when the live bar widens the live session
|
| int last=rates_total-1;
|
| bool newbar=(time[last]!=g_draw_bar);
|
| bool widen=(g_secs<=PeriodSeconds(PERIOD_H4) && (high[last]>g_draw_hi || low[last]<g_draw_lo) && GetTickCount()-g_draw_tick>=1000);
|
| if(newbar || widen)
|
| Draw(rates_total,time,high,low);
|
| return(rates_total);
|
| }
|
| //+------------------------------------------------------------------+
|