Complete x-121.x-rply tool ...

This commit is contained in:
2026-08-17 13:58:19 +03:30
parent cc51c0e906
commit 536f5c59e2
4 changed files with 2185 additions and 155 deletions
@@ -0,0 +1,318 @@
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>X121 XReplay - سند پروژه</title>
<style>
@page { size: A4; margin: 2cm; }
@media print {
.page-break { page-break-before: always; }
body { font-size: 11px; }
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Arial, sans-serif;
line-height: 1.9; color: #1a1a2e; background: #fff;
padding: 40px; max-width: 850px; margin: 0 auto;
}
h1 { font-size: 1.7em; color: #0f3460; border-bottom: 3px solid #e94560; padding-bottom: 12px; margin-bottom: 20px; }
h2 { font-size: 1.35em; color: #16213e; margin-top: 28px; margin-bottom: 12px; padding-right: 12px; border-right: 4px solid #e94560; }
h3 { font-size: 1.1em; color: #0f3460; margin-top: 18px; margin-bottom: 8px; }
p { margin-bottom: 10px; text-align: justify; }
.cover { text-align: center; padding: 70px 0; }
.cover h1 { font-size: 2.4em; border: none; color: #e94560; margin-bottom: 10px; }
.cover .subtitle { font-size: 1.2em; color: #555; margin-top: 12px; }
.cover .meta { margin-top: 50px; color: #777; line-height: 2.2; }
table { width: 100%; border-collapse: collapse; margin: 12px 0 20px; font-size: 0.9em; }
th, td { border: 1px solid #ccc; padding: 9px 11px; text-align: right; }
th { background: #0f3460; color: #fff; font-weight: 600; }
tr:nth-child(even) { background: #f4f6f9; }
code { background: #f0f0f0; padding: 2px 5px; border-radius: 3px; font-family: 'Courier New', monospace; font-size: 0.87em; color: #c7254e; }
pre {
background: #1e1e2e; color: #cdd6f4; padding: 16px; border-radius: 6px;
overflow-x: auto; margin: 12px 0 20px; font-size: 0.82em;
line-height: 1.6; direction: ltr; text-align: left;
}
.note { background: #fff3cd; border-right: 4px solid #ffc107; padding: 12px 16px; margin: 12px 0; border-radius: 0 5px 5px 0; }
.tip { background: #d1ecf1; border-right: 4px solid #17a2b8; padding: 12px 16px; margin: 12px 0; border-radius: 0 5px 5px 0; }
.success { background: #d4edda; border-right: 4px solid #28a745; padding: 12px 16px; margin: 12px 0; border-radius: 0 5px 5px 0; }
.flow-box { background: #e8f4fd; border: 1px solid #b3d9f2; border-radius: 6px; padding: 14px; margin: 10px 0; }
ul, ol { padding-right: 22px; margin-bottom: 10px; }
li { margin-bottom: 5px; }
.footer { margin-top: 50px; padding-top: 15px; border-top: 2px solid #eee; text-align: center; color: #999; font-size: 0.85em; }
.kbd { background: #eee; border: 1px solid #ccc; border-radius: 3px; padding: 2px 6px; font-family: monospace; }
.section-num { color: #e94560; font-weight: bold; }
</style>
</head>
<body>
<!-- ===== صفحه عنوان ===== -->
<div class="cover page-break">
<h1>X121 XReplay</h1>
<div class="subtitle">ابزار پخش مجدد کندل‌ها در متاتریدر ۵</div>
<div class="subtitle" style="color:#e94560;">TradingView-Style Candle Replay Tool for MetaTrader 5</div>
<div class="meta">
<p><strong>نسخه:</strong> 1.00</p>
<p><strong>توسعه‌دهنده:</strong> هادی خزاعی اصل</p>
<p><strong>شرکت:</strong> فن آوران ساحر علم</p>
<p><strong>SaherElm IT Center</strong></p>
</div>
</div>
<!-- ===== بخش ۱: نیازمندی ===== -->
<div class="page-break">
<h1><span class="section-num">۱.</span> شرح نیازمندی</h1>
<h2>۱.۱ مسئله</h2>
<p>
معامله‌گران برای تمرین و بک‌تست دستی استراتژی‌های خود به ابزاری نیاز دارند که بتواند
کندل‌های بازار را از یک نقطه زمانی مشخص پنهان کند و سپس به‌صورت تدریجی
(کندل به کندل) آن‌ها را نمایش دهد. این قابلیت در پلتفرم TradingView با نام
<strong>Bar Replay</strong> وجود دارد، اما در MetaTrader 5 به‌صورت پیش‌فرض
در دسترس نیست.
</p>
<h2>۱.۲ نیازمندی‌های عملکردی</h2>
<table>
<tr><th>#</th><th>نیازمندی</th><th>توضیح</th></tr>
<tr><td>۱</td><td>پنهان‌سازی کندل‌ها</td><td>کندل‌های بعد از نقطه cutoff باید از دید کاربر پنهان شوند</td></tr>
<tr><td>۲</td><td>نمایش تدریجی</td><td>امکان نمایش کندل‌ها یکی‌یکی با دکمه یا کیبورد</td></tr>
<tr><td>۳</td><td>پخش خودکار</td><td>امکان پخش خودکار کندل‌ها با سرعت قابل تنظیم</td></tr>
<tr><td>۴</td><td>بازنشانی</td><td>امکان بازگشت به نقطه شروع با یک کلیک</td></tr>
<tr><td>۵</td><td>تغییر تایم‌فریم</td><td>حفظ وضعیت Replay هنگام تغییر تایم‌فریم چارت</td></tr>
<tr><td>۶</td><td>بازیابی خودکار</td><td>بازیابی خودکار حالت چارت پس از حذف اندیکاتور</td></tr>
</table>
<h2>۱.۳ محدودیت‌های فنی MQL5</h2>
<ul>
<li>امکان حذف یا پنهان‌سازی مستقیم کندل‌های واقعی وجود ندارد</li>
<li>اندیکاتور نمی‌تواند داده‌های تاریخی را تغییر دهد</li>
<li>مدیریت رویدادهای چارت محدود به <code>OnChartEvent</code> است</li>
<li>ذخیره‌سازی وضعیت بین جلسات نیازمند Global Variables یا فایل است</li>
</ul>
</div>
<!-- ===== بخش ۲: معرفی ابزار ===== -->
<div class="page-break">
<h1><span class="section-num">۲.</span> معرفی ابزار</h1>
<h2>۲.۱ مشخصات کلی</h2>
<table>
<tr><th>مشخصه</th><th>مقدار</th></tr>
<tr><td>نام</td><td><code>X121 XReplay</code></td></tr>
<tr><td>نوع</td><td>اندیکاتور (Indicator)</td></tr>
<tr><td>پلتفرم</td><td>MetaTrader 5 (MQL5)</td></tr>
<tr><td>نسخه</td><td>1.00</td></tr>
<tr><td>فایل</td><td><code>x-saherelm.x121.x-reply.mq5</code></td></tr>
<tr><td>مسیر نصب</td><td><code>MQL5/Indicators/</code></td></tr>
<tr><td>وابستگی</td><td><code>x-saherelm.common.lib.mq5</code></td></tr>
<tr><td>توسعه‌دهنده</td><td>هادی خزاعی اصل</td></tr>
<tr><td>شرکت</td><td>فن آوران ساحر علم (SaherElm IT Center)</td></tr>
</table>
<h2>۲.۲ روش کار</h2>
<p>
ابزار از روش <strong>DRAW_COLOR_CANDLES</strong> با ۵ بافر برای رسم کندل‌ها استفاده می‌کند.
کندل‌های واقعی چارت با تغییر استایل چارت (تبدیل رنگ‌ها به <code>CLR_NONE</code>)
پنهان می‌شوند و کندل‌های Replay توسط بافرهای اندیکاتور رسم می‌شوند.
</p>
<h2>۲.۳ بافرها</h2>
<table>
<tr><th>بافر</th><th>نوع</th><th>کاربرد</th></tr>
<tr><td><code>pBarOpenBuffer</code></td><td>INDICATOR_DATA</td><td>قیمت Open</td></tr>
<tr><td><code>pBarHighBuffer</code></td><td>INDICATOR_DATA</td><td>قیمت High</td></tr>
<tr><td><code>pBarLowBuffer</code></td><td>INDICATOR_DATA</td><td>قیمت Low</td></tr>
<tr><td><code>pBarCloseBuffer</code></td><td>INDICATOR_DATA</td><td>قیمت Close</td></tr>
<tr><td><code>pBarColorBuffer</code></td><td>INDICATOR_COLOR_INDEX</td><td>ایندکس رنگ کندل</td></tr>
</table>
<h2>۲.۴ ایندکس‌های رنگ</h2>
<table>
<tr><th>ایندکس</th><th>نام</th><th>کاربرد</th></tr>
<tr><td>0</td><td><code>hideColorIDX</code></td><td>کندل پنهان (CLR_NONE)</td></tr>
<tr><td>1</td><td><code>bullishColorIDX</code></td><td>کندل صعودی (clrDodgerBlue)</td></tr>
<tr><td>2</td><td><code>bearishColorIDX</code></td><td>کندل نزولی (clrDarkOrange)</td></tr>
<tr><td>3</td><td><code>neuturalColorIDX</code></td><td>کندل خنثی</td></tr>
</table>
<h2>۲.۵ پارامترهای ورودی</h2>
<table>
<tr><th>گروه</th><th>پارامتر</th><th>پیش‌فرض</th><th>توضیح</th></tr>
<tr><td rowspan="2">Replay Settings</td><td><code>startBar</code></td><td>20</td><td>تعداد کندل‌های cutoff</td></tr>
<tr><td><code>autoPlaySpeed</code></td><td>1000</td><td>سرعت پخش خودکار (ms)</td></tr>
<tr><td rowspan="4">Presentation</td><td><code>panelButtonTextColor</code></td><td>clrYellow</td><td>رنگ متن دکمه‌ها</td></tr>
<tr><td><code>panelBackgroundColor</code></td><td>clrBlack</td><td>رنگ پس‌زمینه پنل</td></tr>
<tr><td><code>infoColor</code></td><td>clrWhite</td><td>رنگ متن اطلاعات</td></tr>
<tr><td><code>infoPanelBorderColor</code></td><td>clrBlack</td><td>رنگ حاشیه پنل</td></tr>
</table>
<h2>۲.۶ کنترل‌ها</h2>
<table>
<tr><th>دکمه</th><th>متن</th><th>عملکرد</th><th>کیبورد</th></tr>
<tr><td>Prev</td><td><code>|&lt;</code></td><td>یک کندل به عقب</td><td><span class="kbd">←</span> / <span class="kbd">↓</span></td></tr>
<tr><td>Play</td><td><code>&gt;</code></td><td>شروع پخش خودکار</td><td><span class="kbd">Space</span></td></tr>
<tr><td>Pause</td><td><code>||</code></td><td>توقف پخش</td><td><span class="kbd">Space</span></td></tr>
<tr><td>Next</td><td><code>&gt;|</code></td><td>یک کندل به جلو</td><td><span class="kbd">→</span> / <span class="kbd">↑</span></td></tr>
<tr><td>Reset</td><td><code>R</code></td><td>بازنشانی به نقطه شروع</td><td><span class="kbd">R</span></td></tr>
</table>
</div>
<!-- ===== بخش ۳: معماری ===== -->
<div class="page-break">
<h1><span class="section-num">۳.</span> معماری فنی</h1>
<h2>۳.۱ ساختار فایل</h2>
<table>
<tr><th>بخش</th><th>محتوا</th></tr>
<tr><td><code>#region Imports</code></td><td>کتابخانه‌های مورد نیاز</td></tr>
<tr><td><code>#region Definitions</code></td><td>ثابت‌ها، ساختار XChartStyle، ایندکس‌های رنگ</td></tr>
<tr><td><code>#region Inputs</code></td><td>پارامترهای ورودی کاربر</td></tr>
<tr><td><code>#region Buffers</code></td><td>تعریف ۵ بافر و پلات</td></tr>
<tr><td><code>#region Variables</code></td><td>متغیرهای سراسری</td></tr>
<tr><td><code>#region Event Handlers</code></td><td>OnInit، OnDeinit، OnCalculate، OnTimer، OnChartEvent</td></tr>
<tr><td><code>#region Custm Functions</code></td><td>توابع کمکی و اعتبارسنجی</td></tr>
<tr><td><code>#region Value Calculators</code></td><td>محاسبه مقادیر بافرها</td></tr>
<tr><td><code>#region Chart Style</code></td><td>مدیریت استایل چارت</td></tr>
<tr><td><code>#region UI</code></td><td>پنل کنترل و دکمه‌ها</td></tr>
<tr><td><code>#region Control Actions</code></td><td>اکشن‌های پخش</td></tr>
<tr><td><code>#region TimeFrame Change</code></td><td>مدیریت تغییر تایم‌فریم</td></tr>
</table>
<h2>۳.۲ ساختار XChartStyle</h2>
<p>این ساختار ۲۱ فیلد دارد و برای ذخیره و بازیابی کامل حالت چارت استفاده می‌شود:</p>
<table>
<tr><th>دسته</th><th>فیلدها</th></tr>
<tr><td>شناسایی</td><td>chartId, mode</td></tr>
<tr><td>نمایش</td><td>showBidLine, showAskLine, showGrid, showVolumes, showTradeLevels, autoScroll, quickNavigation</td></tr>
<tr><td>رنگ‌ها</td><td>foreGroundColor, backGroundColor, upColor, downColor, bullishColor, bearishColor, gridColor, bidLineColor, askLineColor, lineColor, stopColor, volumesColor</td></tr>
</table>
<h2>۳.۳ مکانیزم پنهان‌سازی</h2>
<div class="flow-box">
<ol>
<li><strong>ReadChartStyle:</strong> خواندن حالت فعلی چارت و ذخیره در <code>xReplyChartStyle</code></li>
<li><strong>SaveChartStyleToFile:</strong> ذخیره در فایل باینری با <code>FileWriteStruct</code></li>
<li><strong>ساخت clearStyle:</strong> کپی از حالت اصلی با رنگ‌های <code>CLR_NONE</code></li>
<li><strong>HideBars:</strong> اعمال <code>clearStyle</code> بر چارت → کندل‌های واقعی نامرئی می‌شوند</li>
</ol>
</div>
<h2>۳.۴ مکانیزم رسم کندل‌های Replay</h2>
<div class="flow-box">
<ol>
<li><strong>OnCalculate:</strong> پر کردن بافرهای OHLC از داده‌های واقعی</li>
<li><strong>UpdateVisibleColors:</strong> تنظیم رنگ بافر بر اساس وضعیت Replay</li>
<li>کندل‌های قبل از cutoff → رنگ عادی (صعودی/نزولی)</li>
<li>کندل‌های بعد از cutoff → <code>hideColorIDX</code> (پنهان)</li>
<li>کندل‌های قابل نمایش Replay → رنگ عادی (صعودی/نزولی)</li>
</ol>
</div>
<h2>۳.۵ مدیریت تغییر تایم‌فریم</h2>
<div class="flow-box">
<p><strong>هنگام تغییر تایم‌فریم (OnDeinit با REASON_CHARTCHANGE):</strong></p>
<ol>
<li>محاسبه ایندکس آخرین کندل نمایش داده شده</li>
<li>ذخیره زمان آن کندل در Global Variable</li>
<li>ذخیره وضعیت فعال بودن در Global Variable</li>
<li>ذخیره تعداد کندل‌های قابل نمایش در Global Variable</li>
</ol>
<p><strong>در تایم‌فریم جدید (OnInit):</strong></p>
<ol>
<li>بررسی وجود Global Variables</li>
<li>یافتن اولین کندل در تایم‌فریم جدید که شامل زمان ذخیره‌شده است</li>
<li>تنظیم <code>xReplyStartBarIndex</code> بر اساس کندل یافت‌شده</li>
<li>بازیابی تعداد کندل‌های قابل نمایش</li>
<li>حذف Global Variables</li>
</ol>
</div>
</div>
<!-- ===== بخش ۴: فرآیند انجام کار ===== -->
<div class="page-break">
<h1><span class="section-num">۴.</span> فرآیند و مراحل انجام کار</h1>
<h2>۴.۱ خلاصه مراحل توسعه</h2>
<table>
<tr><th>مرحله</th><th>شرح</th><th>نتیجه</th></tr>
<tr><td>۱</td><td>تحلیل نیازمندی و بررسی محدودیت‌های MQL5</td><td>شناسایی روش پنهان‌سازی</td></tr>
<tr><td>۲</td><td>طراحی معماری با DRAW_COLOR_CANDLES</td><td>تعریف ۵ بافر و ۴ رنگ</td></tr>
<tr><td>۳</td><td>پیاده‌سازی مدیریت Chart Style</td><td>ساختار XChartStyle با ۲۱ فیلد</td></tr>
<tr><td>۴</td><td>پیاده‌سازی ذخیره/بازیابی فایل باینری</td><td>FileWriteStruct / FileReadStruct</td></tr>
<tr><td>۵</td><td>پیاده‌سازی پنل UI و کنترل‌ها</td><td>۵ دکمه + برچسب اطلاعات</td></tr>
<tr><td>۶</td><td>پیاده‌سازی میانبرهای کیبورد</td><td>Arrow Keys + Space + R</td></tr>
<tr><td>۷</td><td>پیاده‌سازی تایمر و پخش خودکار</td><td>StartTimer / StopTimer</td></tr>
<tr><td>۸</td><td>پیاده‌سازی مدیریت تغییر تایم‌فریم</td><td>Global Variables</td></tr>
<tr><td>۹</td><td>بهینه‌سازی و تست نهایی</td><td>نسخه 1.00</td></tr>
</table>
<h2>۴.۲ تصمیمات فنی کلیدی</h2>
<table>
<tr><th>تصمیم</th><th>دلیل</th></tr>
<tr><td>استفاده از DRAW_COLOR_CANDLES</td><td>کارآمدترین روش رسم کندل در MQL5 بدون ایجاد اشیاء گرافیکی</td></tr>
<tr><td>پنهان‌سازی با CLR_NONE</td><td>تنها روش ممکن برای نامرئی کردن کندل‌های واقعی بدون حذف آن‌ها</td></tr>
<tr><td>ذخیره Chart Style در فایل باینری</td><td>بازیابی خودکار پس از حذف اندیکاتور یا کرش ترمینال</td></tr>
<tr><td>Global Variables برای تایم‌فریم</td><td>حفظ وضعیت بین OnDeinit و OnInit هنگام تغییر تایم‌فریم</td></tr>
<tr><td>تایمر فقط در حالت Play</td><td>بهینه‌سازی مصرف CPU در حالت Pause</td></tr>
</table>
<h2>۴.۳ ساختار کدنویسی</h2>
<p>کد بر اساس دستخط کدنویسی شرکت فن آوران ساحر علم نوشته شده است:</p>
<ul>
<li>بلوک‌بندی با <code>#region</code> / <code>#endregion</code></li>
<li>کامنت‌های ساختاریافته با <code>//</code></li>
<li>پیشوند <code>xReply</code> برای متغیرهای مربوط به Replay</li>
<li>توابع استاندارد: <code>ValidateInputs</code>، <code>InitRequirements</code>، <code>DefineBuffers</code>، <code>SetIndicatorName</code></li>
<li>مدیریت خطا با الگوی <code>has</code> / <code>result</code></li>
<li>پاکسازی منابع در <code>OnDeinit</code></li>
</ul>
</div>
<!-- ===== بخش ۵: راهنمای استفاده ===== -->
<div class="page-break">
<h1><span class="section-num">۵.</span> راهنمای استفاده</h1>
<h2>۵.۱ نصب</h2>
<ol>
<li>فایل <code>x-saherelm.x121.x-reply.mq5</code> را در مسیر <code>MQL5/Indicators/</code> قرار دهید</li>
<li>در MetaEditor با <span class="kbd">F7</span> کامپایل کنید</li>
<li>اندیکاتور را از Navigator روی چارت بکشید</li>
<li>پارامتر <code>startBar</code> را تنظیم کنید (تعداد کندل‌های cutoff)</li>
</ol>
<h2>۵.۲ استفاده</h2>
<ul>
<li>با <span class="kbd">→</span> یا <span class="kbd">↑</span> یک کندل به جلو بروید</li>
<li>با <span class="kbd">←</span> یا <span class="kbd">↓</span> یک کندل به عقب برگردید</li>
<li>با <span class="kbd">Space</span> پخش خودکار را شروع/متوقف کنید</li>
<li>با <span class="kbd">R</span> به نقطه شروع برگردید</li>
<li>تایم‌فریم را آزادانه تغییر دهید — وضعیت حفظ می‌شود</li>
</ul>
<h2>۵.۳ حذف</h2>
<p>
با حذف اندیکاتور از چارت، حالت چارت به‌طور خودکار بازیابی می‌شود،
فایل Chart Style حذف می‌شود و اشیاء UI پاک می‌شوند.
</p>
<div class="success">
<strong>✅ نکته:</strong> در صورت کرش ترمینال، فایل Chart Style باقی می‌ماند و در اجرای بعدی
به‌جای خواندن از چارت، از فایل خوانده می‌شود. این تضمین می‌کند که حالت چارت همیشه درست بازیابی شود.
</div>
</div>
<!-- ===== فوتر ===== -->
<div class="footer page-break">
<h2 style="border:none; text-align:center; padding:0;">پایان سند</h2>
<br>
<p><strong>X121 XReplay</strong> — نسخه 1.00</p>
<p>توسعه‌دهنده: هادی خزاعی اصل</p>
<p>فن آوران ساحر علم | SaherElm IT Center</p>
<br>
<p style="color:#aaa; font-size:0.9em;">© تمامی حقوق محفوظ است</p>
</div>
</body>
</html>
File diff suppressed because it is too large Load Diff
+317 -155
View File
@@ -15,7 +15,7 @@
// Global Properties ... // Global Properties ...
#property copyright "Copyright 2023, SaherElm IT Center" #property copyright "Copyright 2023, SaherElm IT Center"
#property link "https://saherelm.ir" #property link "https://saherelm.ir"
#property version "2.00" #property version "1.00"
#property description "SaherElm X121 XReplay Indicator" #property description "SaherElm X121 XReplay Indicator"
#property icon "../Images/SaherElm_Logo_Color.ico" #property icon "../Images/SaherElm_Logo_Color.ico"
#property strict #property strict
@@ -25,8 +25,15 @@
// #endregion // #endregion
// #region Definitions ... // #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 #define hideColorIDX 0
@@ -116,28 +123,10 @@ double pBarColorBuffer[];
#property indicator_label1 "XPBar Open;XPBar High;XPBar Low;XPBar Close" #property indicator_label1 "XPBar Open;XPBar High;XPBar Low;XPBar Close"
#property indicator_type1 DRAW_COLOR_CANDLES #property indicator_type1 DRAW_COLOR_CANDLES
#property indicator_color1 CLR_NONE, clrDodgerBlue, clrDarkOrange #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 // #endregion
// #region Variables ... // #region Variables ...
// //
int xReplyLimit; int xReplyLimit;
int xReplyMaxLength; int xReplyMaxLength;
@@ -161,10 +150,16 @@ string xReplyBtnReset;
// Replay State ... // Replay State ...
int xReplyCandlesCount = 0; int xReplyCandlesCount = 0;
int xReplyStartBarIndex = 0; int xReplyStartBarIndex = 0;
int xReplyCurrentBarIndex = 0;
bool xReplyIsPlaying = false; bool xReplyIsPlaying = false;
ulong xReplyLastPlayTick = 0; ulong xReplyLastPlayTick = 0;
datetime xReplyStartTime = NULL; datetime xReplyStartTime = NULL;
int xReplyCurrentVisibleBars = 0; 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 // #endregion
// #region Event Handlers ... // #region Event Handlers ...
@@ -214,19 +209,37 @@ int OnInit()
ApplyCustomChartStyle(); ApplyCustomChartStyle();
// //
int idx = startBar; xReplyIsResumedFromTFChange = TryResumeFromTimeFrameChange();
if (idx >= Bars(_Symbol, _Period)) 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); xReplyStartTime = iTime(_Symbol, _Period, idx);
xReplyStartBarIndex = iBarShift(_Symbol, _Period, xReplyStartTime, false); xReplyStartBarIndex = iBarShift(_Symbol, _Period, xReplyStartTime, false);
has = xReplyStartBarIndex >= 0; has = xReplyStartBarIndex >= 0;
if (!has) 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 ... // Initial Draw ...
xReplyCurrentVisibleBars = 1;
DrawVisibleCandles(); DrawVisibleCandles();
UpdateInfoLabel(); UpdateInfoLabel();
//
// Enable Timer for Auto Play ...
EventSetMillisecondTimer(100);
// //
// Init Succeed ... // Init Succeed ...
return INIT_SUCCEEDED; return INIT_SUCCEEDED;
@@ -267,6 +275,26 @@ int OnInit()
*/ */
void OnDeinit(const int reason) 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 ... // Show Saved Chart Style ...
ShowBars(); ShowBars();
@@ -278,10 +306,6 @@ void OnDeinit(const int reason)
// //
// Remove UI Objects ... // Remove UI Objects ...
RemoveUIObjects(); RemoveUIObjects();
//
// Kill Timer ...
EventKillTimer();
} }
/** /**
@@ -325,31 +349,15 @@ int OnCalculate(
ArraySetAsSeries(spread, true); ArraySetAsSeries(spread, true);
// //
// Validate Calculated Bars ... // Calculate Size of Loop ...
//
bool isPassedRequiredCalculatedBars = true;
if (!isPassedRequiredCalculatedBars)
{
return prev_calculated;
}
//
xReplyLimit = (prev_calculated > rates_total || prev_calculated <= 0) 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; : (rates_total - prev_calculated) + 1;
//
// Buffers Copy ...
//
// Validate Copied Items ...
bool isPassedRequiredCopiedItems = true;
if (!isPassedRequiredCopiedItems)
{
return prev_calculated;
}
// //
// Main Loop ... // Main Loop ...
for (int i = xReplyLimit - 1; i >= 0 && !IsStopped(); i--) for (int i = xReplyLimit - 1; i >= 0 && !IsStopped(); i--)
@@ -369,12 +377,8 @@ int OnCalculate(
} }
// //
if (prev_calculated == 0) // Incremental color update ...
{ UpdateVisibleColors();
//
string message = "History Finished ...";
Print(message);
}
// //
return rates_total; return rates_total;
@@ -388,6 +392,12 @@ void OnTimer()
// //
if (!xReplyIsPlaying) if (!xReplyIsPlaying)
{ {
//
// Stop Timer ...
if (xReplyTimerActive)
{
StopTimer();
}
return; return;
} }
@@ -401,7 +411,7 @@ void OnTimer()
// //
// Auto Stop at Current Market Bar ... // Auto Stop at Current Market Bar ...
if (xReplyStartBarIndex - xReplyCurrentVisibleBars <= 0) if (xReplyCurrentBarIndex <= 0)
{ {
// //
xReplyIsPlaying = false; xReplyIsPlaying = false;
@@ -585,6 +595,7 @@ bool InitRequirements()
void DefineBuffers() void DefineBuffers()
{ {
// #region Plot Buffers ... // #region Plot Buffers ...
// //
// SIGNAL Bar ... // SIGNAL Bar ...
@@ -617,26 +628,6 @@ void DefineBuffers()
// Close ... // Close ...
ArraySetAsSeries(pBarCloseBuffer, true); ArraySetAsSeries(pBarCloseBuffer, true);
SetIndexBuffer(pBarCloseBufferIndex, pBarCloseBuffer, INDICATOR_DATA); 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 // #endregion
} }
@@ -700,6 +691,32 @@ void FillBuffersZero(int barIndex)
{ {
pBarColorBuffer[barIndex] = hideColorIDX; 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 // #endregion
// #region Value Calculators ... // #region Value Calculators ...
@@ -1203,35 +1220,27 @@ void RemoveUIObjects()
void UpdateInfoLabel() void UpdateInfoLabel()
{ {
// //
int shownBars = MathMin( int totalBars = Bars(_Symbol, _Period);
xReplyCurrentVisibleBars, int shownBars = totalBars - xReplyCurrentBarIndex;
xReplyStartBarIndex + 1 //
);
// //
int currentBarIdx = xReplyStartBarIndex - shownBars + 1; datetime barTime = iTime(_Symbol, _Period, xReplyCurrentBarIndex);
if (currentBarIdx < 0)
{
currentBarIdx = 0;
}
//
datetime barTime = iTime(_Symbol, _Period, currentBarIdx);
string status = xReplyIsPlaying string status = xReplyIsPlaying
? "PLAYING" ? "PLAYING"
: "PAUSED"; : "PAUSED";
// //
string info = StringFormat( string info = StringFormat(
"Bar: %d / %d | %s | %s", "Bar: %d / %d | %s | %s",
shownBars, shownBars,
xReplyStartBarIndex + 1, totalBars,
TimeToString(barTime, TIME_DATE | TIME_MINUTES), TimeToString(barTime, TIME_DATE | TIME_MINUTES),
status // status //
); );
// //
ObjectSetString(0, xReplyLblInfo, OBJPROP_TEXT, info); ObjectSetString(0, xReplyLblInfo, OBJPROP_TEXT, info);
//
ChartRedraw(); ChartRedraw();
} }
@@ -1247,56 +1256,40 @@ void DrawVisibleCandles()
} }
// //
// Destroy Existing Candle Objects ... UpdateVisibleColors();
ArrayInitialize(pBarColorBuffer, hideColorIDX);
// //
int barsToDraw = MathMin( // Track drawn count for future optimization ...
xReplyCurrentVisibleBars, xReplyLastDrawnBarCount = xReplyCurrentVisibleBars;
xReplyStartBarIndex + 1 //
);
// //
int barSeconds = PeriodSeconds(_Period); ChartRedraw();
}
/**
* Called after OHLC buffers are filled in OnCalculate
*/
void UpdateVisibleColors()
{
// //
// Resize Candle Array ... int bufferSize = ArraySize(pBarColorBuffer);
xReplyCandlesCount = barsToDraw; if (bufferSize <= 0)
//
// Main Loop ...
bool isBullish = false;
int colorIDX = hideColorIDX;
for (int i = 0; i < barsToDraw; i++)
{ {
// return;
int barIdx = xReplyStartBarIndex - i;
if (barIdx < 0)
{
break;
}
//
isBullish =
pBarCloseBuffer[barIdx] >= pBarOpenBuffer[barIdx];
colorIDX =
isBullish
? bullishColorIDX
: bearishColorIDX;
pBarColorBuffer[barIdx] = colorIDX;
} }
// //
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 = bool isBullish = pBarCloseBuffer[i] >= pBarOpenBuffer[i];
pBarCloseBuffer[i] >= pBarOpenBuffer[i]; pBarColorBuffer[i] = isBullish ? bullishColorIDX : bearishColorIDX;
colorIDX =
isBullish
? bullishColorIDX
: bearishColorIDX;
pBarColorBuffer[i] = colorIDX;
} }
// //
@@ -1311,17 +1304,13 @@ void DrawVisibleCandles()
void StepForward() void StepForward()
{ {
// //
int maxBars = xReplyStartBarIndex + 1; if (xReplyCurrentBarIndex > 0)
if (xReplyCurrentVisibleBars < maxBars)
{ {
// //
xReplyCurrentVisibleBars++; xReplyCurrentBarIndex--;
DrawVisibleCandles(); DrawVisibleCandles();
UpdateInfoLabel(); UpdateInfoLabel();
ScrollToBar(xReplyCurrentBarIndex);
//
int currentBarIndex = xReplyStartBarIndex - xReplyCurrentVisibleBars + 1;
ScrollToBar(currentBarIndex);
} }
} }
@@ -1331,16 +1320,14 @@ void StepForward()
void StepBackward() void StepBackward()
{ {
// //
if (xReplyCurrentVisibleBars > 1) int totalBars = Bars(_Symbol, _Period);
if (xReplyCurrentBarIndex < totalBars - 1)
{ {
// //
xReplyCurrentVisibleBars--; xReplyCurrentBarIndex++;
DrawVisibleCandles(); DrawVisibleCandles();
UpdateInfoLabel(); UpdateInfoLabel();
ScrollToBar(xReplyCurrentBarIndex);
//
int currentBarIndex = xReplyStartBarIndex - xReplyCurrentVisibleBars + 1;
ScrollToBar(currentBarIndex);
} }
} }
@@ -1350,7 +1337,7 @@ void StepBackward()
void ResetReplay() void ResetReplay()
{ {
// //
xReplyCurrentVisibleBars = 1; xReplyCurrentBarIndex = xReplyStartBarIndex;
xReplyIsPlaying = false; xReplyIsPlaying = false;
// //
@@ -1385,4 +1372,179 @@ void ScrollToBar(int barIndex)
ChartSetInteger(ChartID(), CHART_FIRST_VISIBLE_BAR, barIndex); ChartSetInteger(ChartID(), CHART_FIRST_VISIBLE_BAR, barIndex);
ChartRedraw(); 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 // #endregion