diff --git a/Documents/Docs/X121XReplay/X121 XReplay - Documentation.html b/Documents/Docs/X121XReplay/X121 XReplay - Documentation.html new file mode 100644 index 00000000..73b522b3 --- /dev/null +++ b/Documents/Docs/X121XReplay/X121 XReplay - Documentation.html @@ -0,0 +1,318 @@ + + + + + + X121 XReplay - سند پروژه + + + + + +
+

X121 XReplay

+
ابزار پخش مجدد کندل‌ها در متاتریدر ۵
+
TradingView-Style Candle Replay Tool for MetaTrader 5
+
+

نسخه: 1.00

+

توسعه‌دهنده: هادی خزاعی اصل

+

شرکت: فن آوران ساحر علم

+

SaherElm IT Center

+
+
+ + +
+

۱. شرح نیازمندی

+ +

۱.۱ مسئله

+

+ معامله‌گران برای تمرین و بک‌تست دستی استراتژی‌های خود به ابزاری نیاز دارند که بتواند + کندل‌های بازار را از یک نقطه زمانی مشخص پنهان کند و سپس به‌صورت تدریجی + (کندل به کندل) آن‌ها را نمایش دهد. این قابلیت در پلتفرم TradingView با نام + Bar Replay وجود دارد، اما در MetaTrader 5 به‌صورت پیش‌فرض + در دسترس نیست. +

+ +

۱.۲ نیازمندی‌های عملکردی

+ + + + + + + + +
#نیازمندیتوضیح
۱پنهان‌سازی کندل‌هاکندل‌های بعد از نقطه cutoff باید از دید کاربر پنهان شوند
۲نمایش تدریجیامکان نمایش کندل‌ها یکی‌یکی با دکمه یا کیبورد
۳پخش خودکارامکان پخش خودکار کندل‌ها با سرعت قابل تنظیم
۴بازنشانیامکان بازگشت به نقطه شروع با یک کلیک
۵تغییر تایم‌فریمحفظ وضعیت Replay هنگام تغییر تایم‌فریم چارت
۶بازیابی خودکاربازیابی خودکار حالت چارت پس از حذف اندیکاتور
+ +

۱.۳ محدودیت‌های فنی MQL5

+ +
+ + +
+

۲. معرفی ابزار

+ +

۲.۱ مشخصات کلی

+ + + + + + + + + + + +
مشخصهمقدار
نامX121 XReplay
نوعاندیکاتور (Indicator)
پلتفرمMetaTrader 5 (MQL5)
نسخه1.00
فایلx-saherelm.x121.x-reply.mq5
مسیر نصبMQL5/Indicators/
وابستگیx-saherelm.common.lib.mq5
توسعه‌دهندههادی خزاعی اصل
شرکتفن آوران ساحر علم (SaherElm IT Center)
+ +

۲.۲ روش کار

+

+ ابزار از روش DRAW_COLOR_CANDLES با ۵ بافر برای رسم کندل‌ها استفاده می‌کند. + کندل‌های واقعی چارت با تغییر استایل چارت (تبدیل رنگ‌ها به CLR_NONE) + پنهان می‌شوند و کندل‌های Replay توسط بافرهای اندیکاتور رسم می‌شوند. +

+ +

۲.۳ بافرها

+ + + + + + + +
بافرنوعکاربرد
pBarOpenBufferINDICATOR_DATAقیمت Open
pBarHighBufferINDICATOR_DATAقیمت High
pBarLowBufferINDICATOR_DATAقیمت Low
pBarCloseBufferINDICATOR_DATAقیمت Close
pBarColorBufferINDICATOR_COLOR_INDEXایندکس رنگ کندل
+ +

۲.۴ ایندکس‌های رنگ

+ + + + + + +
ایندکسنامکاربرد
0hideColorIDXکندل پنهان (CLR_NONE)
1bullishColorIDXکندل صعودی (clrDodgerBlue)
2bearishColorIDXکندل نزولی (clrDarkOrange)
3neuturalColorIDXکندل خنثی
+ +

۲.۵ پارامترهای ورودی

+ + + + + + + + +
گروهپارامترپیش‌فرضتوضیح
Replay SettingsstartBar20تعداد کندل‌های cutoff
autoPlaySpeed1000سرعت پخش خودکار (ms)
PresentationpanelButtonTextColorclrYellowرنگ متن دکمه‌ها
panelBackgroundColorclrBlackرنگ پس‌زمینه پنل
infoColorclrWhiteرنگ متن اطلاعات
infoPanelBorderColorclrBlackرنگ حاشیه پنل
+ +

۲.۶ کنترل‌ها

+ + + + + + + +
دکمهمتنعملکردکیبورد
Prev|<یک کندل به عقب← / ↓
Play>شروع پخش خودکارSpace
Pause||توقف پخشSpace
Next>|یک کندل به جلو→ / ↑
ResetRبازنشانی به نقطه شروعR
+
+ + +
+

۳. معماری فنی

+ +

۳.۱ ساختار فایل

+ + + + + + + + + + + + + + +
بخشمحتوا
#region Importsکتابخانه‌های مورد نیاز
#region Definitionsثابت‌ها، ساختار XChartStyle، ایندکس‌های رنگ
#region Inputsپارامترهای ورودی کاربر
#region Buffersتعریف ۵ بافر و پلات
#region Variablesمتغیرهای سراسری
#region Event HandlersOnInit، OnDeinit، OnCalculate، OnTimer، OnChartEvent
#region Custm Functionsتوابع کمکی و اعتبارسنجی
#region Value Calculatorsمحاسبه مقادیر بافرها
#region Chart Styleمدیریت استایل چارت
#region UIپنل کنترل و دکمه‌ها
#region Control Actionsاکشن‌های پخش
#region TimeFrame Changeمدیریت تغییر تایم‌فریم
+ +

۳.۲ ساختار XChartStyle

+

این ساختار ۲۱ فیلد دارد و برای ذخیره و بازیابی کامل حالت چارت استفاده می‌شود:

+ + + + + +
دستهفیلدها
شناساییchartId, mode
نمایشshowBidLine, showAskLine, showGrid, showVolumes, showTradeLevels, autoScroll, quickNavigation
رنگ‌هاforeGroundColor, backGroundColor, upColor, downColor, bullishColor, bearishColor, gridColor, bidLineColor, askLineColor, lineColor, stopColor, volumesColor
+ +

۳.۳ مکانیزم پنهان‌سازی

+
+
    +
  1. ReadChartStyle: خواندن حالت فعلی چارت و ذخیره در xReplyChartStyle
  2. +
  3. SaveChartStyleToFile: ذخیره در فایل باینری با FileWriteStruct
  4. +
  5. ساخت clearStyle: کپی از حالت اصلی با رنگ‌های CLR_NONE
  6. +
  7. HideBars: اعمال clearStyle بر چارت → کندل‌های واقعی نامرئی می‌شوند
  8. +
+
+ +

۳.۴ مکانیزم رسم کندل‌های Replay

+
+
    +
  1. OnCalculate: پر کردن بافرهای OHLC از داده‌های واقعی
  2. +
  3. UpdateVisibleColors: تنظیم رنگ بافر بر اساس وضعیت Replay
  4. +
  5. کندل‌های قبل از cutoff → رنگ عادی (صعودی/نزولی)
  6. +
  7. کندل‌های بعد از cutoff → hideColorIDX (پنهان)
  8. +
  9. کندل‌های قابل نمایش Replay → رنگ عادی (صعودی/نزولی)
  10. +
+
+ +

۳.۵ مدیریت تغییر تایم‌فریم

+
+

هنگام تغییر تایم‌فریم (OnDeinit با REASON_CHARTCHANGE):

+
    +
  1. محاسبه ایندکس آخرین کندل نمایش داده شده
  2. +
  3. ذخیره زمان آن کندل در Global Variable
  4. +
  5. ذخیره وضعیت فعال بودن در Global Variable
  6. +
  7. ذخیره تعداد کندل‌های قابل نمایش در Global Variable
  8. +
+

در تایم‌فریم جدید (OnInit):

+
    +
  1. بررسی وجود Global Variables
  2. +
  3. یافتن اولین کندل در تایم‌فریم جدید که شامل زمان ذخیره‌شده است
  4. +
  5. تنظیم xReplyStartBarIndex بر اساس کندل یافت‌شده
  6. +
  7. بازیابی تعداد کندل‌های قابل نمایش
  8. +
  9. حذف Global Variables
  10. +
+
+
+ + +
+

۴. فرآیند و مراحل انجام کار

+ +

۴.۱ خلاصه مراحل توسعه

+ + + + + + + + + + + +
مرحلهشرحنتیجه
۱تحلیل نیازمندی و بررسی محدودیت‌های MQL5شناسایی روش پنهان‌سازی
۲طراحی معماری با DRAW_COLOR_CANDLESتعریف ۵ بافر و ۴ رنگ
۳پیاده‌سازی مدیریت Chart Styleساختار XChartStyle با ۲۱ فیلد
۴پیاده‌سازی ذخیره/بازیابی فایل باینریFileWriteStruct / FileReadStruct
۵پیاده‌سازی پنل UI و کنترل‌ها۵ دکمه + برچسب اطلاعات
۶پیاده‌سازی میانبرهای کیبوردArrow Keys + Space + R
۷پیاده‌سازی تایمر و پخش خودکارStartTimer / StopTimer
۸پیاده‌سازی مدیریت تغییر تایم‌فریمGlobal Variables
۹بهینه‌سازی و تست نهایینسخه 1.00
+ +

