2026-10-21 18:04:52 +08:00
//+------------------------------------------------------------------+
//| SessionLevels.mq4 |
//| 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.|
//| MT4 edition: the same calculation, inputs and 7 iCustom buffers |
//| as the MT5 one. MT4 has no transparency, so a filled box is the |
//| session colour blended into the chart background. |
//+------------------------------------------------------------------+
// 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 "
2026-10-31 14:28:54 +08:00
# property version " 1.1 "
2026-10-21 18:04:52 +08:00
# 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 indicator_chart_window
# property indicator_buffers 7
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 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 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 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 bool InpSessionLabels = true ; // Label each box
input bool InpFillBoxes = true ; // Fill the boxes (translucent)
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 ENUM_SL_CLOCK InpClock = SL_CLOCK_AUTO ; // Broker server clock
input int InpFixedUTC = 0 ; // Broker offset from UTC, hours (Fixed offset only)
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"
2026-10-31 14:28:54 +08:00
# include "ObjSync.mqh"
# include <ChartKit/CkDebug.mqh>
input bool InpDebug = false ; // Debug log for bug reports (Experts tab)
2026-10-21 18:04:52 +08:00
//--- buffers shown to the outside
double g_pdh [ ] , g_pdl [ ] , g_pwh [ ] , g_pwl [ ] , g_ash [ ] , g_asl [ ] , g_sig [ ] ;
//--- 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
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 ] ;
bool g_s_on [ 3 ] ;
color g_s_clr [ 3 ] ;
string g_s_name [ 3 ] = { " Asia " , " London " , " New York " } ;
string g_pfx = " " ;
2026-10-31 14:28:54 +08:00
int g_obj_fail = 0 , g_dbg_fail = 0 ; // objects the chart refused in the last pass / last reported
2026-10-21 18:04:52 +08:00
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)
//+------------------------------------------------------------------+
2026-10-31 14:28:54 +08:00
//| Broker clock -> New York time (ported from Gold Trend Sentinel) |
2026-10-21 18:04:52 +08:00
//+------------------------------------------------------------------+
//--- 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 ( ! IsTesting ( ) )
{
datetime gmt = TimeGMT ( ) ;
//--- MT4 has no TimeTradeServer(): the last tick's server time; stale while the market is closed, so an
//--- offset that is no real UTC offset keeps the New York close assumption
int off = ( int ) MathRound ( ( double ) ( TimeCurrent ( ) - gmt ) / 1800.0 ) * 1800 ;
if ( off < -12 * 3600 | | off > 14 * 3600 | | 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 ) ;
}
int OnInit ( )
{
2026-10-31 14:28:54 +08:00
CkDebugStart ( PRODUCT , " 1.1 " , 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) " ,
InpDays , InpShowAsia , InpAsiaStart , InpAsiaEnd , InpShowLondon , InpLondonStart , InpLondonEnd , InpShowNY , InpNYStart , InpNYEnd ,
InpShowPD , InpShowPW , InpShowOpen , ( int ) InpClock , InpFixedUTC ) ) ;
2026-10-21 18:04:52 +08:00
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) " ) ;
2026-10-31 14:28:54 +08:00
CkDbg ( " init refused: session times " ) ;
2026-10-21 18:04:52 +08:00
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 ;
2026-10-31 14:28:54 +08:00
if ( InpDays < 1 | | InpLevelWidth < 1 | | InpLevelWidth > 5 | | InpFixedUTC < -12 | | InpFixedUTC > 14 )
2026-10-21 18:04:52 +08:00
{
2026-10-31 14:28:54 +08:00
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 " ) ;
2026-10-21 18:04:52 +08:00
return ( INIT_PARAMETERS_INCORRECT ) ;
}
IndicatorBuffers ( 20 ) ;
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_nyw , INDICATOR_CALCULATIONS ) ;
SetIndexBuffer ( 8 , g_dk , INDICATOR_CALCULATIONS ) ;
SetIndexBuffer ( 9 , g_wk , INDICATOR_CALCULATIONS ) ;
SetIndexBuffer ( 10 , g_dhi , INDICATOR_CALCULATIONS ) ;
SetIndexBuffer ( 11 , g_dlo , INDICATOR_CALCULATIONS ) ;
SetIndexBuffer ( 12 , g_dop , INDICATOR_CALCULATIONS ) ;
SetIndexBuffer ( 13 , g_whi , INDICATOR_CALCULATIONS ) ;
SetIndexBuffer ( 14 , g_wlo , INDICATOR_CALCULATIONS ) ;
SetIndexBuffer ( 15 , g_aown , INDICATOR_CALCULATIONS ) ;
SetIndexBuffer ( 16 , g_adk , INDICATOR_CALCULATIONS ) ;
SetIndexBuffer ( 17 , g_ahi , INDICATOR_CALCULATIONS ) ;
SetIndexBuffer ( 18 , g_alo , INDICATOR_CALCULATIONS ) ;
SetIndexBuffer ( 19 , g_flag , INDICATOR_CALCULATIONS ) ;
string names [ 7 ] = { " Prev day high " , " Prev day low " , " Prev week high " , " Prev week low " , " Asia high (so far) " ,
" Asia low (so far) " , " Signal " } ;
for ( int p = 0 ; p < 20 ; p + + )
{
SetIndexStyle ( p , DRAW_NONE ) ;
SetIndexEmptyValue ( p , EMPTY_VALUE ) ;
SetIndexLabel ( p , p < 7 ? names [ p ] : NULL ) ;
}
IndicatorShortName ( " Session Levels " ) ;
IndicatorDigits ( _Digits ) ;
ResolveClock ( ) ;
2026-10-31 14:28:54 +08:00
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 " ) ) ;
2026-10-21 18:04:52 +08:00
if ( IsTesting ( ) & & 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 ;
2026-10-31 14:28:54 +08:00
g_obj_fail = 0 ;
g_dbg_fail = 0 ;
2026-10-21 18:04:52 +08:00
g_no_draw = ( IsTesting ( ) & & ! IsVisualMode ( ) ) ;
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 )
{
2026-10-31 14:28:54 +08:00
if ( ! OsPlace ( name , type , 2 , t1 , p1 , t2 , p2 ) )
2026-10-21 18:04:52 +08:00
{
2026-10-31 14:28:54 +08:00
g_obj_fail + + ;
return ;
2026-10-21 18:04:52 +08:00
}
2026-10-31 14:28:54 +08:00
OsInt ( name , OBJPROP_BACK , true ) ;
2026-10-21 18:04:52 +08:00
}
void PutText ( const string name , const datetime t , const double price , const string text , const color clr , const ENUM_ANCHOR_POINT anchor )
{
2026-10-31 14:28:54 +08:00
if ( ! OsPlace ( name , OBJ_TEXT , 1 , t , price , 0 , 0 ) )
2026-10-21 18:04:52 +08:00
{
2026-10-31 14:28:54 +08:00
g_obj_fail + + ;
return ;
2026-10-21 18:04:52 +08:00
}
2026-10-31 14:28:54 +08:00
OsStr ( name , OBJPROP_TEXT , text ) ;
OsInt ( name , OBJPROP_COLOR , clr ) ;
OsInt ( name , OBJPROP_ANCHOR , anchor ) ;
OsInt ( name , OBJPROP_FONTSIZE , 8 ) ;
2026-10-21 18:04:52 +08:00
}
//--- MT4 cannot draw a translucent rectangle: blend the colour into the chart background by `alpha` (0-255)
color Blend ( const color c , const int alpha )
{
color bg = ( color ) ChartGetInteger ( 0 , CHART_COLOR_BACKGROUND ) ;
int r = ( ( c & 0xFF ) * alpha + ( bg & 0xFF ) * ( 255 - alpha ) ) / 255 ;
int g = ( ( ( c > > 8 ) & 0xFF ) * alpha + ( ( bg > > 8 ) & 0xFF ) * ( 255 - alpha ) ) / 255 ;
int b = ( ( ( c > > 16 ) & 0xFF ) * alpha + ( ( bg > > 16 ) & 0xFF ) * ( 255 - alpha ) ) / 255 ;
return ( ( color ) ( r | ( g < < 8 ) | ( b < < 16 ) ) ) ;
}
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 ) ;
2026-10-31 14:28:54 +08:00
OsInt ( nm , OBJPROP_COLOR , InpFillBoxes ? Blend ( g_s_clr [ s ] , 60 ) : g_s_clr [ s ] ) ;
OsInt ( nm , OBJPROP_FILL , InpFillBoxes ) ;
OsInt ( nm , OBJPROP_BACK , InpFillBoxes ) ; // MT4 fills a rectangle drawn in the background
OsInt ( nm , OBJPROP_WIDTH , 1 ) ;
2026-10-21 18:04:52 +08:00
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 ) ;
2026-10-31 14:28:54 +08:00
OsInt ( nm , OBJPROP_COLOR , clr ) ;
OsInt ( nm , OBJPROP_WIDTH , InpLevelWidth ) ;
OsInt ( nm , OBJPROP_STYLE , style ) ;
OsInt ( nm , OBJPROP_RAY_RIGHT , current ) ;
2026-10-21 18:04:52 +08:00
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 ) ;
2026-10-31 14:28:54 +08:00
OsBegin ( ) ;
g_obj_fail = 0 ;
2026-10-21 18:04:52 +08:00
//--- 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 ) ;
}
//--- 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 ;
}
2026-10-31 14:28:54 +08:00
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 ( ) ) ) ;
}
2026-10-21 18:04:52 +08:00
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 ) ;
//--- count bars from the oldest, as MT5 does (prices and every buffer)
ArraySetAsSeries ( time , false ) ;
ArraySetAsSeries ( open , false ) ;
ArraySetAsSeries ( high , false ) ;
ArraySetAsSeries ( low , false ) ;
ArraySetAsSeries ( close , false ) ;
ArraySetAsSeries ( g_pdh , false ) ; ArraySetAsSeries ( g_pdl , false ) ; ArraySetAsSeries ( g_pwh , false ) ;
ArraySetAsSeries ( g_pwl , false ) ; ArraySetAsSeries ( g_ash , false ) ; ArraySetAsSeries ( g_asl , false ) ;
ArraySetAsSeries ( g_sig , false ) ; ArraySetAsSeries ( g_nyw , false ) ; ArraySetAsSeries ( g_dk , false ) ;
ArraySetAsSeries ( g_wk , false ) ; ArraySetAsSeries ( g_dhi , false ) ; ArraySetAsSeries ( g_dlo , false ) ;
ArraySetAsSeries ( g_dop , false ) ; ArraySetAsSeries ( g_whi , false ) ; ArraySetAsSeries ( g_wlo , false ) ;
ArraySetAsSeries ( g_aown , false ) ; ArraySetAsSeries ( g_adk , false ) ; ArraySetAsSeries ( g_ahi , false ) ;
ArraySetAsSeries ( g_alo , false ) ; ArraySetAsSeries ( g_flag , false ) ;
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 ] ;
}
//--- 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 ) ;
//--- 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 ( ( 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 & & 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 ( txt ! = " " )
2026-10-31 14:28:54 +08:00
{
CkDbg ( " alert for the closed bar " + TimeToString ( time [ i ] ) + " : " + StringSubstr ( txt , 0 , StringLen ( txt ) -2 ) ) ;
2026-10-21 18:04:52 +08:00
SignalAlert ( PRODUCT , StringSubstr ( txt , 0 , StringLen ( txt ) -2 ) , time [ i ] , prev_calculated > 0 ) ;
2026-10-31 14:28:54 +08:00
}
2026-10-21 18:04:52 +08:00
}
}
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 ) ;
}
//+------------------------------------------------------------------+