ForexSessionsClock/ForexSessionsClock.mq5
1768 lines
72 KiB
MQL5
meisamd
2026-11-02 21:51:42 +00:00
//+------------------------------------------------------------------+
//| ForexSessionsClock.mq5 |
//| ساعت و نقشه سشن‌های فارکس | Forex Sessions Clock & Map |
//+------------------------------------------------------------------+
#property copyright "AiTrader"
#property link ""
#property version "5.00"
#property description "ساعت ۲۴ ساعته سشن‌های فارکس (سیدنی، توکیو، لندن، نیویورک) - فارسی و انگلیسی"
#property description "Forex sessions 24h clock & map (Sydney, Tokyo, London, New York) - Persian & English"
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots 0
#include <Canvas\Canvas.mqh>
//+------------------------------------------------------------------+
//| Enumerations |
//+------------------------------------------------------------------+
enum ENUM_UI_LANG
{
UI_FA = 0, // فارسی | Persian
UI_EN = 1 // English | انگلیسی
};
enum ENUM_DIGIT_STYLE
{
DIG_AUTO = 0, // خودکار (مطابق زبان) | Auto (follow language)
DIG_LATIN = 1, // ارقام لاتین 0123456789 | Latin digits
DIG_PERSIAN = 2 // ارقام فارسی ۰۱۲۳۴۵۶۷۸۹ | Persian digits
};
enum ENUM_THEME_MODE
{
THEME_LIGHT = 0, // روشن | Light
THEME_DARK = 1 // تیره | Dark
};
enum ENUM_REF_TZ
{
TZ_TEHRAN = 0, // ایران – تهران (UTC+3:30) | Iran – Tehran
TZ_UTC = 1, // زمان جهانی UTC | UTC
TZ_BROKER = 2, // زمان سرور بروکر | Broker server time
TZ_LONDON = 3, // انگلیس – لندن | UK – London
TZ_NEWYORK = 4, // آمریکا – نیویورک | USA – New York
TZ_TOKYO = 5, // ژاپن – توکیو | Japan – Tokyo
TZ_SYDNEY = 6, // استرالیا – سیدنی | Australia – Sydney
TZ_DUBAI = 7, // امارات – دبی | UAE – Dubai
TZ_ISTANBUL = 8, // ترکیه – استانبول | Turkey – Istanbul
TZ_BERLIN = 9, // آلمان – برلین | Germany – Berlin
TZ_CUSTOM = 10 // دلخواه (انحراف دستی) | Custom offset
};
#define TZ_COUNT 11
enum ENUM_PANEL_CORNER
{
PC_TL = 0, // بالا-چپ | Top-Left
PC_TR = 1, // بالا-راست | Top-Right
PC_BL = 2, // پایین-چپ | Bottom-Left
PC_BR = 3 // پایین-راست | Bottom-Right
};
// ساعت‌ها به صورت لیست کشویی (ComboBox) با گام ۱۵ دقیقه | Time list (ComboBox), 15-minute steps
enum ENUM_HM
{
HM_0000 = 0, // 00:00
HM_0015 = 15, // 00:15
HM_0030 = 30, // 00:30
HM_0045 = 45, // 00:45
HM_0100 = 60, // 01:00
HM_0115 = 75, // 01:15
HM_0130 = 90, // 01:30
HM_0145 = 105, // 01:45
HM_0200 = 120, // 02:00
HM_0215 = 135, // 02:15
HM_0230 = 150, // 02:30
HM_0245 = 165, // 02:45
HM_0300 = 180, // 03:00
HM_0315 = 195, // 03:15
HM_0330 = 210, // 03:30
HM_0345 = 225, // 03:45
HM_0400 = 240, // 04:00
HM_0415 = 255, // 04:15
HM_0430 = 270, // 04:30
HM_0445 = 285, // 04:45
HM_0500 = 300, // 05:00
HM_0515 = 315, // 05:15
HM_0530 = 330, // 05:30
HM_0545 = 345, // 05:45
HM_0600 = 360, // 06:00
HM_0615 = 375, // 06:15
HM_0630 = 390, // 06:30
HM_0645 = 405, // 06:45
HM_0700 = 420, // 07:00
HM_0715 = 435, // 07:15
HM_0730 = 450, // 07:30
HM_0745 = 465, // 07:45
HM_0800 = 480, // 08:00
HM_0815 = 495, // 08:15
HM_0830 = 510, // 08:30
HM_0845 = 525, // 08:45
HM_0900 = 540, // 09:00
HM_0915 = 555, // 09:15
HM_0930 = 570, // 09:30
HM_0945 = 585, // 09:45
HM_1000 = 600, // 10:00
HM_1015 = 615, // 10:15
HM_1030 = 630, // 10:30
HM_1045 = 645, // 10:45
HM_1100 = 660, // 11:00
HM_1115 = 675, // 11:15
HM_1130 = 690, // 11:30
HM_1145 = 705, // 11:45
HM_1200 = 720, // 12:00
HM_1215 = 735, // 12:15
HM_1230 = 750, // 12:30
HM_1245 = 765, // 12:45
HM_1300 = 780, // 13:00
HM_1315 = 795, // 13:15
HM_1330 = 810, // 13:30
HM_1345 = 825, // 13:45
HM_1400 = 840, // 14:00
HM_1415 = 855, // 14:15
HM_1430 = 870, // 14:30
HM_1445 = 885, // 14:45
HM_1500 = 900, // 15:00
HM_1515 = 915, // 15:15
HM_1530 = 930, // 15:30
HM_1545 = 945, // 15:45
HM_1600 = 960, // 16:00
HM_1615 = 975, // 16:15
HM_1630 = 990, // 16:30
HM_1645 = 1005, // 16:45
HM_1700 = 1020, // 17:00
HM_1715 = 1035, // 17:15
HM_1730 = 1050, // 17:30
HM_1745 = 1065, // 17:45
HM_1800 = 1080, // 18:00
HM_1815 = 1095, // 18:15
HM_1830 = 1110, // 18:30
HM_1845 = 1125, // 18:45
HM_1900 = 1140, // 19:00
HM_1915 = 1155, // 19:15
HM_1930 = 1170, // 19:30
HM_1945 = 1185, // 19:45
HM_2000 = 1200, // 20:00
HM_2015 = 1215, // 20:15
HM_2030 = 1230, // 20:30
HM_2045 = 1245, // 20:45
HM_2100 = 1260, // 21:00
HM_2115 = 1275, // 21:15
HM_2130 = 1290, // 21:30
HM_2145 = 1305, // 21:45
HM_2200 = 1320, // 22:00
HM_2215 = 1335, // 22:15
HM_2230 = 1350, // 22:30
HM_2245 = 1365, // 22:45
HM_2300 = 1380, // 23:00
HM_2315 = 1395, // 23:15
HM_2330 = 1410, // 23:30
HM_2345 = 1425 // 23:45
};
//+------------------------------------------------------------------+
//| Inputs |
//+------------------------------------------------------------------+
input group "■■■ ① تنظیمات عمومی ‖ GENERAL SETTINGS ■■■"
input ENUM_UI_LANG InpLang = UI_FA; // ◆ زبان پیش‌فرض پنل | Default panel language
input bool InpRemember = true; // ◆ به‌خاطر سپردن زبان و منطقه‌ی زمانی انتخابی (با کلیک روی پنل) | Remember language & time zone chosen on the panel
input ENUM_DIGIT_STYLE InpDigits = DIG_AUTO; // ◆ نوع ارقام | Digit style
input ENUM_THEME_MODE InpTheme = THEME_LIGHT; // ◆ پوسته‌ی پیش‌فرض (روی خود پنل هم قابل تغییر است) | Default theme (can also be switched on the panel)
input bool InpFaFix = false; // ◆ اصلاح اتصال حروف فارسی (فقط اگر متن فارسی جدا یا برعکس دیده شد روشن کنید) | Persian letter-joining fix (turn ON only if Persian text looks broken)
input string InpFontFa = "Tahoma"; // ◆ فونت فارسی | Persian font
input string InpFontEn = "Segoe UI"; // ◆ فونت انگلیسی و ساعت دیجیتال | English / digital-clock font
input string InpBrand = "Forex Sessions Clock"; // ◆ عنوان بالای پنل (خالی = مخفی) | Header title (empty = hidden)
input group "■■■ ② منطقه‌ی زمانی ‖ TIME ZONE ■■■"
input ENUM_REF_TZ InpRefTz = TZ_TEHRAN; // ◆ منطقه‌ی زمانی مرجع (پیش‌فرض: تهران) | Reference time zone (default: Tehran)
input int InpCustomMin = 210; // ◆ انحراف دلخواه از UTC به دقیقه (فقط حالت «دلخواه») | Custom UTC offset in minutes (custom mode only)
input bool InpWeekend = true; // ◆ بازار در آخر هفته «بسته» نمایش داده شود | Show market as closed on weekends
input group "■■■ ③ جای‌گذاری و اندازه‌ی پنل ‖ PANEL POSITION & SIZE ■■■"
input ENUM_PANEL_CORNER InpCorner = PC_TL; // ◆ گوشه‌ی نمایش پنل | Panel corner
input int InpX = 10; // ◆ فاصله از لبه‌ی افقی (پیکسل) | Horizontal margin (px)
input int InpY = 20; // ◆ فاصله از لبه‌ی عمودی (پیکسل) | Vertical margin (px)
input int InpScale = 100; // ◆ حداکثر اندازه‌ی پنل به درصد (۵۰ تا ۲۰۰) | Maximum panel scale in % (50-200)
input bool InpAutoFit = true; // ◆ تنظیم خودکار اندازه‌ی پنل با اندازه‌ی چارت | Auto-fit panel to chart size
input group "■■■ ④ بخش‌های قابل نمایش ‖ VISIBLE SECTIONS ■■■"
input bool InpShowHeader = true; // ◆ سربرگ (منطقه‌ی زمانی، دکمه‌های زبان و وضعیت) | Header (time zone, language buttons & status)
input bool InpShowClock = true; // ◆ ساعت ۲۴ ساعته‌ی حلقه‌ای | 24-hour ring clock
input bool InpShowNumbers = true; // ◆ اعداد ساعت دور ساعت | Hour numbers around clock
input bool InpShowSecHand = true; // ◆ عقربه‌ی ثانیه‌شمار | Seconds hand
input bool InpShowDigital = true; // ◆ ساعت دیجیتال، تاریخ شمسی و میلادی | Digital clock & dates
input bool InpShowInfo = true; // ◆ کارت‌های «سشن بعدی» و «هم‌پوشانی بعدی» | "Next session" & "Next overlap" cards
input bool InpShowOverlaps = true; // ◆ جدول هم‌پوشانی همه‌ی سشن‌ها | Table of all session overlaps
input bool InpShowAlertBar = true; // ◆ نوار وضعیت هشدار | Alert status bar
input bool InpShowCards = true; // ◆ کارت جزئیات هر سشن | Session detail cards
input bool InpShowTimeline = true; // ◆ نقشه‌ی ۲۴ ساعته‌ی بازار (همراه با هم‌پوشانی‌ها) | 24-hour market map (with overlaps)
input group "■■■ ⑤ سشن سیدنی ‖ SYDNEY SESSION ■■■"
input bool InpSydOn = true; // ◆ فعال‌سازی سشن سیدنی | Enable Sydney session
input ENUM_HM InpSydStart = HM_0700; // ◆ ساعت شروع (از لیست انتخاب کنید) - به وقت محلی سیدنی | Start time (pick from list) - Sydney local time
input ENUM_HM InpSydEnd = HM_1600; // ◆ ساعت پایان (از لیست انتخاب کنید) - به وقت محلی سیدنی | End time (pick from list) - Sydney local time
input bool InpSydDst = true; // ◆ اعمال خودکار ساعت تابستانی | Automatic daylight-saving time
input color InpSydClr = C'168,85,247'; // ◆ رنگ سیدنی | Sydney colour
input group "■■■ ⑥ سشن توکیو ‖ TOKYO SESSION ■■■"
input bool InpTokOn = true; // ◆ فعال‌سازی سشن توکیو | Enable Tokyo session
input ENUM_HM InpTokStart = HM_0900; // ◆ ساعت شروع (از لیست انتخاب کنید) - به وقت محلی توکیو | Start time (pick from list) - Tokyo local time
input ENUM_HM InpTokEnd = HM_1800; // ◆ ساعت پایان (از لیست انتخاب کنید) - به وقت محلی توکیو | End time (pick from list) - Tokyo local time
input color InpTokClr = C'245,165,36'; // ◆ رنگ توکیو | Tokyo colour
input group "■■■ ⑦ سشن لندن ‖ LONDON SESSION ■■■"
input bool InpLonOn = true; // ◆ فعال‌سازی سشن لندن | Enable London session
input ENUM_HM InpLonStart = HM_0800; // ◆ ساعت شروع (از لیست انتخاب کنید) - به وقت محلی لندن | Start time (pick from list) - London local time
input ENUM_HM InpLonEnd = HM_1700; // ◆ ساعت پایان (از لیست انتخاب کنید) - به وقت محلی لندن | End time (pick from list) - London local time
input bool InpLonDst = true; // ◆ اعمال خودکار ساعت تابستانی | Automatic daylight-saving time
input color InpLonClr = C'52,201,138'; // ◆ رنگ لندن | London colour
input group "■■■ ⑧ سشن نیویورک ‖ NEW YORK SESSION ■■■"
input bool InpNyOn = true; // ◆ فعال‌سازی سشن نیویورک | Enable New York session
input ENUM_HM InpNyStart = HM_0800; // ◆ ساعت شروع (از لیست انتخاب کنید) - به وقت محلی نیویورک | Start time (pick from list) - New York local time
input ENUM_HM InpNyEnd = HM_1700; // ◆ ساعت پایان (از لیست انتخاب کنید) - به وقت محلی نیویورک | End time (pick from list) - New York local time
input bool InpNyDst = true; // ◆ اعمال خودکار ساعت تابستانی | Automatic daylight-saving time
input color InpNyClr = C'37,99,235'; // ◆ رنگ نیویورک | New York colour
input group "■■■ ⑨ هشدارها ‖ ALERTS ■■■"
input bool InpAlerts = true; // ◆ فعال‌سازی هشدار سشن‌ها | Enable session alerts
input bool InpAlertOpen = true; // ◆ هشدار هنگام باز شدن سشن | Alert when a session opens
input bool InpAlertClose = false; // ◆ هشدار هنگام بسته شدن سشن | Alert when a session closes
input int InpAlertPre = 15; // ◆ هشدار چند دقیقه قبل از شروع (۰ = خاموش) | Alert minutes before open (0 = off)
input bool InpPopup = true; // ◆ نمایش پنجره‌ی هشدار | Popup alert window
input bool InpSound = true; // ◆ پخش صدا | Play sound
input string InpSoundFile = "alert.wav"; // ◆ نام فایل صدا | Sound file name
input bool InpPush = false; // ◆ ارسال نوتیفیکیشن به موبایل | Push notification to mobile
input bool InpMail = false; // ◆ ارسال ایمیل | Send e-mail
//+------------------------------------------------------------------+
//| Globals |
//+------------------------------------------------------------------+
#define GV_LANG "FSC_LANG"
#define GV_TZ "FSC_TZ"
#define GV_THEME "FSC_THEME"
CCanvas g_cv; // main panel (redrawn once per minute)
CCanvas g_ck; // optional live overlay: seconds hand
string g_fn=""; int g_fp=-1; uint g_ff=0; // font cache
string g_tn=""; // native label for digital time
bool g_built=false,g_refit=false;
bool g_hasCk=false,g_hasDg=false;
int g_ckx=0,g_cky=0,g_ckw=0,g_dgx=0,g_dgy=0,g_dgw=0,g_dgh=0;
long g_lastMin=-1;
int g_cw=0,g_chh=0,g_key=0;
#define SH_HDR 0
#define SH_CLK 1
#define SH_DIG 2
#define SH_INF 3
#define SH_ALR 4
#define SH_CRD 5
#define SH_TL 6
#define SH_OVL 7
bool g_sh[8];
struct SessSt
{
bool active;
long st;
long en;
long left;
double prog;
};
SessSt g_st[4];
string g_name;
int g_W=0,g_H=0,g_px=0,g_py=0;
double g_S=1.0;
bool g_fa=true,g_pd=true,g_fix=true,g_wk=false;
int g_tz=0;
string g_fFa,g_fEn;
datetime g_last=0;
long g_u=0,g_refOff=0,g_refNow=0,g_refMid=0;
CCanvas g_pp; // time-zone combo-box popup
bool g_popOpen=false;
int g_ppx=0,g_ppy=0,g_ppw=0,g_pph=0;
int g_PWb=1340,g_colX0=12;
int g_np=0,g_pa[6],g_pb[6]; // overlapping pairs that can really overlap
uint g_lct=0; // last handled click time
bool g_dark=false;
int g_hit[4][4]; // 0=tz box, 1=Persian button, 2=English button (x,y,w,h)
color kBg,kCard,kBorder,kText,kSub,kTrack,kNavy,kGreen,kMid,kHand,kFace,kAccent;
struct SessCfg
{
bool on;
int zone;
int sMin;
int eMin;
bool dst;
color clr;
string fa;
string en;
};
SessCfg g_s[4];
// Arabic/Persian shaping table: code, isolated, final, initial, medial
ushort g_ar[]=
{
0x0621,0xFE80,0xFE80,0,0, 0x0622,0xFE81,0xFE82,0,0, 0x0623,0xFE83,0xFE84,0,0,
0x0624,0xFE85,0xFE86,0,0, 0x0625,0xFE87,0xFE88,0,0, 0x0626,0xFE89,0xFE8A,0xFE8B,0xFE8C,
0x0627,0xFE8D,0xFE8E,0,0, 0x0628,0xFE8F,0xFE90,0xFE91,0xFE92, 0x0629,0xFE93,0xFE94,0,0,
0x062A,0xFE95,0xFE96,0xFE97,0xFE98, 0x062B,0xFE99,0xFE9A,0xFE9B,0xFE9C, 0x062C,0xFE9D,0xFE9E,0xFE9F,0xFEA0,
0x062D,0xFEA1,0xFEA2,0xFEA3,0xFEA4, 0x062E,0xFEA5,0xFEA6,0xFEA7,0xFEA8, 0x062F,0xFEA9,0xFEAA,0,0,
0x0630,0xFEAB,0xFEAC,0,0, 0x0631,0xFEAD,0xFEAE,0,0, 0x0632,0xFEAF,0xFEB0,0,0,
0x0633,0xFEB1,0xFEB2,0xFEB3,0xFEB4, 0x0634,0xFEB5,0xFEB6,0xFEB7,0xFEB8, 0x0635,0xFEB9,0xFEBA,0xFEBB,0xFEBC,
0x0636,0xFEBD,0xFEBE,0xFEBF,0xFEC0, 0x0637,0xFEC1,0xFEC2,0xFEC3,0xFEC4, 0x0638,0xFEC5,0xFEC6,0xFEC7,0xFEC8,
0x0639,0xFEC9,0xFECA,0xFECB,0xFECC, 0x063A,0xFECD,0xFECE,0xFECF,0xFED0, 0x0641,0xFED1,0xFED2,0xFED3,0xFED4,
0x0642,0xFED5,0xFED6,0xFED7,0xFED8, 0x0643,0xFED9,0xFEDA,0xFEDB,0xFEDC, 0x0644,0xFEDD,0xFEDE,0xFEDF,0xFEE0,
0x0645,0xFEE1,0xFEE2,0xFEE3,0xFEE4, 0x0646,0xFEE5,0xFEE6,0xFEE7,0xFEE8, 0x0647,0xFEE9,0xFEEA,0xFEEB,0xFEEC,
0x0648,0xFEED,0xFEEE,0,0, 0x064A,0xFEF1,0xFEF2,0xFEF3,0xFEF4, 0x067E,0xFB56,0xFB57,0xFB58,0xFB59,
0x0686,0xFB7A,0xFB7B,0xFB7C,0xFB7D, 0x0698,0xFB8A,0xFB8B,0,0, 0x06A9,0xFB8E,0xFB8F,0xFB90,0xFB91,
0x06AF,0xFB92,0xFB93,0xFB94,0xFB95, 0x06CC,0xFBFC,0xFBFD,0xFBFE,0xFBFF
};
//+------------------------------------------------------------------+
//| Basic helpers |
//+------------------------------------------------------------------+
uint A(color c){ return ColorToARGB(c,255); }
int P(double v){ return (int)MathRound(v*g_S); }
color Mix(color a,color b,double t)
{
int ai=(int)a, bi=(int)b;
int ar=ai&0xFF, ag=(ai>>8)&0xFF, ab=(ai>>16)&0xFF;
int br=bi&0xFF, bg=(bi>>8)&0xFF, bb=(bi>>16)&0xFF;
int r=(int)MathRound(ar+(br-ar)*t);
int g=(int)MathRound(ag+(bg-ag)*t);
int l=(int)MathRound(ab+(bb-ab)*t);
return (color)(r|(g<<8)|(l<<16));
}
// accent colour for text on tinted backgrounds: darker on light theme, lighter on dark theme
color Dk(color c,double t){ return g_dark?Mix(c,clrWhite,0.62):Mix(c,clrBlack,t); }
//+------------------------------------------------------------------+
//| Persian text engine (digits + letter joining + RTL ordering) |
//+------------------------------------------------------------------+
int ArIdx(ushort c)
{
int n=ArraySize(g_ar)/5;
for(int i=0;i<n;i++) if(g_ar[i*5]==c) return i;
return -1;
}
bool IsLtrCh(ushort c)
{
return (c>='0'&&c<='9')||(c>='A'&&c<='Z')||(c>='a'&&c<='z')||(c>=0x06F0&&c<=0x06F9)||(c>=0x0660&&c<=0x0669);
}
bool IsLatinLetter(ushort c){ return (c>='A'&&c<='Z')||(c>='a'&&c<='z'); }
bool IsJoinCh(ushort c){ return c==':'||c=='.'||c=='/'||c=='-'||c=='+'||c=='%'||c==','||c=='_'; }
string FaShape(string s)
{
int n=StringLen(s);
if(n==0) return s;
ushort o[]; ArrayResize(o,n+2);
int m=0, prev=-1;
for(int i=0;i<n;i++)
{
ushort c=StringGetCharacter(s,i);
if(c==0x200C){ prev=-1; continue; } // ZWNJ
int row=ArIdx(c);
if(row<0){ o[m++]=c; prev=-1; continue; }
bool pj=(prev>=0 && g_ar[prev*5+3]!=0);
if(c==0x0644 && i+1<n && StringGetCharacter(s,i+1)==0x0627) // lam-alef
{ o[m++]=pj?0xFEFC:0xFEFB; i++; prev=-1; continue; }
bool nj=false;
if(g_ar[row*5+3]!=0 && i+1<n)
{
ushort nc=StringGetCharacter(s,i+1);
if(nc!=0x0621 && ArIdx(nc)>=0) nj=true;
}
ushort f;
if(pj&&nj) f=g_ar[row*5+4];
else if(nj) f=g_ar[row*5+3];
else if(pj) f=g_ar[row*5+2];
else f=g_ar[row*5+1];
o[m++]=f; prev=row;
}
// reverse whole string (RTL -> visual LTR)
for(int i=0;i<m/2;i++){ ushort t=o[i]; o[i]=o[m-1-i]; o[m-1-i]=t; }
// restore LTR runs (digits / latin) and mirror brackets
for(int i=0;i<m;i++)
{
if(IsLtrCh(o[i]))
{
int j=i;
while(j+1<m)
{
if(IsLtrCh(o[j+1])) j++;
else if(IsJoinCh(o[j+1]) && j+2<m && IsLtrCh(o[j+2])) j+=2;
else if(o[j+1]==' ' && IsLatinLetter(o[j]) && j+2<m && IsLatinLetter(o[j+2])) j+=2;
else break;
}
for(int a=i,b=j;a<b;a++,b--){ ushort t=o[a]; o[a]=o[b]; o[b]=t; }
i=j;
}
else if(o[i]=='(') o[i]=')';
else if(o[i]==')') o[i]='(';
}
string r="";
for(int i=0;i<m;i++) r+=ShortToString(o[i]);
return r;
}
string Dig(string s)
{
if(!g_pd) return s;
string r=""; int n=StringLen(s);
for(int i=0;i<n;i++)
{
ushort c=StringGetCharacter(s,i);
if(c>='0'&&c<='9') c=(ushort)(0x06F0+(c-'0'));
r+=ShortToString(c);
}
return r;
}
string Pre(string s,bool shape){ s=Dig(s); if(shape&&g_fix) s=FaShape(s); return s; }
string L(string fa,string en){ return g_fa ? Pre(fa,true) : Pre(en,false); }
//+------------------------------------------------------------------+
//| Time zones & daylight saving |
//+------------------------------------------------------------------+
datetime NthSun(int y,int mo,int n)
{
MqlDateTime d; d.year=y; d.mon=mo; d.day=1; d.hour=0; d.min=0; d.sec=0;
datetime f=StructToTime(d);
TimeToStruct(f,d);
int fs=1+(7-d.day_of_week)%7;
int day;
if(n>0) day=fs+(n-1)*7;
else day=fs+7*((31-fs)/7);
return f+(datetime)((day-1)*86400);
}
long g_yFrom=0,g_yTo=0;
datetime g_euS=0,g_euE=0,g_usS=0,g_usE=0,g_auS=0,g_auE=0;
void DstEnsure(datetime u)
{
if((long)u>=g_yFrom && (long)u<g_yTo) return;
MqlDateTime d; TimeToStruct(u,d);
int y=d.year;
d.mon=1; d.day=1; d.hour=0; d.min=0; d.sec=0;
g_yFrom=(long)StructToTime(d);
d.year=y+1; g_yTo=(long)StructToTime(d);
g_euS=NthSun(y,3,-1)+3600; g_euE=NthSun(y,10,-1)+3600;
g_usS=NthSun(y,3,2)+7*3600; g_usE=NthSun(y,11,1)+6*3600;
g_auE=NthSun(y,4,1)-28800; g_auS=NthSun(y,10,1)-28800;
}
bool EuDst(datetime u){ DstEnsure(u); return (u>=g_euS && u<g_euE); }
bool UsDst(datetime u){ DstEnsure(u); return (u>=g_usS && u<g_usE); }
bool AuDst(datetime u){ DstEnsure(u); return (u<g_auE || u>=g_auS); }
int ZoneOff(int z,datetime u,bool dst=true)
{
switch(z)
{
case TZ_TEHRAN: return 12600;
case TZ_UTC: return 0;
case TZ_BROKER: { long d=(long)TimeTradeServer()-(long)TimeGMT(); return (int)(MathRound(d/900.0)*900); }
case TZ_LONDON: return (dst&&EuDst(u))?3600:0;
case TZ_NEWYORK: return -18000+((dst&&UsDst(u))?3600:0);
case TZ_TOKYO: return 32400;
case TZ_SYDNEY: return 36000+((dst&&AuDst(u))?3600:0);
case TZ_DUBAI: return 14400;
case TZ_ISTANBUL: return 10800;
case TZ_BERLIN: return 3600+((dst&&EuDst(u))?3600:0);
case TZ_CUSTOM: return InpCustomMin*60;
}
return 0;
}
string TzLong(int z,bool fa)
{
string f[TZ_COUNT]={"ایران – تهران","زمان جهانی (UTC)","زمان سرور بروکر","انگلیس – لندن","آمریکا – نیویورک","ژاپن – توکیو","استرالیا – سیدنی","امارات – دبی","ترکیه – استانبول","آلمان – برلین","دلخواه (UTC±)"};
string e[TZ_COUNT]={"Iran – Tehran","UTC (Universal)","Broker server time","UK – London","USA – New York","Japan – Tokyo","Australia – Sydney","UAE – Dubai","Turkey – Istanbul","Germany – Berlin","Custom offset"};
return fa?f[z]:e[z];
}
string TzShort(int z,bool fa)
{
string f[TZ_COUNT]={"تهران","UTC","سرور","لندن","نیویورک","توکیو","سیدنی","دبی","استانبول","برلین","دلخواه"};
string e[TZ_COUNT]={"Tehran","UTC","Server","London","New York","Tokyo","Sydney","Dubai","Istanbul","Berlin","Custom"};
return fa?f[z]:e[z];
}
int ParseHM(string s,int def)
{
StringTrimLeft(s); StringTrimRight(s);
int p=StringFind(s,":");
if(p<0) return def;
int h=(int)StringToInteger(StringSubstr(s,0,p));
int m=(int)StringToInteger(StringSubstr(s,p+1));
if(h<0||h>24||m<0||m>59) return def;
return (h*60+m)%1440;
}
void SetupSessions()
{
g_s[0].on=InpSydOn; g_s[0].zone=TZ_SYDNEY; g_s[0].sMin=(int)InpSydStart; g_s[0].eMin=(int)InpSydEnd;
g_s[0].dst=InpSydDst; g_s[0].clr=InpSydClr; g_s[0].fa="سیدنی"; g_s[0].en="Sydney";
g_s[1].on=InpTokOn; g_s[1].zone=TZ_TOKYO; g_s[1].sMin=(int)InpTokStart; g_s[1].eMin=(int)InpTokEnd;
g_s[1].dst=false; g_s[1].clr=InpTokClr; g_s[1].fa="توکیو"; g_s[1].en="Tokyo";
g_s[2].on=InpLonOn; g_s[2].zone=TZ_LONDON; g_s[2].sMin=(int)InpLonStart; g_s[2].eMin=(int)InpLonEnd;
g_s[2].dst=InpLonDst; g_s[2].clr=InpLonClr; g_s[2].fa="لندن"; g_s[2].en="London";
g_s[3].on=InpNyOn; g_s[3].zone=TZ_NEWYORK; g_s[3].sMin=(int)InpNyStart; g_s[3].eMin=(int)InpNyEnd;
g_s[3].dst=InpNyDst; g_s[3].clr=InpNyClr; g_s[3].fa="نیویورک"; g_s[3].en="New York";
}
// five consecutive instances (UTC seconds) of session s around time u
void Inst(int s,long u,long &st[],long &en[])
{
int z=g_s[s].zone; bool dst=g_s[s].dst;
long lu=u+ZoneOff(z,(datetime)u,dst);
long ld=lu-(lu%86400);
int dur=g_s[s].eMin-g_s[s].sMin; if(dur<=0) dur+=1440;
for(int d=-2;d<=2;d++)
{
long ls=ld+(long)d*86400+(long)g_s[s].sMin*60;
long g=ls-ZoneOff(z,(datetime)ls,dst);
long su=ls-ZoneOff(z,(datetime)g,dst);
st[d+2]=su; en[d+2]=su+(long)dur*60;
}
}
bool WkClosed(datetime u)
{
MqlDateTime d; TimeToStruct(u,d);
if(d.day_of_week==6) return true;
if(d.day_of_week==5 && d.hour>=22) return true;
if(d.day_of_week==0 && d.hour<22) return true;
return false;
}
void GetState(int s,SessSt &r)
{
long st[5],en[5]; Inst(s,g_u,st,en);
int ai=-1,ni=-1;
for(int i=0;i<5;i++)
{
if(st[i]<=g_u && g_u<en[i]) ai=i;
if(st[i]>g_u && (ni<0 || st[i]<st[ni])) ni=i;
}
if(ni<0) ni=4;
if(ai>=0 && !g_wk)
{
r.active=true; r.st=st[ai]; r.en=en[ai]; r.left=en[ai]-g_u;
r.prog=(double)(g_u-st[ai])/(double)(en[ai]-st[ai]);
}
else
{
r.active=false; r.st=st[ni]; r.en=en[ni]; r.left=st[ni]-g_u; r.prog=0;
}
}
void RefreshStates(){ for(int s=0;s<4;s++) if(g_s[s].on) GetState(s,g_st[s]); }
int ClipDay(int s,long &a0[],long &a1[])
{
long st[5],en[5]; Inst(s,g_u,st,en);
int n=0;
for(int i=0;i<5;i++)
{
long a=st[i]+g_refOff-g_refMid, b=en[i]+g_refOff-g_refMid;
if(a<0) a=0;
if(b>86400) b=86400;
if(b>a){ a0[n]=a; a1[n]=b; n++; }
}
return n;
}
bool Overlap(long &a,long &b)
{
if(!g_s[2].on||!g_s[3].on) return false;
long s1[5],e1[5],s2[5],e2[5];
Inst(2,g_u,s1,e1); Inst(3,g_u,s2,e2);
bool f=false; long bs=0,be=0;
for(int i=0;i<5;i++)
for(int j=0;j<5;j++)
{
long lo=MathMax(s1[i],s2[j]), hi=MathMin(e1[i],e2[j]);
if(hi>lo && hi>g_u && (!f||lo<bs)){ bs=lo; be=hi; f=true; }
}
a=bs; b=be; return f;
}
string HM(long t)
{
long l=t+g_refOff; int m=(int)((l%86400)/60);
return StringFormat("%02d:%02d",m/60,m%60);
}
void DayW(long t,string &fa,string &en)
{
long l=t+g_refOff; long dd=(l-(l%86400)-g_refMid)/86400;
if(dd==0){ fa="امروز"; en="Today"; }
else if(dd==1){ fa="فردا"; en="Tomorrow"; }
else if(dd==-1){ fa="دیروز"; en="Yesterday"; }
else { fa=IntegerToString(dd)+" روز"; en=IntegerToString(dd)+" d"; }
}
string DurTxt(long s,bool fa)
{
if(s<0) s=0;
long h=s/3600, m=(s%3600)/60;
if(fa)
{
if(h>0) return IntegerToString(h)+" ساعت و "+IntegerToString(m)+" دقیقه";
if(m>0) return IntegerToString(m)+" دقیقه";
return "کمتر از ۱ دقیقه";
}
if(h>0) return IntegerToString(h)+"h "+IntegerToString(m)+"m";
if(m>0) return IntegerToString(m)+"m";
return "<1m";
}
// Gregorian -> Jalali
void G2J(int gy,int gm,int gd,int &jy,int &jm,int &jd)
{
int gdm[12]={0,31,59,90,120,151,181,212,243,273,304,334};
int gy2=(gm>2)?(gy+1):gy;
int days=355666+(365*gy)+((gy2+3)/4)-((gy2+99)/100)+((gy2+399)/400)+gd+gdm[gm-1];
jy=-1595+(33*(days/12053)); days%=12053;
jy+=4*(days/1461); days%=1461;
if(days>365){ jy+=(days-1)/365; days=(days-1)%365; }
if(days<186){ jm=1+(days/31); jd=1+(days%31); }
else { jm=7+((days-186)/30); jd=1+((days-186)%30); }
}
//+------------------------------------------------------------------+
//| Drawing primitives |
//+------------------------------------------------------------------+
void FillRRc(int x,int y,int w,int h,int r,uint clr)
{
if(w<=0||h<=0) return;
if(r>h/2) r=h/2;
if(r>w/2) r=w/2;
if(r<1){ g_cv.FillRectangle(x,y,x+w-1,y+h-1,clr); return; }
g_cv.FillRectangle(x+r,y,x+w-1-r,y+h-1,clr);
g_cv.FillRectangle(x,y+r,x+w-1,y+h-1-r,clr);
g_cv.FillCircle(x+r,y+r,r,clr); g_cv.FillCircle(x+w-1-r,y+r,r,clr);
g_cv.FillCircle(x+r,y+h-1-r,r,clr); g_cv.FillCircle(x+w-1-r,y+h-1-r,r,clr);
}
void RR(int x,int y,int w,int h,int r,color fill,color border,int t=1)
{
FillRRc(x,y,w,h,r,A(border));
FillRRc(x+t,y+t,w-2*t,h-2*t,MathMax(r-t,0),A(fill));
}
void CircAA(int cx,int cy,double r,color c)
{
int n=(int)MathMax(32,r*0.9);
int px=0,py=0;
for(int i=0;i<=n;i++)
{
double a=2*M_PI*i/n;
int x=(int)MathRound(cx+r*MathSin(a)), y=(int)MathRound(cy-r*MathCos(a));
if(i>0) g_cv.Line(px,py,x,y,A(c));
px=x; py=y;
}
}
void Disc(int cx,int cy,double r,color c,bool aa=false)
{
g_cv.FillCircle(cx,cy,(int)MathRound(r),A(c));
}
// ring segment; angles in radians, 0 = top, clockwise
void Seg(int cx,int cy,double r1,double r0,double a0,double a1,color c)
{
int steps=(int)MathMax(2,MathCeil((a1-a0)/0.12));
int n=(steps+1)*2;
int xs[],ys[]; ArrayResize(xs,n); ArrayResize(ys,n);
for(int k=0;k<=steps;k++)
{
double a=a0+(a1-a0)*k/steps;
xs[k]=(int)MathRound(cx+r1*MathSin(a)); ys[k]=(int)MathRound(cy-r1*MathCos(a));
double b=a1-(a1-a0)*k/steps;
xs[steps+1+k]=(int)MathRound(cx+r0*MathSin(b)); ys[steps+1+k]=(int)MathRound(cy-r0*MathCos(b));
}
g_cv.FillPolygon(xs,ys,A(c));
}
void Needle(CCanvas &cv,int cx,int cy,double ang,double len,double tail,double wid,color c)
{
double sx=MathSin(ang), sy=-MathCos(ang), qx=-sy, qy=sx;
int xs[4],ys[4];
xs[0]=(int)MathRound(cx+sx*len); ys[0]=(int)MathRound(cy+sy*len);
xs[1]=(int)MathRound(cx+qx*wid); ys[1]=(int)MathRound(cy+qy*wid);
xs[2]=(int)MathRound(cx-sx*tail); ys[2]=(int)MathRound(cy-sy*tail);
xs[3]=(int)MathRound(cx-qx*wid); ys[3]=(int)MathRound(cy-qy*wid);
cv.FillPolygon(xs,ys,A(c));
}
// soft drop-shadow (pre-blended with the panel background, so it needs no transparency)
void Shadow(int x,int y,int w,int h,int r,color sh,int blur)
{
int u=MathMax(1,P(1.2));
for(int k=blur;k>=1;k--)
{
double t=(g_dark?0.30:0.17)*(blur-k+1)/(double)blur;
int e=k*u;
FillRRc(x-e,y-e+2*u,w+2*e,h+2*e,r+e,A(Mix(kBg,sh,t)));
}
}
// raised card: shadow + body + top highlight / bottom shade (3-D look)
void Card(int x,int y,int w,int h,int r,color fill,color border,int t=1,int blur=3,color sh=clrNONE)
{
if(sh==clrNONE) sh=g_dark?clrBlack:C'40,55,100';
Shadow(x,y,w,h,r,sh,blur);
RR(x,y,w,h,r,fill,border,t);
color hi=g_dark?Mix(fill,clrWhite,0.10):Mix(fill,clrWhite,0.8);
color lo=g_dark?Mix(fill,clrBlack,0.30):Mix(fill,C'40,55,100',0.10);
g_cv.Line(x+r,y+t,x+w-r,y+t,A(hi));
g_cv.Line(x+r,y+h-t-1,x+w-r,y+h-t-1,A(lo));
}
void DiscShadow(int cx,int cy,int R)
{
color sh=g_dark?clrBlack:C'40,55,100';
for(int k=4;k>=1;k--)
Disc(cx,cy+P(3),R+P(4)+k*P(1.6),Mix(kCard,sh,(g_dark?0.34:0.18)*(5-k)/4.0));
}
// section title with an accent bar
void Title(int xa,int xb,int y,string s)
{
int off=P(16);
int bx=g_fa?xb-P(5):xa;
FillRRc(bx,y-P(13),P(5),P(26),P(2),A(kAccent));
TxS(g_fa?xa:xa+off,g_fa?xb-off:xb,y,s,kText,21,true);
}
void FS(string n,int p,uint f){ if(p==g_fp && f==g_ff && n==g_fn) return; g_cv.FontSet(n,p,f); g_fn=n; g_fp=p; g_ff=f; }
// text helpers (sizes are in base units, scaled by P)
void Tx(int x,int y,string s,color c,double sz,bool bold,uint al)
{
if(s=="") return;
FS(g_fa?g_fFa:g_fEn,P(sz),bold?FW_BOLD:FW_NORMAL);
g_cv.TextOut(x,y,s,A(c),al|TA_VCENTER);
}
void TxS(int xa,int xb,int y,string s,color c,double sz,bool bold) // text at the reading-start side
{ if(g_fa) Tx(xb,y,s,c,sz,bold,TA_RIGHT); else Tx(xa,y,s,c,sz,bold,TA_LEFT); }
void TxE(int xa,int xb,int y,string s,color c,double sz,bool bold) // text at the reading-end side
{ if(g_fa) Tx(xa,y,s,c,sz,bold,TA_LEFT); else Tx(xb,y,s,c,sz,bold,TA_RIGHT); }
int TW(string s,double sz,bool bold)
{
FS(g_fa?g_fFa:g_fEn,P(sz),bold?FW_BOLD:FW_NORMAL);
return g_cv.TextWidth(s);
}
int ColX(int k,int cw,int gap)
{
int left=P(10), right=left+cw+gap;
return ((k==0)==g_fa)?right:left;
}
color SC(int s,bool active){ return active?g_s[s].clr:Mix(g_s[s].clr,kCard,g_dark?0.28:0.55); }
// readable text colour on top of a coloured arc
color OnClr(color c)
{
int ci=(int)c;
double lum=(0.299*(ci&0xFF)+0.587*((ci>>8)&0xFF)+0.114*((ci>>16)&0xFF))/255.0;
return (lum>0.55)?C'31,42,68':clrWhite;
}
int OnCount(){ int n=0; for(int i=0;i<4;i++) if(g_s[i].on) n++; return n; }
string DurShort(long s,bool fa)
{
if(s<0) s=0;
long h=s/3600, m=(s%3600)/60;
if(fa)
{
if(m==0) return IntegerToString(h)+" ساعت";
if(h>0) return IntegerToString(h)+" ساعت و "+IntegerToString(m)+" دقیقه";
return IntegerToString(m)+" دقیقه";
}
if(m==0) return IntegerToString(h)+"h";
if(h>0) return IntegerToString(h)+"h "+IntegerToString(m)+"m";
return IntegerToString(m)+"m";
}
string UtcStr(int off)
{
int a=MathAbs(off)/60;
return StringFormat("UTC%s%02d:%02d",off<0?"-":"+",a/60,a%60);
}
// nearest (current or next) overlap of two sessions
bool PairNext(int a,int b,long &lo,long &hi,bool &now)
{
long s1[5],e1[5],s2[5],e2[5];
Inst(a,g_u,s1,e1); Inst(b,g_u,s2,e2);
bool f=false; lo=0; hi=0; now=false;
for(int i=0;i<5;i++)
for(int j=0;j<5;j++)
{
long l1=MathMax(s1[i],s2[j]), h1=MathMin(e1[i],e2[j]);
if(h1>l1 && h1>g_u && (!f || l1<lo)){ lo=l1; hi=h1; f=true; }
}
if(f) now=(!g_wk && lo<=g_u && g_u<hi);
return f;
}
bool NextOvAny(int &ba,int &bb,long &lo,long &hi,bool &now)
{
bool f=false; ba=-1; bb=-1; lo=0; hi=0; now=false;
for(int q=0;q<g_np;q++)
{
int a=g_pa[q], b=g_pb[q];
long l,h; bool nw;
if(!PairNext(a,b,l,h,nw)) continue;
if(!f || (nw&&!now) || (nw==now && l<lo)){ f=true; ba=a; bb=b; lo=l; hi=h; now=nw; }
}
return f;
}
// overlap segments of a pair inside today's 24h window (seconds from local midnight)
int PairClip(int a,int b,long &a0[],long &a1[])
{
long s1[5],e1[5],s2[5],e2[5];
Inst(a,g_u,s1,e1); Inst(b,g_u,s2,e2);
int n=0;
for(int i=0;i<5;i++)
for(int j=0;j<5;j++)
{
long lo=MathMax(s1[i],s2[j]), hi=MathMin(e1[i],e2[j]);
if(hi<=lo) continue;
long x=lo+g_refOff-g_refMid, y=hi+g_refOff-g_refMid;
if(x<0) x=0;
if(y>86400) y=86400;
if(y>x && n<25){ a0[n]=x; a1[n]=y; n++; }
}
return n;
}
// can these two sessions ever overlap (checked in winter and summer)?
bool PairEver(int a,int b)
{
MqlDateTime d; TimeToStruct(TimeGMT(),d);
for(int m=0;m<2;m++)
{
d.mon=(m==0)?1:7; d.day=15; d.hour=12; d.min=0; d.sec=0;
long u=(long)StructToTime(d);
long s1[5],e1[5],s2[5],e2[5];
Inst(a,u,s1,e1); Inst(b,u,s2,e2);
for(int i=0;i<5;i++)
for(int j=0;j<5;j++)
if(MathMin(e1[i],e2[j])-MathMax(s1[i],s2[j])>=900) return true;
}
return false;
}
void SetupPairs()
{
g_np=0;
for(int a=0;a<4;a++)
for(int b=a+1;b<4;b++)
if(g_s[a].on && g_s[b].on && PairEver(a,b) && g_np<6){ g_pa[g_np]=a; g_pb[g_np]=b; g_np++; }
}
// sort key: start of the pair's overlap measured from the first enabled session's open (market-day order)
long PairKey(int a,int b)
{
int f=0; for(int s=0;s<4;s++) if(g_s[s].on){ f=s; break; }
long s0[5],e0[5]; Inst(f,g_u,s0,e0);
long O=s0[2];
long s1[5],e1[5],s2[5],e2[5];
Inst(a,g_u,s1,e1); Inst(b,g_u,s2,e2);
long best=2000000000;
for(int i=0;i<5;i++)
for(int j=0;j<5;j++)
{
long lo=MathMax(s1[i],s2[j]), hi=MathMin(e1[i],e2[j]);
if(hi-lo<900) continue;
long k=((lo-O)%86400+86400)%86400;
if(k<best) best=k;
}
return best;
}
void SortPairs()
{
long key[6];
for(int q=0;q<g_np;q++) key[q]=PairKey(g_pa[q],g_pb[q]);
for(int i=1;i<g_np;i++)
{
long kk=key[i]; int pa=g_pa[i], pb=g_pb[i]; int j=i-1;
while(j>=0 && key[j]>kk){ key[j+1]=key[j]; g_pa[j+1]=g_pa[j]; g_pb[j+1]=g_pb[j]; j--; }
key[j+1]=kk; g_pa[j+1]=pa; g_pb[j+1]=pb;
}
}
//+------------------------------------------------------------------+
//| Horizontal layout (base units) |
//+------------------------------------------------------------------+
#define W_A 330
#define W_B 580
#define W_C 390
#define G_GAP 10
#define H_HDR 68
#define H_CLK 312
#define H_DIG 150
#define CARD_H 150
#define H_INF 74
#define H_ALR 54
#define ROW_OV 54
bool HasA(){ return g_sh[SH_CLK]||g_sh[SH_DIG]; }
bool HasB(){ return g_sh[SH_CRD]||g_sh[SH_INF]; }
bool HasC(){ return g_sh[SH_OVL]||g_sh[SH_ALR]; }
int NPairs(){ return g_np; }
int HCards(){ return 56+((OnCount()+1)/2)*(CARD_H+10); }
int HOvl(){ return 56+NPairs()*ROW_OV+10; }
int HTL(){ int n=OnCount(), np=NPairs(); return 56+(n+np)*26+(np>0?8:0)+36; }
int BodyH()
{
int a=0,b=0,c=0;
if(g_sh[SH_CLK]) a+=H_CLK;
if(g_sh[SH_DIG]) a+=H_DIG;
if(g_sh[SH_CRD]) b+=HCards();
if(g_sh[SH_INF]) b+=H_INF;
if(g_sh[SH_OVL]) c+=HOvl();
if(g_sh[SH_ALR]) c+=H_ALR;
return MathMax(a,MathMax(b,c));
}
// computes panel width (g_PWb) and returns panel height in base units
int Metrics()
{
int cw=0,cnt=0;
if(HasA()){ cw+=W_A; cnt++; }
if(HasB()){ cw+=W_B; cnt++; }
if(HasC()){ cw+=W_C; cnt++; }
if(cnt>1) cw+=(cnt-1)*G_GAP;
int w=cw+24;
if(g_sh[SH_HDR]) w=MathMax(w,1344);
if(g_sh[SH_TL]) w=MathMax(w,760);
if(w<400) w=400;
g_PWb=w;
g_colX0=12+(w-24-cw)/2;
int h=12;
if(g_sh[SH_HDR]) h+=H_HDR+10;
int bh=BodyH(); if(bh>0) h+=bh+10;
if(g_sh[SH_TL]) h+=HTL()+10;
return h+12;
}
int MX(int x,int w){ return P(g_fa?(g_PWb-x-w):x); } // block left pixel (mirrored for RTL)
string StatusText(color &pc,bool shortForm=false)
{
string af="",ae=""; int na=0; pc=kSub;
for(int s=0;s<4;s++)
{
if(!g_s[s].on || !g_st[s].active) continue;
if(na>0){ af+=" و "; ae+=" + "; }
af+=g_s[s].fa; ae+=g_s[s].en;
if(na==0) pc=g_s[s].clr;
na++;
}
if(g_wk){ pc=kSub; return shortForm?L("بازار بسته","Market closed"):L("بازار بسته است (آخر هفته)","Market closed (weekend)"); }
if(na==0) return shortForm?L("بدون سشن فعال","No active session"):L("اکنون هیچ سشنی فعال نیست","No session is active now");
if(shortForm) return L("فعال: "+af,"Active: "+ae);
return L("سشن "+af+" فعال است",ae+(na>1?" sessions":" session")+" active");
}
void Pill(int cx,int y,int h,string txt,color pc,double sz)
{
int tw=TW(txt,sz,true), pw=tw+P(40);
int px=cx-pw/2;
RR(px,y,pw,h,h/2,Mix(pc,kCard,0.85),Mix(pc,kCard,0.55));
Disc(g_fa?px+pw-P(14):px+P(14),y+h/2,P(4.5),pc);
Tx(g_fa?cx-P(6):cx+P(6),y+h/2,txt,Dk(pc,0.4),sz,true,TA_CENTER);
}
//--------------------------- header -------------------------------
void DrawHeader(int y)
{
int L0=P(12), R0=g_W-P(12), h=P(H_HDR);
Card(L0,y,R0-L0,h,P(14),kCard,kBorder,1,4);
int ia=L0+P(18), ib=R0-P(18);
if(InpBrand!="")
{
TxS(ia,ib,y+P(26),InpBrand,kText,25,true);
TxS(ia,ib,y+P(51),L("ساعت و نقشه‌ی سشن‌های فارکس","Forex sessions clock & map"),kSub,13.5,false);
}
color pc; string st=StatusText(pc);
int ctrlW=2*(P(86)+P(10))+P(120)+P(10)+P(300);
int brandW=0;
if(InpBrand!="") brandW=MathMax(TW(InpBrand,25,true),TW(L("ساعت و نقشه‌ی سشن‌های فارکس","Forex sessions clock & map"),12.5,false));
int zl,zr;
if(g_fa){ zl=ia+ctrlW+P(28); zr=ib-brandW-P(28); }
else { zl=ia+brandW+P(28); zr=ib-ctrlW-P(28); }
double psz=14;
while(psz>10 && TW(st,psz,true)+P(40)>zr-zl) psz-=1;
if(TW(st,psz,true)+P(40)>zr-zl){ st=StatusText(pc,true); psz=12; }
Pill((zl+zr)/2,y+(h-P(34))/2,P(34),st,pc,psz);
int bw=P(86), bh=P(40), gp=P(10), cwid=P(300), ch=P(50);
int by=y+(h-bh)/2, cy=y+(h-ch)/2;
int xP=g_fa?ia:ib-bw;
int xE=g_fa?ia+bw+gp:ib-2*bw-gp;
int tw2=P(120);
int xT=g_fa?ia+2*(bw+gp):ib-2*(bw+gp)-tw2;
int xC=g_fa?ia+2*(bw+gp)+tw2+gp:ib-2*(bw+gp)-tw2-gp-cwid;
Card(xP,by,bw,bh,P(10),g_fa?kNavy:kCard,g_fa?kNavy:kBorder,1,2);
Card(xE,by,bw,bh,P(10),g_fa?kCard:kNavy,g_fa?kBorder:kNavy,1,2);
g_cv.FontSet(g_fFa,P(14),FW_BOLD); g_fn="";
g_cv.TextOut(xP+bw/2,by+bh/2,Pre("فارسی",true),A(g_fa?clrWhite:kSub),TA_CENTER|TA_VCENTER);
g_cv.FontSet(g_fEn,P(14),FW_BOLD); g_fn="";
g_cv.TextOut(xE+bw/2,by+bh/2,"English",A(g_fa?kSub:clrWhite),TA_CENTER|TA_VCENTER);
g_hit[1][0]=xP; g_hit[1][1]=by; g_hit[1][2]=bw; g_hit[1][3]=bh;
g_hit[2][0]=xE; g_hit[2][1]=by; g_hit[2][2]=bw; g_hit[2][3]=bh;
// light / dark theme switch
Card(xT,by,tw2,bh,P(10),kCard,kBorder,1,2);
g_hit[3][0]=xT; g_hit[3][1]=by; g_hit[3][2]=tw2; g_hit[3][3]=bh;
int icx=g_fa?xT+tw2-P(26):xT+P(26), icy=by+bh/2;
if(g_dark){ Disc(icx,icy,P(9),C'250,204,21'); Disc(icx+P(5),icy-P(3),P(8),kCard); }
else
{
Disc(icx,icy,P(6),C'245,158,11');
for(int q=0;q<8;q++)
{
double aq=q*M_PI/4;
g_cv.Line((int)MathRound(icx+P(9)*MathSin(aq)),(int)MathRound(icy-P(9)*MathCos(aq)),
(int)MathRound(icx+P(13)*MathSin(aq)),(int)MathRound(icy-P(13)*MathCos(aq)),A(C'245,158,11'));
}
}
Tx(g_fa?xT+(tw2-P(44))/2:xT+P(44)+(tw2-P(44))/2,by+bh/2,g_dark?L("تیره","Dark"):L("روشن","Light"),kText,14,true,TA_CENTER);
// time-zone combo box
Card(xC,cy,cwid,ch,P(10),kCard,kAccent,2,3);
g_hit[0][0]=xC; g_hit[0][1]=cy; g_hit[0][2]=cwid; g_hit[0][3]=ch;
int ca=xC+P(14), cb=xC+cwid-P(14);
int ts=P(6), ax=g_fa?ca+P(6):cb-P(6), ay=cy+ch/2;
g_cv.FillTriangle(ax-ts,ay-ts/2,ax+ts,ay-ts/2,ax,ay+ts,A(kNavy));
int na=g_fa?ca+P(22):ca, nb=g_fa?cb:cb-P(22);
TxS(na,nb,cy+P(15),L("زمان مرجع (برای تغییر کلیک کنید)","Reference time (click to change)"),kSub,12.5,false);
TxS(na,nb,cy+P(35),Pre(TzLong(g_tz,g_fa),g_fa),kText,15,true);
}
//--------------------------- clock --------------------------------
void DrawClock(int x0,int w,int y)
{
double k=1.24;
int cx=x0+w/2, cy=y+P(142);
double R=P(100*k);
DiscShadow(cx,cy,(int)R);
Disc(cx,cy,R+P(4),kBorder);
Disc(cx,cy,R+P(2.5),kCard);
Disc(cx,cy,R,kFace);
for(int h=0;h<24;h++)
{
double a=2*M_PI*h/24.0;
double r1=P(98.5*k), r2=(h%6==0)?P(91*k):P(95*k);
g_cv.Line((int)MathRound(cx+r1*MathSin(a)),(int)MathRound(cy-r1*MathCos(a)),
(int)MathRound(cx+r2*MathSin(a)),(int)MathRound(cy-r2*MathCos(a)),A(h%6==0?kSub:kBorder));
if(InpShowNumbers)
{
double rn=P(84*k);
FS(g_fEn,P(12.5),(h%6==0)?FW_BOLD:FW_NORMAL);
g_cv.TextOut((int)MathRound(cx+rn*MathSin(a)),(int)MathRound(cy-rn*MathCos(a)),Dig(IntegerToString(h)),
A((h%6==0)?kText:kMid),TA_CENTER|TA_VCENTER);
}
}
double ro[4]={77,62.5,48,33.5};
double th=13.2;
for(int s=0;s<4;s++)
{
if(!g_s[s].on) continue;
double r1=P(ro[s]*k), r0=P((ro[s]-th)*k);
Seg(cx,cy,r1,r0,0.0,2*M_PI-0.0001,kTrack);
SessSt st=g_st[s];
long a0[5],a1[5]; int n=ClipDay(s,a0,a1);
color c=SC(s,st.active);
double best=0,bm=0;
for(int i=0;i<n;i++)
{
double f0=a0[i]/86400.0*2*M_PI, f1=a1[i]/86400.0*2*M_PI;
Seg(cx,cy,r1,r0,f0,f1,c);
if(f1-f0>best){ best=f1-f0; bm=(f0+f1)/2; }
}
if(best>0.6)
{
double rm=(r1+r0)/2;
FS(g_fa?g_fFa:g_fEn,P(12),FW_BOLD);
g_cv.TextOut((int)MathRound(cx+rm*MathSin(bm)),(int)MathRound(cy-rm*MathCos(bm)),
g_fa?Pre(g_s[s].fa,true):g_s[s].en,
A(st.active?OnClr(g_s[s].clr):Dk(g_s[s].clr,0.35)),TA_CENTER|TA_VCENTER);
}
}
Disc(cx,cy,P(18*k),kFace);
double ang=(double)(g_refNow%86400)/86400.0*2*M_PI;
Needle(g_cv,cx,cy,ang,P(80*k),P(12),P(3.2),kHand);
Disc(cx,cy,P(6.5),kHand);
Disc(cx,cy,P(2.5),kCard);
if(InpShowSecHand){ g_ckw=2*P(80*k)+4; g_ckx=cx-g_ckw/2; g_cky=cy-g_ckw/2; }
// legend
int ly=y+P(294);
int wsum=0,cnt=0,gp=P(18);
int wid[4];
for(int s=0;s<4;s++){ wid[s]=0; if(!g_s[s].on) continue; wid[s]=P(16)+TW(g_fa?Pre(g_s[s].fa,true):g_s[s].en,13.5,false); wsum+=wid[s]; cnt++; }
wsum+=gp*MathMax(cnt-1,0);
int x=cx-wsum/2;
for(int q=0;q<4;q++)
{
int s=g_fa?3-q:q;
if(!g_s[s].on) continue;
Disc(x+P(5),ly,P(5),g_s[s].clr);
Tx(x+P(16),ly,g_fa?Pre(g_s[s].fa,true):g_s[s].en,kMid,13.5,false,TA_LEFT);
x+=wid[s]+gp;
}
}
//--------------------------- digital + dates -----------------------
void DrawDigital(int x0,int w,int y)
{
int cx=x0+w/2;
color pc; string st=StatusText(pc);
Pill(cx,y,P(32),st,pc,13.5);
RR(cx-P(128),y+P(36),P(256),P(60),P(14),kFace,kBorder);
color ish=g_dark?clrBlack:C'40,55,100';
g_cv.Line(cx-P(112),y+P(37),cx+P(112),y+P(37),A(Mix(kFace,ish,g_dark?0.55:0.14)));
g_cv.Line(cx-P(112),y+P(38),cx+P(112),y+P(38),A(Mix(kFace,ish,g_dark?0.30:0.07)));
g_dgw=1; g_dgx=cx; g_dgy=y+P(66); // native label: HH:MM:SS
Tx(cx,y+P(106),L("ساعت فعلی به وقت "+TzShort(g_tz,true),"Current time in "+TzShort(g_tz,false)),kMid,13,false,TA_CENTER);
MqlDateTime d; TimeToStruct((datetime)g_refNow,d);
int jy,jm,jd; G2J(d.year,d.mon,d.day,jy,jm,jd);
string wf[7]={"یکشنبه","دوشنبه","سه‌شنبه","چهارشنبه","پنجشنبه","جمعه","شنبه"};
string we[7]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
string mf[12]={"فروردین","اردیبهشت","خرداد","تیر","مرداد","شهریور","مهر","آبان","آذر","دی","بهمن","اسفند"};
string mj[12]={"Farvardin","Ordibehesht","Khordad","Tir","Mordad","Shahrivar","Mehr","Aban","Azar","Dey","Bahman","Esfand"};
string me[12]={"January","February","March","April","May","June","July","August","September","October","November","December"};
string greg=StringFormat("%02d %s %d",d.day,me[d.mon-1],d.year);
string jal=g_fa?(wf[d.day_of_week]+" "+IntegerToString(jd)+" "+mf[jm-1]+" "+IntegerToString(jy))
:(IntegerToString(jd)+" "+mj[jm-1]+" "+IntegerToString(jy));
string gS=Dig(greg), jS=Pre(jal,g_fa);
if(!g_fa) gS=Dig(we[d.day_of_week]+", "+greg);
int w1=TW(gS,13,false), w2=TW(jS,13,true), gp=P(22);
int xs=cx-(w1+w2+gp)/2;
int dy=y+P(130);
Tx(xs,dy,gS,kMid,13,false,TA_LEFT);
Disc(xs+w1+gp/2,dy,P(2),kMid);
Tx(xs+w1+gp,dy,jS,kText,13,true,TA_LEFT);
}
//--------------------------- session cards -------------------------
void SessCard(int s,int x,int y,int w,int h)
{
SessSt r=g_st[s];
color c=g_s[s].clr;
Card(x,y,w,h,P(12),r.active?Mix(c,kCard,0.9):kCard,r.active?c:kBorder,r.active?P(2):1,r.active?5:3,r.active?c:clrNONE);
int pad=P(14), xa=x+pad, xb=x+w-pad;
int off=P(18);
Disc(g_fa?xb-P(6):xa+P(6),y+P(24),P(6),c);
TxS(g_fa?xa:xa+off,g_fa?xb-off:xb,y+P(24),g_fa?Pre(g_s[s].fa,true):g_s[s].en,kText,17,true);
string stx=r.active?L("باز","Open"):L("بسته","Closed");
int pw=TW(stx,12.5,true)+P(24), ph=P(26);
int pxx=g_fa?xa:xb-pw;
RR(pxx,y+P(24)-ph/2,pw,ph,ph/2,r.active?Mix(c,kCard,0.78):kTrack,r.active?Mix(c,kCard,0.5):kBorder);
Tx(pxx+pw/2,y+P(24),stx,r.active?Dk(c,0.4):kSub,12.5,true,TA_CENTER);
string df,de;
string tf=TzShort(g_tz,true), te=TzShort(g_tz,false);
DayW(r.st,df,de);
TxS(xa,xb,y+P(62),L("شروع | "+tf,"Start | "+te),kSub,13.5,false);
TxE(xa,xb,y+P(62),L(df+" "+HM(r.st),de+" "+HM(r.st)),kText,14,true);
DayW(r.en,df,de);
TxS(xa,xb,y+P(88),L("پایان | "+tf,"End | "+te),kSub,13.5,false);
TxE(xa,xb,y+P(88),L(df+" "+HM(r.en),de+" "+HM(r.en)),kText,14,true);
string rem=r.active?L("تا پایان: "+DurTxt(r.left,true),"Ends in: "+DurTxt(r.left,false))
:L("تا شروع: "+DurTxt(r.left,true),"Opens in: "+DurTxt(r.left,false));
TxS(xa,xb,y+P(116),rem,r.active?Dk(c,0.35):kSub,12.5,r.active);
int by=y+h-P(15), bh=P(6), bw=xb-xa;
FillRRc(xa,by,bw,bh,bh/2,A(kTrack));
if(r.active)
{
int fw=(int)MathRound(bw*r.prog);
if(fw>0) FillRRc(g_fa?xb-fw:xa,by,fw,bh,bh/2,A(c));
}
}
void DrawCards(int x0,int w,int y)
{
Title(x0+P(4),x0+w-P(4),y+P(16),L("سشن‌های اصلی فارکس","Main Forex Sessions"));
TxS(x0+P(4),x0+w-P(4),y+P(40),L("جزئیات سشن‌ها به وقت "+TzShort(g_tz,true)+" نمایش داده می‌شود.","Session details are shown in "+TzShort(g_tz,false)+" time."),kSub,13.5,false);
int gap=P(10), cw=(w-gap)/2, ch=P(CARD_H);
int idx=0;
for(int s=0;s<4;s++)
{
if(!g_s[s].on) continue;
int col=idx%2, row=idx/2;
int x=((col==0)==g_fa)?x0+w-cw:x0;
SessCard(s,x,y+P(56)+row*(ch+gap),cw,ch);
idx++;
}
}
void DrawInfo(int x0,int w,int y)
{
int gap=P(10), cw=(w-gap)/2, ch=P(64);
// next session
{
int x=g_fa?x0+w-cw:x0;
Card(x,y,cw,ch,P(12),kCard,kBorder,1,3);
int ia=x+P(14), ib=x+cw-P(14);
int bs=-1; long bt=0;
for(int s=0;s<4;s++)
{
if(!g_s[s].on) continue;
long st[5],en[5]; Inst(s,g_u,st,en);
for(int i=0;i<5;i++) if(st[i]>g_u && (bs<0||st[i]<bt)){ bs=s; bt=st[i]; }
}
TxS(ia,ib,y+P(14),L("سشن بعدی","Next session"),kSub,13,false);
if(bs>=0)
{
int off=P(16);
Disc(g_fa?ib-P(5):ia+P(5),y+P(34),P(5),g_s[bs].clr);
TxS(g_fa?ia:ia+off,g_fa?ib-off:ib,y+P(34),L("سشن "+g_s[bs].fa,g_s[bs].en+" session"),kText,15.5,true);
TxS(ia,ib,y+P(53),L("شروع "+HM(bt)+" ("+DurTxt(bt-g_u,true)+" دیگر)",HM(bt)+" (in "+DurTxt(bt-g_u,false)+")"),kSub,12.5,false);
}
}
// next / current overlap
{
int x=g_fa?x0:x0+w-cw;
Card(x,y,cw,ch,P(12),kCard,kBorder,1,3);
int ia=x+P(14), ib=x+cw-P(14);
int a,b; long lo,hi; bool now;
TxS(ia,ib,y+P(14),L("هم‌پوشانی (جاری / بعدی)","Overlap (current / next)"),kSub,13,false);
if(NextOvAny(a,b,lo,hi,now))
{
string df,de; DayW(lo,df,de);
TxS(ia,ib,y+P(34),L(g_s[a].fa+" + "+g_s[b].fa,g_s[a].en+" + "+g_s[b].en),now?kGreen:kText,15.5,true);
TxS(ia,ib,y+P(53),L(df+" "+HM(lo)+" – "+HM(hi),de+" "+HM(lo)+" – "+HM(hi)),kSub,12.5,false);
}
else TxS(ia,ib,y+P(38),"—",kSub,15,true);
}
}
//--------------------------- overlaps table -------------------------
void DrawOverlaps(int x0,int w,int y)
{
Title(x0+P(4),x0+w-P(4),y+P(16),L("هم‌پوشانی سشن‌ها","Session overlaps"));
TxS(x0+P(4),x0+w-P(4),y+P(40),L("بازه‌ی هم‌زمانی بازبودن هر دو سشن (به وقت "+TzShort(g_tz,true)+")","Both sessions open at once ("+TzShort(g_tz,false)+" time)"),kSub,13.5,false);
int ry=y+P(56);
for(int q=0;q<g_np;q++)
{
int a=g_pa[q], b=g_pb[q];
long lo,hi; bool now;
bool ok=PairNext(a,b,lo,hi,now);
color pc=Mix(g_s[a].clr,g_s[b].clr,0.5);
int rh=P(48);
Card(x0,ry,w,rh,P(11),now?Mix(pc,kCard,0.88):kCard,now?pc:kBorder,now?P(2):1,now?5:3,now?pc:clrNONE);
int ia=x0+P(14), ib=x0+w-P(14);
int dxa=g_fa?ib-P(5):ia+P(5), dir=g_fa?-1:1;
Disc(dxa,ry+P(16),P(5),g_s[a].clr);
Disc(dxa+dir*P(13),ry+P(16),P(5),g_s[b].clr);
int off=P(36);
TxS(g_fa?ia:ia+off,g_fa?ib-off:ib,ry+P(16),L(g_s[a].fa+" + "+g_s[b].fa,g_s[a].en+" + "+g_s[b].en),kText,14,true);
string ps=now?L("فعال","Active"):(ok?L("بعدی","Next"):"—");
int pw=TW(ps,11.5,true)+P(20), ph=P(22);
int pxx=g_fa?ia:ib-pw;
RR(pxx,ry+P(16)-ph/2,pw,ph,ph/2,now?Mix(kGreen,kCard,0.8):kTrack,now?Mix(kGreen,kCard,0.5):kBorder);
Tx(pxx+pw/2,ry+P(16),ps,now?Dk(kGreen,0.3):kSub,11.5,true,TA_CENTER);
if(ok)
{
string df,de; DayW(lo,df,de);
TxS(ia,ib,ry+P(36),L(df+" "+HM(lo)+" – "+HM(hi)+" ("+DurShort(hi-lo,true)+")",de+" "+HM(lo)+" – "+HM(hi)+" ("+DurShort(hi-lo,false)+")"),
now?Dk(pc,0.35):kSub,12.5,now);
}
else TxS(ia,ib,ry+P(36),L("هم‌پوشانی ندارد","No overlap"),kSub,13.5,false);
ry+=P(ROW_OV);
}
}
//--------------------------- alert bar ------------------------------
void DrawAlertBar(int x0,int w,int y)
{
int h=P(44);
Card(x0,y,w,h,P(12),kCard,kBorder,1,3);
int ia=x0+P(14), ib=x0+w-P(14), cyy=y+h/2;
color dc=InpAlerts?kGreen:kSub;
Disc(g_fa?ib-P(5):ia+P(5),cyy,P(5),dc);
TxS(g_fa?ia:ia+P(18),g_fa?ib-P(18):ib,cyy,L("هشدار سشن‌ها","Session alerts"),kText,14,true);
string st;
if(!InpAlerts) st=L("خاموش","Off");
else if(InpAlertPre>0) st=L("فعال ("+IntegerToString(InpAlertPre)+" دقیقه قبل)","On ("+IntegerToString(InpAlertPre)+" min before)");
else st=L("فعال","On");
TxE(ia,ib,cyy,st,dc,13,true);
}
//--------------------------- 24h timeline ---------------------------
void HBar(int x0,int x1,int y,int h,double f0,double f1,color c)
{
int a=x0+(int)MathRound((x1-x0)*f0), b=x0+(int)MathRound((x1-x0)*f1);
if(b-a<2) b=a+2;
FillRRc(a,y,b-a,h,MathMin(h/2,P(5)),A(c));
}
void DrawTimeline(int y)
{
int x0=P(12), w=g_W-P(24), n=OnCount(), np=NPairs();
int ch=P(HTL());
Card(x0,y,w,ch,P(14),kCard,kBorder,1,4);
int ia=x0+P(18), ib=x0+w-P(18);
Title(ia,ib,y+P(26),L("نقشه‌ی ۲۴ ساعته‌ی بازار و هم‌پوشانی سشن‌ها","24-hour market map & session overlaps"));
long l=g_refNow%86400;
string hm=StringFormat("%02d:%02d",(int)(l/3600),(int)((l%3600)/60));
string bt=L("اکنون "+hm+" ("+TzShort(g_tz,true)+")","Now "+hm+" ("+TzShort(g_tz,false)+")");
int bw=TW(bt,12.5,true)+P(22), bh=P(26);
int bx=g_fa?ia:ib-bw;
RR(bx,y+P(26)-bh/2,bw,bh,bh/2,Mix(kNavy,kCard,0.88),Mix(kNavy,kCard,0.65));
Tx(bx+bw/2,y+P(26),bt,kNavy,12.5,true,TA_CENTER);
int nameW=P(220);
int bx0=g_fa?ia:ia+nameW, bx1=g_fa?ib-nameW:ib;
int rowH=P(26);
int ry=y+P(56);
int sep=(np>0)?P(8):0;
int gridBottom=ry+(n+np)*rowH+sep;
for(int h=0;h<=24;h+=2)
{
int gx=bx0+(int)MathRound((bx1-bx0)*h/24.0);
g_cv.Line(gx,ry-P(4),gx,gridBottom-P(2),A(kBorder));
FS(g_fEn,P(12.5),FW_NORMAL);
g_cv.TextOut(gx,gridBottom+P(14),Dig(StringFormat("%02d",h)),A(kMid),TA_CENTER|TA_VCENTER);
}
int idx=0;
for(int s=0;s<4;s++)
{
if(!g_s[s].on) continue;
int yy=ry+idx*rowH;
SessSt st=g_st[s];
Disc(g_fa?ib-P(5):ia+P(5),yy+P(10),P(5),g_s[s].clr);
TxS(g_fa?ia:ia+P(18),g_fa?ib-P(18):ib,yy+P(10),g_fa?Pre(g_s[s].fa,true):g_s[s].en,kText,14,st.active);
FillRRc(bx0,yy+P(3),bx1-bx0,P(14),P(5),A(kTrack));
long a0[5],a1[5]; int k=ClipDay(s,a0,a1);
for(int i=0;i<k;i++) HBar(bx0,bx1,yy+P(3),P(14),a0[i]/86400.0,a1[i]/86400.0,SC(s,st.active));
idx++;
}
if(np>0)
{
int sy=ry+n*rowH+P(3);
g_cv.Line(ia,sy,ib,sy,A(kBorder));
int j=0;
for(int q=0;q<g_np;q++)
{
int a=g_pa[q], b=g_pb[q];
int yy=ry+n*rowH+sep+j*rowH;
color pc=Mix(g_s[a].clr,g_s[b].clr,0.5);
long x0s[25],x1s[25]; int k=PairClip(a,b,x0s,x1s);
long lo,hi; bool now; PairNext(a,b,lo,hi,now);
int dxa=g_fa?ib-P(5):ia+P(5), dir=g_fa?-1:1;
Disc(dxa,yy+P(10),P(4),g_s[a].clr);
Disc(dxa+dir*P(11),yy+P(10),P(4),g_s[b].clr);
int off=P(30);
TxS(g_fa?ia:ia+off,g_fa?ib-off:ib,yy+P(10),L(g_s[a].fa+" + "+g_s[b].fa,g_s[a].en+" + "+g_s[b].en),now?kText:kSub,13.5,now);
FillRRc(bx0,yy+P(2),bx1-bx0,P(18),P(5),A(kTrack));
color ca=now?g_s[a].clr:Mix(g_s[a].clr,kCard,g_dark?0.28:0.5);
color cb=now?g_s[b].clr:Mix(g_s[b].clr,kCard,g_dark?0.28:0.5);
for(int i=0;i<k;i++)
{
HBar(bx0,bx1,yy+P(3),P(8),x0s[i]/86400.0,x1s[i]/86400.0,ca); // upper stripe = first session
HBar(bx0,bx1,yy+P(11),P(8),x0s[i]/86400.0,x1s[i]/86400.0,cb); // lower stripe = second session
}
j++;
}
}
int nx=bx0+(int)MathRound((bx1-bx0)*(double)l/86400.0);
g_cv.Line(nx,ry-P(4),nx,gridBottom-P(2),A(kHand));
g_cv.Line(nx+1,ry-P(4),nx+1,gridBottom-P(2),A(kHand));
Disc(nx,ry-P(5),P(4),kHand);
}
//+------------------------------------------------------------------+
//| Main redraw (once per minute / on click) |
//+------------------------------------------------------------------+
// keep the seconds hand and the digital-time label on top of their slots (they move when the layout is mirrored)
void PlaceOverlays()
{
if(g_hasCk){ ObjectSetInteger(0,g_name+"_C",OBJPROP_XDISTANCE,g_px+g_ckx); ObjectSetInteger(0,g_name+"_C",OBJPROP_YDISTANCE,g_py+g_cky); }
if(g_hasDg){ ObjectSetInteger(0,g_tn,OBJPROP_XDISTANCE,g_px+g_dgx); ObjectSetInteger(0,g_tn,OBJPROP_YDISTANCE,g_py+g_dgy); }
}
void FullRedraw()
{
datetime u=TimeGMT();
g_u=(long)u;
g_refOff=ZoneOff(g_tz,u);
g_refNow=g_u+g_refOff;
g_refMid=g_refNow-(g_refNow%86400);
g_wk=(InpWeekend && WkClosed(u));
RefreshStates();
SortPairs();
g_ckw=0; g_dgw=0;
g_cv.Erase(0);
RR(0,0,g_W,g_H,P(18),kBg,kBorder);
int y=P(12);
if(g_sh[SH_HDR]){ DrawHeader(y); y+=P(H_HDR+10); }
int bodyH=BodyH();
if(bodyH>0)
{
int x=g_colX0;
if(HasA())
{
int px=MX(x,W_A), yy=y;
Card(px,y,P(W_A),P((g_sh[SH_CLK]?H_CLK:0)+(g_sh[SH_DIG]?H_DIG:0)),P(16),kCard,kBorder,1,4);
if(g_sh[SH_CLK]){ DrawClock(px,P(W_A),yy); yy+=P(H_CLK); }
if(g_sh[SH_DIG]) DrawDigital(px,P(W_A),yy);
x+=W_A+G_GAP;
}
if(HasB())
{
int px=MX(x,W_B), yy=y;
if(g_sh[SH_CRD]){ DrawCards(px,P(W_B),yy); yy+=P(HCards()); }
if(g_sh[SH_INF]) DrawInfo(px,P(W_B),yy);
x+=W_B+G_GAP;
}
if(HasC())
{
int px=MX(x,W_C), yy=y;
if(g_sh[SH_OVL]){ DrawOverlaps(px,P(W_C),yy); yy+=P(HOvl()); }
if(g_sh[SH_ALR]) DrawAlertBar(px,P(W_C),yy);
}
y+=P(bodyH+10);
}
if(g_sh[SH_TL]) DrawTimeline(y);
g_cv.Update(false);
PlaceOverlays();
}
// light per-second update: digital time label (+ seconds hand)
void LiveDraw()
{
g_u=(long)TimeGMT();
g_refNow=g_u+g_refOff;
if(g_hasCk)
{
g_ck.Erase(0);
int c=g_ckw/2;
double sa=(double)(g_refNow%60)/60.0*2*M_PI;
g_ck.Line((int)MathRound(c-P(12)*MathSin(sa)),(int)MathRound(c+P(12)*MathCos(sa)),
(int)MathRound(c+P(98)*MathSin(sa)),(int)MathRound(c-P(98)*MathCos(sa)),A(C'239,68,68'));
g_ck.FillCircle(c,c,P(3.5),A(C'239,68,68'));
g_ck.Update(false);
}
if(g_hasDg)
{
long l=g_refNow%86400;
ObjectSetString(0,g_tn,OBJPROP_TEXT,Dig(StringFormat("%02d:%02d:%02d",(int)(l/3600),(int)((l%3600)/60),(int)(l%60))));
}
ChartRedraw();
}
//+------------------------------------------------------------------+
//| Alerts |
//+------------------------------------------------------------------+
void Notify(int s,int type)
{
string m;
if(g_fa)
{
if(type==0) m="سشن "+g_s[s].fa+" باز شد";
else if(type==1) m="سشن "+g_s[s].fa+" بسته شد";
else m=IntegerToString(InpAlertPre)+" دقیقه تا شروع سشن "+g_s[s].fa;
}
else
{
if(type==0) m=g_s[s].en+" session opened";
else if(type==1) m=g_s[s].en+" session closed";
else m=g_s[s].en+" session opens in "+IntegerToString(InpAlertPre)+" min";
}
if(InpPopup) Alert(m);
if(InpSound && InpSoundFile!="") PlaySound(InpSoundFile);
if(InpPush) SendNotification(m);
if(InpMail) SendMail("Forex Sessions",m);
}
void CheckAlerts(datetime u)
{
if(!InpAlerts || g_last==0){ g_last=u; return; }
long a=(long)g_last, b=(long)u;
for(int s=0;s<4;s++)
{
if(!g_s[s].on) continue;
long st[5],en[5]; Inst(s,b,st,en);
for(int i=0;i<5;i++)
{
if(InpWeekend && WkClosed((datetime)st[i])) continue;
if(InpAlertOpen && st[i]>a && st[i]<=b) Notify(s,0);
if(InpAlertClose && en[i]>a && en[i]<=b) Notify(s,1);
if(InpAlertPre>0){ long t=st[i]-(long)InpAlertPre*60; if(t>a && t<=b) Notify(s,2); }
}
}
g_last=u;
}
//+------------------------------------------------------------------+
//| Setup helpers |
//+------------------------------------------------------------------+
void SetupTheme()
{
if(g_dark)
{
kBg=C'15,18,28'; kCard=C'30,36,54'; kBorder=C'56,66,92'; kText=C'236,240,248';
kSub=C'160,171,194'; kTrack=C'38,45,64'; kNavy=C'96,125,255'; kGreen=C'52,211,120';
kMid=C'200,208,226'; kHand=C'245,247,252'; kFace=C'22,26,40'; kAccent=C'96,125,255';
}
else
{
kBg=C'238,241,248'; kCard=C'255,255,255'; kBorder=C'208,216,232'; kText=C'24,34,58';
kSub=C'96,108,132'; kTrack=C'238,241,247'; kNavy=C'27,42,74'; kGreen=C'34,166,100';
kMid=C'48,60,88'; kHand=C'27,42,74'; kFace=C'247,249,253'; kAccent=C'37,99,235';
}
}
void ApplyLang()
{
g_pd=(InpDigits==DIG_PERSIAN)||(InpDigits==DIG_AUTO && g_fa);
}
void PopupClose()
{
if(!g_popOpen) return;
g_pp.Destroy();
g_popOpen=false;
ChartRedraw();
}
void Persist()
{
if(InpRemember){ GlobalVariableSet(GV_LANG,g_fa?0:1); GlobalVariableSet(GV_TZ,g_tz); GlobalVariableSet(GV_THEME,g_dark?1:0); }
}
// time-zone drop-down list (combo box)
void PopupOpen()
{
int rh=P(36), pad=P(4);
g_ppw=MathMax(g_hit[0][2],P(360));
g_pph=TZ_COUNT*rh+2*pad;
g_ppx=g_px+g_hit[0][0];
g_ppy=g_py+g_hit[0][1]+g_hit[0][3]+P(2);
int cw=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS), chh=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
if(g_ppx+g_ppw>cw) g_ppx=MathMax(0,cw-g_ppw);
if(g_ppy+g_pph>chh) g_ppy=MathMax(0,chh-g_pph);
if(!g_pp.CreateBitmapLabel(0,0,g_name+"_P",g_ppx,g_ppy,g_ppw,g_pph,COLOR_FORMAT_ARGB_NORMALIZE)) return;
ObjectSetInteger(0,g_name+"_P",OBJPROP_SELECTABLE,false);
ObjectSetInteger(0,g_name+"_P",OBJPROP_BACK,false);
ObjectSetInteger(0,g_name+"_P",OBJPROP_ZORDER,200);
g_popOpen=true;
g_pp.Erase(0);
g_pp.FillRectangle(0,0,g_ppw-1,g_pph-1,A(kNavy));
g_pp.FillRectangle(2,2,g_ppw-3,g_pph-3,A(kCard));
datetime u=TimeGMT();
for(int i=0;i<TZ_COUNT;i++)
{
int ry=pad+i*rh;
if(i==g_tz) g_pp.FillRectangle(3,ry,g_ppw-4,ry+rh-1,A(Mix(kNavy,kCard,0.86)));
g_pp.FontSet(g_fa?g_fFa:g_fEn,P(15),(i==g_tz)?FW_BOLD:FW_NORMAL);
g_pp.TextOut(g_fa?g_ppw-P(16):P(16),ry+rh/2,Pre(TzLong(i,g_fa),g_fa),A(kText),(g_fa?TA_RIGHT:TA_LEFT)|TA_VCENTER);
g_pp.FontSet(g_fEn,P(13),FW_NORMAL);
g_pp.TextOut(g_fa?P(16):g_ppw-P(16),ry+rh/2,UtcStr(ZoneOff(i,u)),A(kSub),(g_fa?TA_LEFT:TA_RIGHT)|TA_VCENTER);
}
g_pp.Update();
}
void PopupClick(int y)
{
int rh=P(36), pad=P(4);
int row=(y-pad)/rh;
if(y<pad || row<0 || row>=TZ_COUNT) return;
g_tz=row;
Persist();
PopupClose();
FullRedraw();
LiveDraw();
}
void Place()
{
int cw=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
int chh=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
switch(InpCorner)
{
case PC_TL: g_px=InpX; g_py=InpY; break;
case PC_TR: g_px=cw-g_W-InpX; g_py=InpY; break;
case PC_BL: g_px=InpX; g_py=chh-g_H-InpY; break;
default: g_px=cw-g_W-InpX; g_py=chh-g_H-InpY; break;
}
if(g_px<0) g_px=0;
if(g_py<0) g_py=0;
ObjectSetInteger(0,g_name,OBJPROP_XDISTANCE,g_px);
ObjectSetInteger(0,g_name,OBJPROP_YDISTANCE,g_py);
if(g_hasCk){ ObjectSetInteger(0,g_name+"_C",OBJPROP_XDISTANCE,g_px+g_ckx); ObjectSetInteger(0,g_name+"_C",OBJPROP_YDISTANCE,g_py+g_cky); }
if(g_hasDg){ ObjectSetInteger(0,g_tn,OBJPROP_XDISTANCE,g_px+g_dgx); ObjectSetInteger(0,g_tn,OBJPROP_YDISTANCE,g_py+g_dgy); }
}
// choose scale / visible sections so the panel fits the chart
int Fit()
{
double req=MathMax(0.5,MathMin(2.0,InpScale/100.0));
g_sh[SH_HDR]=InpShowHeader; g_sh[SH_CLK]=InpShowClock; g_sh[SH_DIG]=InpShowDigital; g_sh[SH_INF]=InpShowInfo;
g_sh[SH_ALR]=InpShowAlertBar; g_sh[SH_CRD]=InpShowCards; g_sh[SH_TL]=InpShowTimeline; g_sh[SH_OVL]=InpShowOverlaps;
g_S=req;
int hb=Metrics();
if(InpAutoFit)
{
int chh=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
int cw=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
if(chh>150 && cw>150)
{
double availH=chh-InpY-6, availW=cw-InpX-6;
int drop[4]={SH_TL,SH_INF,SH_ALR,SH_OVL};
double fs=req;
for(int k=0;k<=4;k++)
{
hb=Metrics();
fs=MathMin(req,MathMin(availH/(double)hb,availW/(double)g_PWb));
if(fs>=0.6 || k==4) break;
g_sh[drop[k]]=false;
}
g_S=MathMax(0.4,fs);
}
}
int key=(int)(g_S*1000);
for(int i=0;i<8;i++) if(g_sh[i]) key+=(1<<(i+12));
return key;
}
void Build()
{
PopupClose();
g_cv.Destroy(); g_ck.Destroy(); ObjectDelete(0,g_tn);
g_hasCk=false; g_hasDg=false; g_fn=""; g_fp=-1;
int hb=Metrics();
g_W=P(g_PWb); g_H=P(hb);
if(!g_cv.CreateBitmapLabel(0,0,g_name,0,0,g_W,g_H,COLOR_FORMAT_ARGB_NORMALIZE))
{ Print("Forex Sessions Clock: cannot create canvas"); return; }
ObjectSetInteger(0,g_name,OBJPROP_SELECTABLE,false);
ObjectSetInteger(0,g_name,OBJPROP_BACK,false);
ObjectSetInteger(0,g_name,OBJPROP_ZORDER,100);
FullRedraw();
if(g_ckw>0 && g_ck.CreateBitmapLabel(0,0,g_name+"_C",0,0,g_ckw,g_ckw,COLOR_FORMAT_ARGB_NORMALIZE))
{
g_hasCk=true;
ObjectSetInteger(0,g_name+"_C",OBJPROP_SELECTABLE,false);
ObjectSetInteger(0,g_name+"_C",OBJPROP_BACK,false);
}
if(g_dgw>0 && ObjectCreate(0,g_tn,OBJ_LABEL,0,0,0))
{
g_hasDg=true;
ObjectSetInteger(0,g_tn,OBJPROP_CORNER,CORNER_LEFT_UPPER);
ObjectSetInteger(0,g_tn,OBJPROP_ANCHOR,ANCHOR_CENTER);
ObjectSetString(0,g_tn,OBJPROP_FONT,g_fEn+" Bold");
ObjectSetInteger(0,g_tn,OBJPROP_FONTSIZE,(int)MathMax(9,MathRound(34*g_S)));
ObjectSetInteger(0,g_tn,OBJPROP_COLOR,kText);
ObjectSetInteger(0,g_tn,OBJPROP_SELECTABLE,false);
ObjectSetInteger(0,g_tn,OBJPROP_HIDDEN,true);
ObjectSetInteger(0,g_tn,OBJPROP_BACK,false);
}
Place();
LiveDraw();
}
bool InRect(int idx,int x,int y)
{
return (x>=g_hit[idx][0] && x<=g_hit[idx][0]+g_hit[idx][2] && y>=g_hit[idx][1] && y<=g_hit[idx][1]+g_hit[idx][3]);
}
void HandleClick(int x,int y)
{
bool wasOpen=g_popOpen;
if(wasOpen) PopupClose();
if(!g_sh[SH_HDR]) return;
if(InRect(0,x,y)){ if(!wasOpen) PopupOpen(); return; }
if(InRect(1,x,y)) g_fa=true;
else if(InRect(2,x,y)) g_fa=false;
else if(InRect(3,x,y)){ g_dark=!g_dark; SetupTheme(); if(g_hasDg) ObjectSetInteger(0,g_tn,OBJPROP_COLOR,kText); }
else return;
ApplyLang();
Persist();
FullRedraw();
LiveDraw();
}
//+------------------------------------------------------------------+
//| Indicator events |
//+------------------------------------------------------------------+
int OnInit()
{
g_fa=(InpLang==UI_FA);
g_tz=(int)InpRefTz;
g_fix=InpFaFix;
g_fFa=InpFontFa; g_fEn=InpFontEn;
g_dark=(InpTheme==THEME_DARK);
if(InpRemember)
{
if(GlobalVariableCheck(GV_THEME)) g_dark=(GlobalVariableGet(GV_THEME)>0.5);
if(GlobalVariableCheck(GV_LANG)) g_fa=(GlobalVariableGet(GV_LANG)<0.5);
if(GlobalVariableCheck(GV_TZ)){ int t=(int)GlobalVariableGet(GV_TZ); if(t>=0&&t<TZ_COUNT) g_tz=t; }
}
ApplyLang();
SetupTheme();
SetupSessions();
SetupPairs();
ArrayInitialize(g_hit,0);
g_name="FSC_Panel_"+IntegerToString(ChartID());
g_tn=g_name+"_T";
g_built=false;
EventSetMillisecondTimer(200); // first drawing happens in OnTimer, so loading is instant
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
EventKillTimer();
g_pp.Destroy(); g_ck.Destroy(); g_cv.Destroy();
ObjectDelete(0,g_tn);
ChartRedraw();
}
void OnTimer()
{
if(!g_built)
{
EventKillTimer();
g_cw=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
g_chh=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
g_key=Fit();
Build();
g_built=true;
g_lastMin=(long)TimeGMT()/60;
EventSetTimer(1);
return;
}
if(g_refit)
{
g_refit=false;
int k=Fit();
if(k!=g_key){ g_key=k; Build(); } else Place();
ChartRedraw();
}
datetime u=TimeGMT();
long mn=(long)u/60;
if(mn!=g_lastMin){ g_lastMin=mn; FullRedraw(); }
LiveDraw();
CheckAlerts(u);
}
void Click(int ax,int ay)
{
uint t=GetTickCount();
if(t-g_lct<300) return; // same physical click delivered twice
g_lct=t;
if(g_popOpen && ax>=g_ppx && ax<g_ppx+g_ppw && ay>=g_ppy && ay<g_ppy+g_pph){ PopupClick(ay-g_ppy); return; }
if(ax>=g_px && ax<g_px+g_W && ay>=g_py && ay<g_py+g_H){ HandleClick(ax-g_px,ay-g_py); return; }
PopupClose();
}
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
if(!g_built) return;
if(id==CHARTEVENT_CHART_CHANGE)
{
int cw=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS), chh=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
if(cw!=g_cw || chh!=g_chh){ g_cw=cw; g_chh=chh; g_refit=true; } // handled later in OnTimer
return;
}
if(id==CHARTEVENT_OBJECT_CLICK)
{
if(StringFind(sparam,g_name)==0) Click((int)lparam,(int)dparam);
return;
}
if(id==CHARTEVENT_CLICK){ Click((int)lparam,(int)dparam); return; }
}
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[])
{
return rates_total;
}
//+------------------------------------------------------------------+