۴.۲ تصمیمات فنی کلیدی

+ + + + + + + +
تصمیمدلیل
استفاده از DRAW_COLOR_CANDLESکارآمدترین روش رسم کندل در MQL5 بدون ایجاد اشیاء گرافیکی
پنهان‌سازی با CLR_NONEتنها روش ممکن برای نامرئی کردن کندل‌های واقعی بدون حذف آن‌ها
ذخیره Chart Style در فایل باینریبازیابی خودکار پس از حذف اندیکاتور یا کرش ترمینال
Global Variables برای تایم‌فریمحفظ وضعیت بین OnDeinit و OnInit هنگام تغییر تایم‌فریم
تایمر فقط در حالت Playبهینه‌سازی مصرف CPU در حالت Pause
+ +

۴.۳ ساختار کدنویسی

+

کد بر اساس دستخط کدنویسی شرکت فن آوران ساحر علم نوشته شده است:

+ +
+ + +
+

۵. راهنمای استفاده

+ +

۵.۱ نصب

+
    +
  1. فایل x-saherelm.x121.x-reply.mq5 را در مسیر MQL5/Indicators/ قرار دهید
  2. +
  3. در MetaEditor با F7 کامپایل کنید
  4. +
  5. اندیکاتور را از Navigator روی چارت بکشید
  6. +
  7. پارامتر startBar را تنظیم کنید (تعداد کندل‌های cutoff)
  8. +
+ +

۵.۲ استفاده

+ + +

۵.۳ حذف

+

+ با حذف اندیکاتور از چارت، حالت چارت به‌طور خودکار بازیابی می‌شود، + فایل Chart Style حذف می‌شود و اشیاء UI پاک می‌شوند. +

