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
+317 -155
View File
@@ -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