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
+
ابزار پخش مجدد کندلها در متاتریدر ۵
+
TradingView-Style Candle Replay Tool for MetaTrader 5
+
+
+
+
+
+
۱. شرح نیازمندی
+
+
۱.۱ مسئله
+
+ معاملهگران برای تمرین و بکتست دستی استراتژیهای خود به ابزاری نیاز دارند که بتواند
+ کندلهای بازار را از یک نقطه زمانی مشخص پنهان کند و سپس بهصورت تدریجی
+ (کندل به کندل) آنها را نمایش دهد. این قابلیت در پلتفرم TradingView با نام
+ Bar Replay وجود دارد، اما در MetaTrader 5 بهصورت پیشفرض
+ در دسترس نیست.
+
+
+
۱.۲ نیازمندیهای عملکردی
+
+ | # | نیازمندی | توضیح |
+ | ۱ | پنهانسازی کندلها | کندلهای بعد از نقطه cutoff باید از دید کاربر پنهان شوند |
+ | ۲ | نمایش تدریجی | امکان نمایش کندلها یکییکی با دکمه یا کیبورد |
+ | ۳ | پخش خودکار | امکان پخش خودکار کندلها با سرعت قابل تنظیم |
+ | ۴ | بازنشانی | امکان بازگشت به نقطه شروع با یک کلیک |
+ | ۵ | تغییر تایمفریم | حفظ وضعیت Replay هنگام تغییر تایمفریم چارت |
+ | ۶ | بازیابی خودکار | بازیابی خودکار حالت چارت پس از حذف اندیکاتور |
+
+
+
۱.۳ محدودیتهای فنی MQL5
+
+ - امکان حذف یا پنهانسازی مستقیم کندلهای واقعی وجود ندارد
+ - اندیکاتور نمیتواند دادههای تاریخی را تغییر دهد
+ - مدیریت رویدادهای چارت محدود به
OnChartEvent است
+ - ذخیرهسازی وضعیت بین جلسات نیازمند Global Variables یا فایل است
+
+
+
+
+
+
۲. معرفی ابزار
+
+
۲.۱ مشخصات کلی
+
+ | مشخصه | مقدار |
+ | نام | 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 توسط بافرهای اندیکاتور رسم میشوند.
+
+
+
۲.۳ بافرها
+
+ | بافر | نوع | کاربرد |
+ pBarOpenBuffer | INDICATOR_DATA | قیمت Open |
+ pBarHighBuffer | INDICATOR_DATA | قیمت High |
+ pBarLowBuffer | INDICATOR_DATA | قیمت Low |
+ pBarCloseBuffer | INDICATOR_DATA | قیمت Close |
+ pBarColorBuffer | INDICATOR_COLOR_INDEX | ایندکس رنگ کندل |
+
+
+
۲.۴ ایندکسهای رنگ
+
+ | ایندکس | نام | کاربرد |
+ | 0 | hideColorIDX | کندل پنهان (CLR_NONE) |
+ | 1 | bullishColorIDX | کندل صعودی (clrDodgerBlue) |
+ | 2 | bearishColorIDX | کندل نزولی (clrDarkOrange) |
+ | 3 | neuturalColorIDX | کندل خنثی |
+
+
+
۲.۵ پارامترهای ورودی
+
+ | گروه | پارامتر | پیشفرض | توضیح |
+ | Replay Settings | startBar | 20 | تعداد کندلهای cutoff |
+ autoPlaySpeed | 1000 | سرعت پخش خودکار (ms) |
+ | Presentation | panelButtonTextColor | clrYellow | رنگ متن دکمهها |
+ panelBackgroundColor | clrBlack | رنگ پسزمینه پنل |
+ infoColor | clrWhite | رنگ متن اطلاعات |
+ infoPanelBorderColor | clrBlack | رنگ حاشیه پنل |
+
+
+
۲.۶ کنترلها
+
+ | دکمه | متن | عملکرد | کیبورد |
+ | Prev | |< | یک کندل به عقب | ← / ↓ |
+ | Play | > | شروع پخش خودکار | Space |
+ | Pause | || | توقف پخش | Space |
+ | Next | >| | یک کندل به جلو | → / ↑ |
+ | Reset | R | بازنشانی به نقطه شروع | R |
+
+
+
+
+
+
۳. معماری فنی
+
+
۳.۱ ساختار فایل
+
+ | بخش | محتوا |
+ #region Imports | کتابخانههای مورد نیاز |
+ #region Definitions | ثابتها، ساختار XChartStyle، ایندکسهای رنگ |
+ #region Inputs | پارامترهای ورودی کاربر |
+ #region Buffers | تعریف ۵ بافر و پلات |
+ #region Variables | متغیرهای سراسری |
+ #region Event Handlers | OnInit، 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 |
+
+
+
۳.۳ مکانیزم پنهانسازی
+
+
+ - ReadChartStyle: خواندن حالت فعلی چارت و ذخیره در
xReplyChartStyle
+ - SaveChartStyleToFile: ذخیره در فایل باینری با
FileWriteStruct
+ - ساخت clearStyle: کپی از حالت اصلی با رنگهای
CLR_NONE
+ - HideBars: اعمال
clearStyle بر چارت → کندلهای واقعی نامرئی میشوند
+
+
+
+
۳.۴ مکانیزم رسم کندلهای Replay
+
+
+ - OnCalculate: پر کردن بافرهای OHLC از دادههای واقعی
+ - UpdateVisibleColors: تنظیم رنگ بافر بر اساس وضعیت Replay
+ - کندلهای قبل از cutoff → رنگ عادی (صعودی/نزولی)
+ - کندلهای بعد از cutoff →
hideColorIDX (پنهان)
+ - کندلهای قابل نمایش Replay → رنگ عادی (صعودی/نزولی)
+
+
+
+
۳.۵ مدیریت تغییر تایمفریم
+
+
هنگام تغییر تایمفریم (OnDeinit با REASON_CHARTCHANGE):
+
+ - محاسبه ایندکس آخرین کندل نمایش داده شده
+ - ذخیره زمان آن کندل در Global Variable
+ - ذخیره وضعیت فعال بودن در Global Variable
+ - ذخیره تعداد کندلهای قابل نمایش در Global Variable
+
+
در تایمفریم جدید (OnInit):
+
+ - بررسی وجود Global Variables
+ - یافتن اولین کندل در تایمفریم جدید که شامل زمان ذخیرهشده است
+ - تنظیم
xReplyStartBarIndex بر اساس کندل یافتشده
+ - بازیابی تعداد کندلهای قابل نمایش
+ - حذف Global Variables
+
+
+
+
+
+
+
۴. فرآیند و مراحل انجام کار
+
+
۴.۱ خلاصه مراحل توسعه
+
+ | مرحله | شرح | نتیجه |
+ | ۱ | تحلیل نیازمندی و بررسی محدودیتهای 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 |
+
+
+
۴.۳ ساختار کدنویسی
+
کد بر اساس دستخط کدنویسی شرکت فن آوران ساحر علم نوشته شده است:
+
+ - بلوکبندی با
#region / #endregion
+ - کامنتهای ساختاریافته با
//
+ - پیشوند
xReply برای متغیرهای مربوط به Replay
+ - توابع استاندارد:
ValidateInputs، InitRequirements، DefineBuffers، SetIndicatorName
+ - مدیریت خطا با الگوی
has / result
+ - پاکسازی منابع در
OnDeinit
+
+
+
+
+
+
۵. راهنمای استفاده
+
+
۵.۱ نصب
+
+ - فایل
x-saherelm.x121.x-reply.mq5 را در مسیر MQL5/Indicators/ قرار دهید
+ - در MetaEditor با F7 کامپایل کنید
+ - اندیکاتور را از Navigator روی چارت بکشید
+ - پارامتر
startBar را تنظیم کنید (تعداد کندلهای cutoff)
+
+
+
۵.۲ استفاده
+
+ - با → یا ↑ یک کندل به جلو بروید
+ - با ← یا ↓ یک کندل به عقب برگردید
+ - با Space پخش خودکار را شروع/متوقف کنید
+ - با R به نقطه شروع برگردید
+ - تایمفریم را آزادانه تغییر دهید — وضعیت حفظ میشود
+
+
+
۵.۳ حذف
+
+ با حذف اندیکاتور از چارت، حالت چارت بهطور خودکار بازیابی میشود،
+ فایل 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