+ +
+ ✅ نکته: در صورت کرش ترمینال، فایل Chart Style باقی می‌ماند و در اجرای بعدی + به‌جای خواندن از چارت، از فایل خوانده می‌شود. این تضمین می‌کند که حالت چارت همیشه درست بازیابی شود. +
+
+ + + + + + \ No newline at end of file diff --git a/Documents/Docs/X121XReplay/X121 XReplay - Documentation.pdf b/Documents/Docs/X121XReplay/X121 XReplay - Documentation.pdf new file mode 100644 index 00000000..dab7ffea Binary files /dev/null and b/Documents/Docs/X121XReplay/X121 XReplay - Documentation.pdf differ diff --git a/Documents/Docs/X121XReplay/x-saherelm.x121.x-reply.mq5 b/Documents/Docs/X121XReplay/x-saherelm.x121.x-reply.mq5 new file mode 100644 index 00000000..27681cd9 --- /dev/null +++ b/Documents/Docs/X121XReplay/x-saherelm.x121.x-reply.mq5 @@ -0,0 +1,1550 @@ +/////////////////////////////////////////////////////// +// +// SaherElm IT Center MQL5 Indicator +// --------------------------------------------------- +// Name: X121 XReplay +// Description: SaherElm Replay Tool ... +// +// +// Maintainer: +// ------------ +// Hadi Khazaee Asl (hadi_khazaee_asl@yahoo.com) +// +////////////////////////////////////////////////////// +// +// Global Properties ... +#property copyright "Copyright 2023, SaherElm IT Center" +#property link "https://saherelm.ir" +#property version "1.00" +#property description "SaherElm X121 XReplay Indicator" +#property icon "../Images/SaherElm_Logo_Color.ico" +#property strict + +// #region Imports ... +#include "../Libraries/x-saherelm.common.lib.mq5" +// #endregion + +// #region Definitions ... + +// +#define ShortName "X121-XReplay" + +// +// Global Variable Keys for TimeFrame Change ... +#define GV_ACTIVE_SUFFIX "_Active" +#define GV_TIME_SUFFIX "_LastVisibleTime" +#define GV_VISIBLE_BARS_SUFFIX "_VisibleBars" + +// +#define hideColorIDX 0 +#define bullishColorIDX 1 +#define bearishColorIDX 2 +#define neuturalColorIDX 3 + +// +#define bullishState 1 +#define neuturalState 0 +#define bearishState -1 + +// +#define emptyValue 0.0 + +// +// an strucutre to save Chart Style ... +struct XChartStyle +{ + long chartId; // chart's ID ... + ENUM_CHART_MODE mode; // chart's mode ... + bool showBidLine; // show bid line ... + bool showAskLine; // show ask line ... + bool showGrid; // show grids on chart ... + bool showVolumes; // show volumes ... + bool showTradeLevels; // Displaying trade levels in the chart (levels of open positions, Stop Loss, Take Profit and pending orders) ... + bool autoScroll; // chart autoscroll ... + bool quickNavigation; // chart quick navigation state ... + color foreGroundColor; // chart's foreground color ... + color backGroundColor; // chart's background color ... + color upColor; // Up Color ... + color downColor; // Down Color ... + color bullishColor; // Bullish color ... + color bearishColor; // Bearish color ... + color gridColor; // grid color ... + color bidLineColor; // bid line color ... + color askLineColor; // ask line color ... + color lineColor; // line mode and doji candlestick color ... + color stopColor; // Color of stop order levels (Stop Loss and Take Profit) ... + color volumesColor; // volumes color ... +}; +// #endregion + +// #region Inputs ... +input group "Replay Settings"; +input int startBar = 20; // Cutof Bars +input int autoPlaySpeed = 1000; // Auto-Play Speed (ms) + +input group "Presentation"; +input color panelButtonTextColor = clrYellow; // Button Text Color +input color panelBackgroundColor = clrBlack; // Panel Background +input color infoColor = clrWhite; // Info Color +input color infoPanelBorderColor = clrBlack; // Info Panel Border Color +// #endregion + +// #region Buffers ... +#define buffersCount 0 +#property indicator_chart_window +#property indicator_buffers 5 +#property indicator_plots 1 + +// +// Played Bar ... + +// +#define pBarOpenBufferIndex buffersCount +double pBarOpenBuffer[]; + +// +#define pBarHighBufferIndex buffersCount + 1 +double pBarHighBuffer[]; + +// +#define pBarLowBufferIndex buffersCount + 2 +double pBarLowBuffer[]; + +// +#define pBarCloseBufferIndex buffersCount + 3 +double pBarCloseBuffer[]; + +// +#define pBarColorBufferIndex buffersCount + 4 +double pBarColorBuffer[]; + +// +#define pBarBufferPlotIndex 0 +#property indicator_label1 "XPBar Open;XPBar High;XPBar Low;XPBar Close" +#property indicator_type1 DRAW_COLOR_CANDLES +#property indicator_color1 CLR_NONE, clrDodgerBlue, clrDarkOrange +// #endregion + +// #region Variables ... + +// +int xReplyLimit; +int xReplyMaxLength; +int xReplyFirstBarIndex; + +// +XChartStyle xReplyChartStyle; +XChartStyle xReplyClearStyle; +string xReplyChartStyleFileName = "XChartStyle_" + ShortName + "_" + _Symbol + "_" + ToXString(_Period) + ".bin"; + +// +// UI Object Names ... +string xReplyBtnPlay; +string xReplyBtnPrev; +string xReplyBtnNext; +string xReplyLblInfo; +string xReplyBtnPause; +string xReplyBtnReset; + +// +// Replay State ... +int xReplyCandlesCount = 0; +int xReplyStartBarIndex = 0; +int xReplyCurrentBarIndex = 0; +bool xReplyIsPlaying = false; +ulong xReplyLastPlayTick = 0; +datetime xReplyStartTime = NULL; +int xReplyCurrentVisibleBars = 0; + +// +bool xReplyTimerActive = false; // Timer is active only when playing +int xReplyLastDrawnBarCount = 0; // Track last drawn bar count for incremental draw +bool xReplyIsResumedFromTFChange = false; // Flag: resumed from timeframe change +// #endregion + +// #region Event Handlers ... +/** + * Initialize Indicator ... + * + * @return ( int ) + */ +int OnInit() +{ + // + bool has = false; + + // + // Validate Inputs ... + has = ValidateInputs(); + if (!has) + { + return INIT_PARAMETERS_INCORRECT; + } + + // + // because in some cases we may have more than one input for + // calculation and we must prevent any calculation + // untill we pass the biggest input length, here we get max Input length + // and then wait until pass it ... + xReplyMaxLength = ExtractxReplyMaxLengthOfInputs(); + + // + // Initial Requirements ... + has = InitRequirements(); + if (!has) + { + return INIT_FAILED; + } + + // + // Define Index Buffers ... + DefineBuffers(); + + // + // Set Indicator ShortName ... + SetIndicatorName(); + + // + // Apply Chart Style ... + ApplyCustomChartStyle(); + + // + xReplyIsResumedFromTFChange = TryResumeFromTimeFrameChange(); + if (!xReplyIsResumedFromTFChange) + { + // + int idx = startBar; + if (idx >= Bars(_Symbol, _Period)) + { + idx = Bars(_Symbol, _Period) - 10; + } + + // + xReplyStartTime = iTime(_Symbol, _Period, idx); + xReplyStartBarIndex = iBarShift(_Symbol, _Period, xReplyStartTime, false); + has = xReplyStartBarIndex >= 0; + if (!has) + { + return INIT_PARAMETERS_INCORRECT; + } + + // + xReplyCurrentVisibleBars = 1; + xReplyCurrentBarIndex = xReplyStartBarIndex; + } + else + { + // + // Validate Resumed State ... + if (xReplyStartBarIndex < 0) + { + return INIT_PARAMETERS_INCORRECT; + } + } + + // + // Create UI Panel ... + CreatePanel(); + + // + // Initial Draw ... + DrawVisibleCandles(); + UpdateInfoLabel(); + + // + // Init Succeed ... + return INIT_SUCCEEDED; + + // +} + +/** + * De Initialize Indicator ... + * + * @param reason: Integer, De Initialization Reason ... + * - REASON_PROGRAM 0 The EA has stopped working calling the ExpertRemove() function + * - REASON_REMOVE 1 Program removed from a chart + * - REASON_RECOMPILE 2 Program recompiled + * - REASON_CHARTCHANGE 3 A symbol or a chart period is changed + * - REASON_CHARTCLOSE 4 Chart closed + * - REASON_PARAMETERS 5 Inputs changed by a user + * - REASON_ACCOUNT 6 Another account has been activated or reconnection to the trade server has occurred due to changes in the account settings + * - REASON_TEMPLATE 7 Another chart template applied + * - REASON_INITFAILED 8 The OnInit() handler returned a non-zero value + * - REASON_CLOSE 9 Terminal closed + */ +void OnDeinit(const int reason) +{ + if (reason == REASON_CHARTCHANGE) + { + // + // Save Global Variables ... + SaveReplayStateForTimeFrameChange(); + } + else + { + // + // Remove Global Variables for Other Reasons ... + RemoveReplayGlobalVariables(); + } + + // + // Kill Timer if it's Activated ... + if (xReplyTimerActive) + { + StopTimer(); + } + + // + // Show Saved Chart Style ... + ShowBars(); + + // + // Remove Chart Style File ... + FileDelete(xReplyChartStyleFileName); + + // + // Remove UI Objects ... + RemoveUIObjects(); +} + +/** + * Calculate Bars ... + * + * @param rates_total: Integer, Total Bars on Chart ... + * @param prev_calculated: Integer, Total Calculated Bars on Charts ... + * @param time: DateTime Array, History of Open Time ... + * @param open: Double Array, History of Open Prices ... + * @param high: Double Array, History of High Prices ... + * @param low: Double Array, History of Low Prices ... + * @param close: Double Array, History of Close Prices ... + * @param tick_volume: Long, History of Tick Volumes on Bar ... + * @param volume: Long, History of Trade Volumes ... + * @param spread: Double, History of Spread Price ... + * + * @return ( int ) + */ +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[] // +) +{ + // + // Prepare Buffers ... + ArraySetAsSeries(time, true); + ArraySetAsSeries(open, true); + ArraySetAsSeries(high, true); + ArraySetAsSeries(low, true); + ArraySetAsSeries(close, true); + ArraySetAsSeries(tick_volume, true); + ArraySetAsSeries(volume, true); + ArraySetAsSeries(spread, true); + + // + // Calculate Size of Loop ... + xReplyLimit = (prev_calculated > rates_total || prev_calculated <= 0) + // + // First Call ... + ? rates_total - 1 + // + // Subsequent calls: only new bars + current forming bar ... + : (rates_total - prev_calculated) + 1; + + // + // Main Loop ... + for (int i = xReplyLimit - 1; i >= 0 && !IsStopped(); i--) + { + // + CalculateBuffers( + i, + prev_calculated, + rates_total, + // + open, + high, + close, + low, + tick_volume // + ); + } + + // + // Incremental color update ... + UpdateVisibleColors(); + + // + return rates_total; +} + +/** + * Handle Timer Event ... + */ +void OnTimer() +{ + // + if (!xReplyIsPlaying) + { + // + // Stop Timer ... + if (xReplyTimerActive) + { + StopTimer(); + } + return; + } + + // + ulong now = GetTickCount64(); + if (now - xReplyLastPlayTick >= (ulong)autoPlaySpeed) + { + // + xReplyLastPlayTick = now; + StepForward(); + + // + // Auto Stop at Current Market Bar ... + if (xReplyCurrentBarIndex <= 0) + { + // + xReplyIsPlaying = false; + ObjectSetInteger(0, xReplyBtnPlay, OBJPROP_STATE, false); + ObjectSetInteger(0, xReplyBtnPause, OBJPROP_STATE, true); + } + } +} + +/** + * Handle Chart Events ... + * + * @param id: int, Event id ... + * @param lparam: long, Event Long Parameter ... + * @param dparam: double, Event Double Parameter ... + * @param sparam: string, Event String Parameter ... + */ +void OnChartEvent( + const int id, // Event id ... + const long &lparam, // Event Long Parameter ... + const double &dparam, // Event Double Parameter ... + const string &sparam // Event String Parameter ... +) +{ + // + // Button Clicks ... + if (id == CHARTEVENT_OBJECT_CLICK) + { + // + if (sparam == xReplyBtnNext) + { + // + StepForward(); + ObjectSetInteger(0, xReplyBtnNext, OBJPROP_STATE, false); + ChartRedraw(); + } + else if (sparam == xReplyBtnPrev) + { + // + StepBackward(); + ObjectSetInteger(0, xReplyBtnPrev, OBJPROP_STATE, false); + ChartRedraw(); + } + else if (sparam == xReplyBtnPlay) + { + // + xReplyIsPlaying = true; + ObjectSetInteger(0, xReplyBtnPlay, OBJPROP_STATE, true); + ObjectSetInteger(0, xReplyBtnPause, OBJPROP_STATE, false); + ChartRedraw(); + } + else if (sparam == xReplyBtnPause) + { + // + xReplyIsPlaying = false; + ObjectSetInteger(0, xReplyBtnPause, OBJPROP_STATE, true); + ObjectSetInteger(0, xReplyBtnPlay, OBJPROP_STATE, false); + ChartRedraw(); + } + else if (sparam == xReplyBtnReset) + { + // + ResetReplay(); + ObjectSetInteger(0, xReplyBtnReset, OBJPROP_STATE, false); + ChartRedraw(); + } + // + return; + } + + // + // Keyboard Shortcuts ... + if (id == CHARTEVENT_KEYDOWN) + { + // + switch ((int)lparam) + { + // + // Arrow Right ... + case 38: + case 39: + StepForward(); + break; + // + // Arrow Left ... + case 37: + case 40: + StepBackward(); + break; + // + // Space ... + case 32: + xReplyIsPlaying = !xReplyIsPlaying; + ObjectSetInteger(0, xReplyBtnPlay, OBJPROP_STATE, xReplyIsPlaying); + ObjectSetInteger(0, xReplyBtnPause, OBJPROP_STATE, !xReplyIsPlaying); + ChartRedraw(); + break; + // + // R ... + case 82: + ResetReplay(); + break; + } + + // + return; + } +} +// #endregion + +// #region Custm Functions ... +/** + * Set Indicator Short Name and also we can define Buffers Labels ... + */ +void SetIndicatorName() +{ + // + IndicatorSetInteger(INDICATOR_DIGITS, _Digits); + IndicatorSetString(INDICATOR_SHORTNAME, ShortName); +} + +/** + * Validate Input Args for Initialization ... + * + * @return ( bool ) + */ +bool ValidateInputs() +{ + // + bool result = false; + + // + result = + // + startBar > 0 + // + ; + + // + return result; +} + +/** + * Extract Max Length of Inputs ... + * + * @return ( int ) + */ +int ExtractxReplyMaxLengthOfInputs() +{ + // + int result = 0; + + // + + // + return result; +} + +/** + * Initial all Indicator Requirements ... + * + * @return ( false ) + * + */ +bool InitRequirements() +{ + // + bool result = false; + + // + // Handlers ... + result = true; + + // + return result; +} + +/** + * Define Required Buffers ... + */ +void DefineBuffers() +{ + // #region Plot Buffers ... + + // + // SIGNAL Bar ... + + // + // Signal Bars Color ... + ArraySetAsSeries(pBarColorBuffer, true); + SetIndexBuffer(pBarColorBufferIndex, pBarColorBuffer, INDICATOR_COLOR_INDEX); + + // + // Set EMPTY_VALUE on XHK Bar Buffer ... + PlotIndexSetDouble(pBarBufferPlotIndex, PLOT_EMPTY_VALUE, emptyValue); + PlotIndexSetInteger(pBarBufferPlotIndex, PLOT_SHOW_DATA, false); + + // + // Open ... + ArraySetAsSeries(pBarOpenBuffer, true); + SetIndexBuffer(pBarOpenBufferIndex, pBarOpenBuffer, INDICATOR_DATA); + + // + // High ... + ArraySetAsSeries(pBarHighBuffer, true); + SetIndexBuffer(pBarHighBufferIndex, pBarHighBuffer, INDICATOR_DATA); + + // + // Low ... + ArraySetAsSeries(pBarLowBuffer, true); + SetIndexBuffer(pBarLowBufferIndex, pBarLowBuffer, INDICATOR_DATA); + + // + // Close ... + ArraySetAsSeries(pBarCloseBuffer, true); + SetIndexBuffer(pBarCloseBufferIndex, pBarCloseBuffer, INDICATOR_DATA); + // #endregion +} + +/** + * Calculate Custom Buffers ... + * + * @param barIndex: Integer, Represent Current Bar ... + * @param prevCalculated: Integer, Represent Previous Calculated Bars ... + * @param ratesTotal: Integer, Represents All Available Bars ... + * @param open: Double Array, History of Open Prices ... + * @param high: Double Array, History of High Prices ... + * @param close: Double Array, History of Close Prices ... + * @param low: Double Array, History of Low Prices ... + * @param tickVolume: Long, History of Tick Volumes on Bar ... + */ +void CalculateBuffers( + int barIndex, + const int prevCalculated, + const int ratesTotal, + const double &open[], + const double &high[], + const double &close[], + const double &low[], + const long &tickVolume[] // +) +{ + // + int barsxReplyLimit = ratesTotal; + xReplyFirstBarIndex = barsxReplyLimit - 1; + + // + bool canCalculate = + barIndex <= barsxReplyLimit; + if (canCalculate) + { + // + // Calculate Values ... + CalculateValues( + barIndex, + prevCalculated, + ratesTotal, + open, + high, + close, + low, + tickVolume // + ); + } + else + { + FillBuffersZero(barIndex); + } +} + +/** + * Fill All Bufers to Zero Vlue for Specified Bar Index ... + * + * @param barIndex: Integer ... + */ +void FillBuffersZero(int barIndex) +{ + pBarColorBuffer[barIndex] = hideColorIDX; +} + +/** + * Start Timer only when needed ... + */ +void StartTimer() +{ + // + if (!xReplyTimerActive) + { + EventSetMillisecondTimer(100); + xReplyTimerActive = true; + } +} + +/** + * Stop Timer to save resources ... + */ +void StopTimer() +{ + // + if (xReplyTimerActive) + { + EventKillTimer(); + xReplyTimerActive = false; + } +} +// #endregion + +// #region Value Calculators ... +/** + * Calculate Values ... + * + * @param barIndex: int, Specified Bar Index ... + * @param prevCalculated: int, Provides Previous Calculated Bars ... + * @param ratesTotal: int, Provides All Availabled Bars ... + * @param open: double Collection, Provides Open Prices Time Series ... + * @param high: double Collection, Provides High Prices Time Series ... + * @param close: double Collection, Provides Close Prices Time Series ... + * @param low: double Collection, Provides Low Prices Time Series ... + * @param tickVolume: long Collection, Provides Ticks Volume Prices Time Series ... + */ +void CalculateValues( + int barIndex, + const int prevCalculated, + const int ratesTotal, + const double &open[], + const double &high[], + const double &close[], + const double &low[], + const long &tickVolume[] // +) +{ + // + int lastBarIndex = barIndex + 1; + int maxBarIndex = ratesTotal; + bool isFirstBar = barIndex == xReplyFirstBarIndex; + + // + // Filling Buffers ... + pBarLowBuffer[barIndex] = low[barIndex]; + pBarOpenBuffer[barIndex] = open[barIndex]; + pBarHighBuffer[barIndex] = high[barIndex]; + pBarCloseBuffer[barIndex] = close[barIndex]; + + // + // Actually Bars Color Index ... + int colorIDX = + close[barIndex] >= open[barIndex] + ? bullishColorIDX + : close[barIndex] < open[barIndex] + ? bearishColorIDX + : neuturalColorIDX; + + // + int selectedColorIDX = colorIDX; + if (barIndex <= startBar) + { + selectedColorIDX = hideColorIDX; + } + pBarColorBuffer[barIndex] = selectedColorIDX; +} +// #endregion + +// #region Chart Style ... +/** + * Show Bars ... + */ +void ShowBars() +{ + LoadChartStyleFromFile(xReplyChartStyle); + SetChartStyle(xReplyChartStyle); +} + +/** + * Show Hide ... + */ +void HideBars() +{ + SetChartStyle(xReplyClearStyle); +} + +/** + * Read and Save Chart Style ... + */ +void ReadChartStyle() +{ + // + if (LoadChartStyleFromFile(xReplyChartStyle)) + { + return; + } + + // + // Retrieve Current Chart ID ... + long chartId = ChartID(); + xReplyChartStyle.chartId = chartId; + + // + // Retrieve Chart Mode ... + xReplyChartStyle.mode = (ENUM_CHART_MODE)ChartGetInteger(chartId, CHART_MODE); + + // + xReplyChartStyle.showBidLine = (bool)ChartGetInteger(chartId, CHART_SHOW_BID_LINE); + xReplyChartStyle.showAskLine = (bool)ChartGetInteger(chartId, CHART_SHOW_ASK_LINE); + xReplyChartStyle.showGrid = (bool)ChartGetInteger(chartId, CHART_SHOW_GRID); + xReplyChartStyle.showVolumes = (bool)ChartGetInteger(chartId, CHART_SHOW_VOLUMES); + xReplyChartStyle.showTradeLevels = (bool)ChartGetInteger(chartId, CHART_SHOW_TRADE_LEVELS); + xReplyChartStyle.autoScroll = (bool)ChartGetInteger(chartId, CHART_AUTOSCROLL); + xReplyChartStyle.quickNavigation = (bool)ChartGetInteger(chartId, CHART_QUICK_NAVIGATION); + + // + xReplyChartStyle.foreGroundColor = (color)ChartGetInteger(chartId, CHART_COLOR_FOREGROUND); + xReplyChartStyle.backGroundColor = (color)ChartGetInteger(chartId, CHART_COLOR_BACKGROUND); + xReplyChartStyle.upColor = (color)ChartGetInteger(chartId, CHART_COLOR_CHART_UP); + xReplyChartStyle.downColor = (color)ChartGetInteger(chartId, CHART_COLOR_CHART_DOWN); + xReplyChartStyle.bullishColor = (color)ChartGetInteger(chartId, CHART_COLOR_CANDLE_BULL); + xReplyChartStyle.bearishColor = (color)ChartGetInteger(chartId, CHART_COLOR_CANDLE_BEAR); + xReplyChartStyle.gridColor = (color)ChartGetInteger(chartId, CHART_COLOR_GRID); + xReplyChartStyle.bidLineColor = (color)ChartGetInteger(chartId, CHART_COLOR_BID); + xReplyChartStyle.askLineColor = (color)ChartGetInteger(chartId, CHART_COLOR_ASK); + xReplyChartStyle.stopColor = (color)ChartGetInteger(chartId, CHART_COLOR_STOP_LEVEL); + xReplyChartStyle.lineColor = (color)ChartGetInteger(chartId, CHART_COLOR_CHART_LINE); + xReplyChartStyle.volumesColor = (color)ChartGetInteger(chartId, CHART_COLOR_VOLUME); + + // + SaveChartStyleToFile(xReplyChartStyle); +} + +/** + * Setting Chart Style based on given Configuration ... + * + * @param mChartStyle: XChartStyle instance ... + */ +void SetChartStyle( + XChartStyle &mChartStyle // Chart Style to Apply +) +{ + // + ApplyChartStyle( + mChartStyle.chartId, + mChartStyle.mode, + mChartStyle.showBidLine, + mChartStyle.showAskLine, + mChartStyle.showGrid, + mChartStyle.showVolumes, + mChartStyle.showTradeLevels, + mChartStyle.autoScroll, + mChartStyle.quickNavigation, + mChartStyle.foreGroundColor, + mChartStyle.backGroundColor, + mChartStyle.upColor, + mChartStyle.downColor, + mChartStyle.bullishColor, + mChartStyle.bearishColor, + mChartStyle.gridColor, + mChartStyle.bidLineColor, + mChartStyle.askLineColor, + mChartStyle.lineColor, + mChartStyle.stopColor, + mChartStyle.volumesColor); +} + +/** + * Setting Chart Style by Providing required data ... + * + * @param mChartId: int, Chart ID ... + * @param mMode: ENUM_CHART_MODE, Chart Bars Style ... + * @param mShowBidLine: bool, Show Bid Line ... + * @param mShowAskLine: bool, Show Ask Line ... + * @param mShowGrid: bool, Show Grids ... + * @param mShowVolumes: bool, Show Volumes ... + * @param mShowTradeLevels: bool, Show Trade Levels ... + * @param mAutoScroll: bool, Auto Scroll Chart ... + * @param mQuickNavigation: bool, Show Quick Navigations ... + * @param mForeGroundColor: color, Foreground Color ... + * @param mBackGroundColor: color, Background Color ... + * @param mUpColor: color, Up Bars Color ... + * @param mDownColor: color, Down Bars Color ... + * @param mBullishColor: color, Bullish Bars Color ... + * @param mBearishColor: color, Bearish Bars Color ... + * @param mGridColor: color, Chart Grid Color ... + * @param mBidLineColor: color, Bid Line Color ... + * @param mAskLineColor: color, Ask Line Color ... + * @param mLineColor: color, Bars Line Color ... + * @param mStopColor: color, Trading Stop Color ... + * @param mVolumesColor: color, Volumes Bar Color ... + */ +void ApplyChartStyle( + long mChartId = -1, // chart's ID + ENUM_CHART_MODE mMode = CHART_CANDLES, // chart's mMode + bool mShowBidLine = true, // show bid line + bool mShowAskLine = true, // show ask line + bool mShowGrid = false, // show grids on chart + bool mShowVolumes = false, // show volumes + bool mShowTradeLevels = true, // Displaying trade levels in the chart (levels of open positions, Stop Loss, Take Profit and pending orders) + bool mAutoScroll = true, // chart autoscroll + bool mQuickNavigation = true, // chart quick navigation state + color mForeGroundColor = clrWhite, // chart's foreground color + color mBackGroundColor = clrBlack, // chart's background color + color mUpColor = clrGreen, // Up Color + color mDownColor = clrRed, // Down Color + color mBullishColor = clrGreen, // Bullish color + color mBearishColor = clrRed, // Bearish color + color mGridColor = clrGray, // grid color + color mBidLineColor = clrGray, // bid line color + color mAskLineColor = clrRed, // ask line color + color mLineColor = clrLime, // line mMode and doji candlestick color + color mStopColor = clrGold, // Color of stop order levels (Stop Loss and Take Profit) + color mVolumesColor = clrGreen // volumes color +) +{ + // + // Validate Args ... + if (mChartId == -1) + { + mChartId = 0; + } + + // + ChartSetInteger(mChartId, CHART_MODE, mMode); + ChartSetInteger(mChartId, CHART_SHOW_BID_LINE, mShowBidLine); + ChartSetInteger(mChartId, CHART_SHOW_ASK_LINE, mShowAskLine); + ChartSetInteger(mChartId, CHART_SHOW_GRID, mShowGrid); + ChartSetInteger(mChartId, CHART_SHOW_VOLUMES, mShowVolumes); + ChartSetInteger(mChartId, CHART_SHOW_TRADE_LEVELS, mShowTradeLevels); + ChartSetInteger(mChartId, CHART_AUTOSCROLL, mAutoScroll); + ChartSetInteger(mChartId, CHART_QUICK_NAVIGATION, mQuickNavigation); + ChartSetInteger(mChartId, CHART_COLOR_FOREGROUND, mForeGroundColor); + ChartSetInteger(mChartId, CHART_COLOR_BACKGROUND, mBackGroundColor); + ChartSetInteger(mChartId, CHART_COLOR_CHART_UP, mUpColor); + ChartSetInteger(mChartId, CHART_COLOR_CHART_DOWN, mDownColor); + ChartSetInteger(mChartId, CHART_COLOR_CANDLE_BULL, mBullishColor); + ChartSetInteger(mChartId, CHART_COLOR_CANDLE_BEAR, mBearishColor); + ChartSetInteger(mChartId, CHART_COLOR_GRID, mGridColor); + ChartSetInteger(mChartId, CHART_COLOR_BID, mBidLineColor); + ChartSetInteger(mChartId, CHART_COLOR_ASK, mAskLineColor); + ChartSetInteger(mChartId, CHART_COLOR_STOP_LEVEL, mStopColor); + ChartSetInteger(mChartId, CHART_COLOR_CHART_LINE, mLineColor); + ChartSetInteger(mChartId, CHART_COLOR_VOLUME, mVolumesColor); + + // + ChartRedraw(); +} + +/** + * Apply Custom Chart Style ... + */ +void ApplyCustomChartStyle() +{ + // + // Read Current Chart Config and Store it ... + ReadChartStyle(); + + // + // After Reading Current Chart Style ... + // we have to Save Configs and Change Styles ... + xReplyClearStyle = xReplyChartStyle; + + // + xReplyClearStyle.upColor = CLR_NONE; + xReplyClearStyle.downColor = CLR_NONE; + xReplyClearStyle.lineColor = CLR_NONE; + xReplyClearStyle.bullishColor = CLR_NONE; + xReplyClearStyle.bearishColor = CLR_NONE; + + // + // Decide to Show or not Candles based on User Input ... + HideBars(); +} + +/** + * Save Chart Style ... + * + * @param style: XChartStyle instance ... + * + * @return ( bool ) + */ +bool SaveChartStyleToFile( + const XChartStyle &style // +) +{ + // + bool result = false; + + // + // Create a File Handler for Writing Content to File ... + int fileHandler = FileOpen(xReplyChartStyleFileName, FILE_WRITE | FILE_BIN | FILE_COMMON); + result = fileHandler != INVALID_HANDLE; + if (!result) + { + return result; + } + + // + // Writing Struct to File ... + uint bytesWritten = FileWriteStruct(fileHandler, style); + FileClose(fileHandler); + result = bytesWritten > 0; + + // + return result; +} + +/** + * Load Chart Style ... + * + * @param style: XChartStyle instance ... + * + * @return ( bool ) + */ +bool LoadChartStyleFromFile( + XChartStyle &style // +) +{ + // + bool result = false; + + // + // Create a File Handler for Reading File Content ... + int fileHandler = FileOpen(xReplyChartStyleFileName, FILE_READ | FILE_BIN | FILE_COMMON); + result = fileHandler != INVALID_HANDLE; + if (!result) + { + return result; + } + + // + // Read File Content and Fill Struct ... + uint bytesRead = FileReadStruct(fileHandler, style); + FileClose(fileHandler); + + // + result = bytesRead > 0; + if (!result) + { + return result; + } + + // + return result; +} +// #endregion + +// #region UI ... +/** + * Create UI Panel ... + */ +void CreatePanel() +{ + // + xReplyBtnPlay = ShortName + "_BTN_PLAY"; + xReplyBtnPause = ShortName + "_BTN_PAUSE"; + xReplyBtnPrev = ShortName + "_BTN_PREV"; + xReplyBtnNext = ShortName + "_BTN_NEXT"; + xReplyBtnReset = ShortName + "_BTN_RESET"; + xReplyLblInfo = ShortName + "_LBL_INFO"; + + // + int x = 20; + int y = 30; + int w = 45; + int h = 30; + int gap = 5; + + // + // Prev Button ... + CreateButton( + xReplyBtnPrev, + x, + y, + w, + h, + "|<", + clrLightSkyBlue // + ); + + // + // Play Button ... + CreateButton( + xReplyBtnPlay, + x + (w + gap) * 1, + y, + w, + h, + ">", + clrLimeGreen // + ); + + // + // Pause Button ... + CreateButton( + xReplyBtnPause, + x + (w + gap) * 2, + y, + w, + h, + "||", + clrGoldenrod // + ); + + // + // Next Button ... + CreateButton( + xReplyBtnNext, + x + (w + gap) * 3, + y, + w, + h, + ">|", + clrLightSkyBlue // + ); + + // + // Reset Button ... + CreateButton( + xReplyBtnReset, + x + (w + gap) * 4, + y, + w, + h, + "R", + clrTomato // + ); + + // + // Info Label ... + ObjectCreate(0, xReplyLblInfo, OBJ_EDIT, 0, 0, 0); + ObjectSetInteger(0, xReplyLblInfo, OBJPROP_XDISTANCE, x); + ObjectSetInteger(0, xReplyLblInfo, OBJPROP_YDISTANCE, y + h + gap); + ObjectSetInteger(0, xReplyLblInfo, OBJPROP_XSIZE, (w + gap) * 5 - gap); + ObjectSetInteger(0, xReplyLblInfo, OBJPROP_YSIZE, 22); + ObjectSetInteger(0, xReplyLblInfo, OBJPROP_BGCOLOR, panelBackgroundColor); + ObjectSetInteger(0, xReplyLblInfo, OBJPROP_COLOR, infoColor); + ObjectSetInteger(0, xReplyLblInfo, OBJPROP_BORDER_COLOR, infoPanelBorderColor); + ObjectSetInteger(0, xReplyLblInfo, OBJPROP_READONLY, true); + ObjectSetInteger(0, xReplyLblInfo, OBJPROP_FONTSIZE, 9); + ObjectSetString(0, xReplyLblInfo, OBJPROP_FONT, "Arial"); + ObjectSetInteger(0, xReplyLblInfo, OBJPROP_CORNER, CORNER_LEFT_UPPER); + ObjectSetInteger(0, xReplyLblInfo, OBJPROP_SELECTABLE, false); + + // + ChartRedraw(); +} + +/** + * Create Specified Button ... + * + * @param name: string, Object Name ... + * @param x: int, X Position ... + * @param y: int, Y Position ... + * @param w: int, Width ... + * @param h: int, Height ... + * @param text: int Button Text ... + * @param borderClr: color, Border Color ... + */ +void CreateButton( + string name, // Button Name ... + int x, // X Position ... + int y, // Y Position ... + int w, // Width ... + int h, // Height ... + string text, // Button Text ... + color borderClr // Border Color ... +) +{ + // + ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0); + ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); + ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); + ObjectSetInteger(0, name, OBJPROP_XSIZE, w); + ObjectSetInteger(0, name, OBJPROP_YSIZE, h); + ObjectSetInteger(0, name, OBJPROP_BGCOLOR, panelBackgroundColor); + ObjectSetInteger(0, name, OBJPROP_COLOR, panelButtonTextColor); + ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, borderClr); + ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 11); + ObjectSetString(0, name, OBJPROP_FONT, "Arial Bold"); + ObjectSetString(0, name, OBJPROP_TEXT, text); + ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); + ObjectSetInteger(0, name, OBJPROP_STATE, false); + ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); +} + +/** + * Remove All Drawn Objects ... + */ +void RemoveUIObjects() +{ + // + long cid = ChartID(); + for (int i = ObjectsTotal(cid, 0) - 1; i >= 0; i--) + { + // + string name = ObjectName(cid, i, 0); + if (StringFind(name, ShortName + "_") >= 0) + { + ObjectDelete(cid, name); + } + } + + // + ChartRedraw(); +} + +/** + * Update Information Label ... + */ +void UpdateInfoLabel() +{ + // + int totalBars = Bars(_Symbol, _Period); + int shownBars = totalBars - xReplyCurrentBarIndex; + + // + datetime barTime = iTime(_Symbol, _Period, xReplyCurrentBarIndex); + string status = xReplyIsPlaying + ? "PLAYING" + : "PAUSED"; + // + string info = StringFormat( + "Bar: %d / %d | %s | %s", + shownBars, + totalBars, + TimeToString(barTime, TIME_DATE | TIME_MINUTES), + status // + ); + + // + ObjectSetString(0, xReplyLblInfo, OBJPROP_TEXT, info); + + // + ChartRedraw(); +} + +/** + * Handle Show Visible Bars ... + */ +void DrawVisibleCandles() +{ + // + if (ArraySize(pBarColorBuffer) <= 0) + { + return; + } + + // + UpdateVisibleColors(); + + // + // Track drawn count for future optimization ... + xReplyLastDrawnBarCount = xReplyCurrentVisibleBars; + + // + ChartRedraw(); +} + +/** + * Called after OHLC buffers are filled in OnCalculate + */ +void UpdateVisibleColors() +{ + // + int bufferSize = ArraySize(pBarColorBuffer); + if (bufferSize <= 0) + { + return; + } + + // + \ for (int i = 0; i < xReplyCurrentBarIndex && i < bufferSize; i++) + { + pBarColorBuffer[i] = hideColorIDX; + } + + // + for (int i = xReplyCurrentBarIndex; i < bufferSize; i++) + { + // + bool isBullish = pBarCloseBuffer[i] >= pBarOpenBuffer[i]; + pBarColorBuffer[i] = isBullish ? bullishColorIDX : bearishColorIDX; + } + + // + ChartRedraw(); +} +// #endregion + +// #region Control Actions ... +/** + * Handle Step Forward Action ... + */ +void StepForward() +{ + // + if (xReplyCurrentBarIndex > 0) + { + // + xReplyCurrentBarIndex--; + DrawVisibleCandles(); + UpdateInfoLabel(); + ScrollToBar(xReplyCurrentBarIndex); + } +} + +/** + * Handle Step Backward Action ... + */ +void StepBackward() +{ + // + int totalBars = Bars(_Symbol, _Period); + if (xReplyCurrentBarIndex < totalBars - 1) + { + // + xReplyCurrentBarIndex++; + DrawVisibleCandles(); + UpdateInfoLabel(); + ScrollToBar(xReplyCurrentBarIndex); + } +} + +/** + * Handle Reset Reply Action ... + */ +void ResetReplay() +{ + // + xReplyCurrentBarIndex = xReplyStartBarIndex; + xReplyIsPlaying = false; + + // + ObjectSetInteger(0, xReplyBtnPlay, OBJPROP_STATE, false); + ObjectSetInteger(0, xReplyBtnPause, OBJPROP_STATE, true); + + // + DrawVisibleCandles(); + UpdateInfoLabel(); + + // + ScrollToBar(xReplyStartBarIndex); +} + +/** + * Scroll Chart to Specified Bar Index ... + * + * @param barIndex: int, Specified Bar Index ... + */ +void ScrollToBar(int barIndex) +{ + // + // Validate Bar Index ... + int totalBars = Bars(_Symbol, _Period); + if (barIndex < 0 || barIndex >= totalBars) + { + return; + } + + // + // Set First Visible Bar to Specified Index ... + ChartSetInteger(ChartID(), CHART_FIRST_VISIBLE_BAR, barIndex); + ChartRedraw(); +} +// #endregion + +// #region TimeFrame Change State Management ... +/** + * Save Replay State for TimeFrame Change ... + * Called in OnDeinit when REASON_CHARTCHANGE + */ +void SaveReplayStateForTimeFrameChange() +{ + // + // Calculate Last Visible Bar Index ... + int lastVisibleBarIndex = xReplyStartBarIndex - xReplyCurrentVisibleBars + 1; + if (lastVisibleBarIndex < 0) + { + lastVisibleBarIndex = 0; + } + + // + // Retrieve Last Visible Bar Time ... + datetime lastVisibleTime = iTime(_Symbol, _Period, lastVisibleBarIndex); + + // + // Validate Time ... + if (!IsXValid(lastVisibleTime)) + { + return; + } + + // + // Generate Global Variable Names ... + string gvTimeName = ShortName + _Symbol + GV_TIME_SUFFIX; + string gvActiveName = ShortName + _Symbol + GV_ACTIVE_SUFFIX; + string gvVisibleBarsName = ShortName + _Symbol + GV_VISIBLE_BARS_SUFFIX; + + // + // Save Last Visible Time ... + GlobalVariableSet(gvTimeName, (double)lastVisibleTime); + + // + // Save Active State ... + GlobalVariableSet(gvActiveName, 1.0); + + // + // Save Visible Bars Count ... + GlobalVariableSet(gvVisibleBarsName, (double)xReplyCurrentVisibleBars); +} + +/** + * Try to Resume Replay from TimeFrame Change ... + * Called in OnInit to check if we should resume + * + * @return ( bool ) - true if resumed successfully + */ +bool TryResumeFromTimeFrameChange() +{ + // + bool result = false; + + // + // Generate Global Variable Names ... + string gvTimeName = ShortName + _Symbol + GV_TIME_SUFFIX; + string gvActiveName = ShortName + _Symbol + GV_ACTIVE_SUFFIX; + string gvVisibleBarsName = ShortName + _Symbol + GV_VISIBLE_BARS_SUFFIX; + + // + // Check Global Variables Exists ... + bool wasActive = GlobalVariableCheck(gvActiveName); + bool hasSavedTime = GlobalVariableCheck(gvTimeName); + + // + result = wasActive && hasSavedTime; + if (!result) + { + // + // Clean up if partial state exists ... + RemoveReplayGlobalVariables(); + return result; + } + + // + // Retrieve Saved Time ... + double savedTimeDouble = GlobalVariableGet(gvTimeName); + datetime savedTime = (datetime)savedTimeDouble; + + // + // Validate Saved Time ... + result = IsXValid(savedTime); + if (!result) + { + // + // Clean up if partial state exists ... + RemoveReplayGlobalVariables(); + return result; + } + + // + // Find First Bar in New TimeFrame at or after Saved Time ... + // iBarShift with exact=false returns the bar containing or before the time + int newBarIndex = iBarShift(_Symbol, _Period, savedTime, false); + + // + // Validate New Bar Index ... + result = newBarIndex >= 0; + if (!result) + { + // + // Clean up if partial state exists ... + RemoveReplayGlobalVariables(); + return result; + } + + // + // Set Replay State for New TimeFrame ... + xReplyStartTime = iTime(_Symbol, _Period, newBarIndex); + xReplyStartBarIndex = newBarIndex; + + // + // Try to restore visible bars count ... + if (GlobalVariableCheck(gvVisibleBarsName)) + { + // + double savedVisibleBars = GlobalVariableGet(gvVisibleBarsName); + xReplyCurrentVisibleBars = (int)savedVisibleBars; + + // + // Clamp to valid range ... + int maxBars = xReplyStartBarIndex + 1; + if (xReplyCurrentVisibleBars > maxBars) + { + xReplyCurrentVisibleBars = maxBars; + } + if (xReplyCurrentVisibleBars < 1) + { + xReplyCurrentVisibleBars = 1; + } + } + else + { + xReplyCurrentVisibleBars = 1; + } + + // + // Clean Global Variables after successful restore ... + RemoveReplayGlobalVariables(); + + // + return result; +} + +/** + * Remove Replay Global Variables ... + */ +void RemoveReplayGlobalVariables() +{ + // + string gvTimeName = ShortName + _Symbol + GV_TIME_SUFFIX; + if (GlobalVariableCheck(gvTimeName)) + { + GlobalVariableDel(gvTimeName); + } + + // + string gvActiveName = ShortName + _Symbol + GV_ACTIVE_SUFFIX; + if (GlobalVariableCheck(gvActiveName)) + { + GlobalVariableDel(gvActiveName); + } + + // + string gvVisibleBarsName = ShortName + _Symbol + GV_VISIBLE_BARS_SUFFIX; + if (GlobalVariableCheck(gvVisibleBarsName)) + { + GlobalVariableDel(gvVisibleBarsName); + } +} +// #endregion \ No newline at end of file diff --git a/Indicators/x-saherelm.x121.x-reply.mq5 b/Indicators/x-saherelm.x121.x-reply.mq5 index 40d2a75f..27681cd9 100644 --- a/Indicators/x-saherelm.x121.x-reply.mq5 +++ b/Indicators/x-saherelm.x121.x-reply.mq5 @@ -15,7 +15,7 @@ // Global Properties ... #property copyright "Copyright 2023, SaherElm IT Center" #property link "https://saherelm.ir" -#property version "2.00" +#property version "1.00" #property description "SaherElm X121 XReplay Indicator" #property icon "../Images/SaherElm_Logo_Color.ico" #property strict @@ -25,8 +25,15 @@ // #endregion // #region Definitions ... + // -#define ShortName "X121 XReplay" +#define ShortName "X121-XReplay" + +// +// Global Variable Keys for TimeFrame Change ... +#define GV_ACTIVE_SUFFIX "_Active" +#define GV_TIME_SUFFIX "_LastVisibleTime" +#define GV_VISIBLE_BARS_SUFFIX "_VisibleBars" // #define hideColorIDX 0 @@ -116,28 +123,10 @@ double pBarColorBuffer[]; #property indicator_label1 "XPBar Open;XPBar High;XPBar Low;XPBar Close" #property indicator_type1 DRAW_COLOR_CANDLES #property indicator_color1 CLR_NONE, clrDodgerBlue, clrDarkOrange - -// -// Data Buffers ... - -// -// #define pBarTimeBufferIndex buffersCount + 5 -// datetime pBarTimeBuffer[]; - -// -// #define pBarTickVolumeBufferIndex buffersCount + 6 -// long pBarTickVolumeBuffer[]; - -// -// #define pBarVolumeBufferIndex buffersCount + 7 -// long pBarVolumeBuffer[]; - -// -// #define pBarSpreadBufferIndex buffersCount + 8 -// long pBarSpreadBuffer[]; // #endregion // #region Variables ... + // int xReplyLimit; int xReplyMaxLength; @@ -161,10 +150,16 @@ string xReplyBtnReset; // Replay State ... int xReplyCandlesCount = 0; int xReplyStartBarIndex = 0; +int xReplyCurrentBarIndex = 0; bool xReplyIsPlaying = false; ulong xReplyLastPlayTick = 0; datetime xReplyStartTime = NULL; int xReplyCurrentVisibleBars = 0; + +// +bool xReplyTimerActive = false; // Timer is active only when playing +int xReplyLastDrawnBarCount = 0; // Track last drawn bar count for incremental draw +bool xReplyIsResumedFromTFChange = false; // Flag: resumed from timeframe change // #endregion // #region Event Handlers ... @@ -214,19 +209,37 @@ int OnInit() ApplyCustomChartStyle(); // - int idx = startBar; - if (idx >= Bars(_Symbol, _Period)) + xReplyIsResumedFromTFChange = TryResumeFromTimeFrameChange(); + if (!xReplyIsResumedFromTFChange) { - idx = Bars(_Symbol, _Period) - 10; - } + // + int idx = startBar; + if (idx >= Bars(_Symbol, _Period)) + { + idx = Bars(_Symbol, _Period) - 10; + } - // - xReplyStartTime = iTime(_Symbol, _Period, idx); - xReplyStartBarIndex = iBarShift(_Symbol, _Period, xReplyStartTime, false); - has = xReplyStartBarIndex >= 0; - if (!has) + // + xReplyStartTime = iTime(_Symbol, _Period, idx); + xReplyStartBarIndex = iBarShift(_Symbol, _Period, xReplyStartTime, false); + has = xReplyStartBarIndex >= 0; + if (!has) + { + return INIT_PARAMETERS_INCORRECT; + } + + // + xReplyCurrentVisibleBars = 1; + xReplyCurrentBarIndex = xReplyStartBarIndex; + } + else { - return INIT_PARAMETERS_INCORRECT; + // + // Validate Resumed State ... + if (xReplyStartBarIndex < 0) + { + return INIT_PARAMETERS_INCORRECT; + } } // @@ -235,14 +248,9 @@ int OnInit() // // Initial Draw ... - xReplyCurrentVisibleBars = 1; DrawVisibleCandles(); UpdateInfoLabel(); - // - // Enable Timer for Auto Play ... - EventSetMillisecondTimer(100); - // // Init Succeed ... return INIT_SUCCEEDED; @@ -267,6 +275,26 @@ int OnInit() */ void OnDeinit(const int reason) { + if (reason == REASON_CHARTCHANGE) + { + // + // Save Global Variables ... + SaveReplayStateForTimeFrameChange(); + } + else + { + // + // Remove Global Variables for Other Reasons ... + RemoveReplayGlobalVariables(); + } + + // + // Kill Timer if it's Activated ... + if (xReplyTimerActive) + { + StopTimer(); + } + // // Show Saved Chart Style ... ShowBars(); @@ -278,10 +306,6 @@ void OnDeinit(const int reason) // // Remove UI Objects ... RemoveUIObjects(); - - // - // Kill Timer ... - EventKillTimer(); } /** @@ -325,31 +349,15 @@ int OnCalculate( ArraySetAsSeries(spread, true); // - // Validate Calculated Bars ... - - // - bool isPassedRequiredCalculatedBars = true; - if (!isPassedRequiredCalculatedBars) - { - return prev_calculated; - } - - // + // Calculate Size of Loop ... xReplyLimit = (prev_calculated > rates_total || prev_calculated <= 0) - ? rates_total + // + // First Call ... + ? rates_total - 1 + // + // Subsequent calls: only new bars + current forming bar ... : (rates_total - prev_calculated) + 1; - // - // Buffers Copy ... - - // - // Validate Copied Items ... - bool isPassedRequiredCopiedItems = true; - if (!isPassedRequiredCopiedItems) - { - return prev_calculated; - } - // // Main Loop ... for (int i = xReplyLimit - 1; i >= 0 && !IsStopped(); i--) @@ -369,12 +377,8 @@ int OnCalculate( } // - if (prev_calculated == 0) - { - // - string message = "History Finished ..."; - Print(message); - } + // Incremental color update ... + UpdateVisibleColors(); // return rates_total; @@ -388,6 +392,12 @@ void OnTimer() // if (!xReplyIsPlaying) { + // + // Stop Timer ... + if (xReplyTimerActive) + { + StopTimer(); + } return; } @@ -401,7 +411,7 @@ void OnTimer() // // Auto Stop at Current Market Bar ... - if (xReplyStartBarIndex - xReplyCurrentVisibleBars <= 0) + if (xReplyCurrentBarIndex <= 0) { // xReplyIsPlaying = false; @@ -585,6 +595,7 @@ bool InitRequirements() void DefineBuffers() { // #region Plot Buffers ... + // // SIGNAL Bar ... @@ -617,26 +628,6 @@ void DefineBuffers() // Close ... ArraySetAsSeries(pBarCloseBuffer, true); SetIndexBuffer(pBarCloseBufferIndex, pBarCloseBuffer, INDICATOR_DATA); - - // - // Time ... - // ArraySetAsSeries(pBarTimeBuffer, true); - // SetIndexBuffer(pBarTimeBufferIndex, pBarTimeBuffer, INDICATOR_CALCULATIONS); - - // - // Tick Volume ... - // ArraySetAsSeries(pBarTickVolumeBuffer, true); - // SetIndexBuffer(pBarTickVolumeBufferIndex, pBarTickVolumeBuffer, INDICATOR_CALCULATIONS); - - // - // Volume ... - // ArraySetAsSeries(pBarVolumeBuffer, true); - // SetIndexBuffer(pBarVolumeBufferIndex, pBarVolumeBuffer, INDICATOR_CALCULATIONS); - - // - // Spread ... - // ArraySetAsSeries(pBarSpreadBuffer, true); - // SetIndexBuffer(pBarSpreadBufferIndex, pBarSpreadBuffer, INDICATOR_CALCULATIONS); // #endregion } @@ -700,6 +691,32 @@ void FillBuffersZero(int barIndex) { pBarColorBuffer[barIndex] = hideColorIDX; } + +/** + * Start Timer only when needed ... + */ +void StartTimer() +{ + // + if (!xReplyTimerActive) + { + EventSetMillisecondTimer(100); + xReplyTimerActive = true; + } +} + +/** + * Stop Timer to save resources ... + */ +void StopTimer() +{ + // + if (xReplyTimerActive) + { + EventKillTimer(); + xReplyTimerActive = false; + } +} // #endregion // #region Value Calculators ... @@ -1203,35 +1220,27 @@ void RemoveUIObjects() void UpdateInfoLabel() { // - int shownBars = MathMin( - xReplyCurrentVisibleBars, - xReplyStartBarIndex + 1 // - ); + int totalBars = Bars(_Symbol, _Period); + int shownBars = totalBars - xReplyCurrentBarIndex; // - int currentBarIdx = xReplyStartBarIndex - shownBars + 1; - if (currentBarIdx < 0) - { - currentBarIdx = 0; - } - - // - datetime barTime = iTime(_Symbol, _Period, currentBarIdx); + datetime barTime = iTime(_Symbol, _Period, xReplyCurrentBarIndex); string status = xReplyIsPlaying ? "PLAYING" : "PAUSED"; - // string info = StringFormat( "Bar: %d / %d | %s | %s", shownBars, - xReplyStartBarIndex + 1, + totalBars, TimeToString(barTime, TIME_DATE | TIME_MINUTES), status // ); // ObjectSetString(0, xReplyLblInfo, OBJPROP_TEXT, info); + + // ChartRedraw(); } @@ -1247,56 +1256,40 @@ void DrawVisibleCandles() } // - // Destroy Existing Candle Objects ... - ArrayInitialize(pBarColorBuffer, hideColorIDX); + UpdateVisibleColors(); // - int barsToDraw = MathMin( - xReplyCurrentVisibleBars, - xReplyStartBarIndex + 1 // - ); + // Track drawn count for future optimization ... + xReplyLastDrawnBarCount = xReplyCurrentVisibleBars; // - int barSeconds = PeriodSeconds(_Period); + ChartRedraw(); +} +/** + * Called after OHLC buffers are filled in OnCalculate + */ +void UpdateVisibleColors() +{ // - // Resize Candle Array ... - xReplyCandlesCount = barsToDraw; - - // - // Main Loop ... - bool isBullish = false; - int colorIDX = hideColorIDX; - for (int i = 0; i < barsToDraw; i++) + int bufferSize = ArraySize(pBarColorBuffer); + if (bufferSize <= 0) { - // - int barIdx = xReplyStartBarIndex - i; - if (barIdx < 0) - { - break; - } - - // - isBullish = - pBarCloseBuffer[barIdx] >= pBarOpenBuffer[barIdx]; - colorIDX = - isBullish - ? bullishColorIDX - : bearishColorIDX; - pBarColorBuffer[barIdx] = colorIDX; + return; } // - for (int i = xReplyStartBarIndex + 1; i < ArraySize(pBarColorBuffer); i++) + \ for (int i = 0; i < xReplyCurrentBarIndex && i < bufferSize; i++) + { + pBarColorBuffer[i] = hideColorIDX; + } + + // + for (int i = xReplyCurrentBarIndex; i < bufferSize; i++) { // - isBullish = - pBarCloseBuffer[i] >= pBarOpenBuffer[i]; - colorIDX = - isBullish - ? bullishColorIDX - : bearishColorIDX; - pBarColorBuffer[i] = colorIDX; + bool isBullish = pBarCloseBuffer[i] >= pBarOpenBuffer[i]; + pBarColorBuffer[i] = isBullish ? bullishColorIDX : bearishColorIDX; } // @@ -1311,17 +1304,13 @@ void DrawVisibleCandles() void StepForward() { // - int maxBars = xReplyStartBarIndex + 1; - if (xReplyCurrentVisibleBars < maxBars) + if (xReplyCurrentBarIndex > 0) { // - xReplyCurrentVisibleBars++; + xReplyCurrentBarIndex--; DrawVisibleCandles(); UpdateInfoLabel(); - - // - int currentBarIndex = xReplyStartBarIndex - xReplyCurrentVisibleBars + 1; - ScrollToBar(currentBarIndex); + ScrollToBar(xReplyCurrentBarIndex); } } @@ -1331,16 +1320,14 @@ void StepForward() void StepBackward() { // - if (xReplyCurrentVisibleBars > 1) + int totalBars = Bars(_Symbol, _Period); + if (xReplyCurrentBarIndex < totalBars - 1) { // - xReplyCurrentVisibleBars--; + xReplyCurrentBarIndex++; DrawVisibleCandles(); UpdateInfoLabel(); - - // - int currentBarIndex = xReplyStartBarIndex - xReplyCurrentVisibleBars + 1; - ScrollToBar(currentBarIndex); + ScrollToBar(xReplyCurrentBarIndex); } } @@ -1350,7 +1337,7 @@ void StepBackward() void ResetReplay() { // - xReplyCurrentVisibleBars = 1; + xReplyCurrentBarIndex = xReplyStartBarIndex; xReplyIsPlaying = false; // @@ -1385,4 +1372,179 @@ void ScrollToBar(int barIndex) ChartSetInteger(ChartID(), CHART_FIRST_VISIBLE_BAR, barIndex); ChartRedraw(); } +// #endregion + +// #region TimeFrame Change State Management ... +/** + * Save Replay State for TimeFrame Change ... + * Called in OnDeinit when REASON_CHARTCHANGE + */ +void SaveReplayStateForTimeFrameChange() +{ + // + // Calculate Last Visible Bar Index ... + int lastVisibleBarIndex = xReplyStartBarIndex - xReplyCurrentVisibleBars + 1; + if (lastVisibleBarIndex < 0) + { + lastVisibleBarIndex = 0; + } + + // + // Retrieve Last Visible Bar Time ... + datetime lastVisibleTime = iTime(_Symbol, _Period, lastVisibleBarIndex); + + // + // Validate Time ... + if (!IsXValid(lastVisibleTime)) + { + return; + } + + // + // Generate Global Variable Names ... + string gvTimeName = ShortName + _Symbol + GV_TIME_SUFFIX; + string gvActiveName = ShortName + _Symbol + GV_ACTIVE_SUFFIX; + string gvVisibleBarsName = ShortName + _Symbol + GV_VISIBLE_BARS_SUFFIX; + + // + // Save Last Visible Time ... + GlobalVariableSet(gvTimeName, (double)lastVisibleTime); + + // + // Save Active State ... + GlobalVariableSet(gvActiveName, 1.0); + + // + // Save Visible Bars Count ... + GlobalVariableSet(gvVisibleBarsName, (double)xReplyCurrentVisibleBars); +} + +/** + * Try to Resume Replay from TimeFrame Change ... + * Called in OnInit to check if we should resume + * + * @return ( bool ) - true if resumed successfully + */ +bool TryResumeFromTimeFrameChange() +{ + // + bool result = false; + + // + // Generate Global Variable Names ... + string gvTimeName = ShortName + _Symbol + GV_TIME_SUFFIX; + string gvActiveName = ShortName + _Symbol + GV_ACTIVE_SUFFIX; + string gvVisibleBarsName = ShortName + _Symbol + GV_VISIBLE_BARS_SUFFIX; + + // + // Check Global Variables Exists ... + bool wasActive = GlobalVariableCheck(gvActiveName); + bool hasSavedTime = GlobalVariableCheck(gvTimeName); + + // + result = wasActive && hasSavedTime; + if (!result) + { + // + // Clean up if partial state exists ... + RemoveReplayGlobalVariables(); + return result; + } + + // + // Retrieve Saved Time ... + double savedTimeDouble = GlobalVariableGet(gvTimeName); + datetime savedTime = (datetime)savedTimeDouble; + + // + // Validate Saved Time ... + result = IsXValid(savedTime); + if (!result) + { + // + // Clean up if partial state exists ... + RemoveReplayGlobalVariables(); + return result; + } + + // + // Find First Bar in New TimeFrame at or after Saved Time ... + // iBarShift with exact=false returns the bar containing or before the time + int newBarIndex = iBarShift(_Symbol, _Period, savedTime, false); + + // + // Validate New Bar Index ... + result = newBarIndex >= 0; + if (!result) + { + // + // Clean up if partial state exists ... + RemoveReplayGlobalVariables(); + return result; + } + + // + // Set Replay State for New TimeFrame ... + xReplyStartTime = iTime(_Symbol, _Period, newBarIndex); + xReplyStartBarIndex = newBarIndex; + + // + // Try to restore visible bars count ... + if (GlobalVariableCheck(gvVisibleBarsName)) + { + // + double savedVisibleBars = GlobalVariableGet(gvVisibleBarsName); + xReplyCurrentVisibleBars = (int)savedVisibleBars; + + // + // Clamp to valid range ... + int maxBars = xReplyStartBarIndex + 1; + if (xReplyCurrentVisibleBars > maxBars) + { + xReplyCurrentVisibleBars = maxBars; + } + if (xReplyCurrentVisibleBars < 1) + { + xReplyCurrentVisibleBars = 1; + } + } + else + { + xReplyCurrentVisibleBars = 1; + } + + // + // Clean Global Variables after successful restore ... + RemoveReplayGlobalVariables(); + + // + return result; +} + +/** + * Remove Replay Global Variables ... + */ +void RemoveReplayGlobalVariables() +{ + // + string gvTimeName = ShortName + _Symbol + GV_TIME_SUFFIX; + if (GlobalVariableCheck(gvTimeName)) + { + GlobalVariableDel(gvTimeName); + } + + // + string gvActiveName = ShortName + _Symbol + GV_ACTIVE_SUFFIX; + if (GlobalVariableCheck(gvActiveName)) + { + GlobalVariableDel(gvActiveName); + } + + // + string gvVisibleBarsName = ShortName + _Symbol + GV_VISIBLE_BARS_SUFFIX; + if (GlobalVariableCheck(gvVisibleBarsName)) + { + GlobalVariableDel(gvVisibleBarsName); + } +} // #endregion \ No newline at end of file