Files

1058 lines
80 KiB
Plaintext
Raw Permalink Normal View History

2026-06-14 15:32:09 +02:00
//+------------------------------------------------------------------+
//| Range_Bars.mq5 |
//| Copyright 2025, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2025, ValenTech Trading Solutions"
#property version "1.10"
#property description "Range Bar NoGap Precision Indicator - Creates gap-free range bars based on tick data"
#property icon "\\Images\\Range_Bars.ico"
#property indicator_separate_window
#property indicator_buffers 0
#property indicator_plots 0
#property indicator_height 25 // Sets the indicator window height to match buttons
#define KEY_ENTER 13
// Input parameters
input group "Range Bars' Settings"
input int InpStartingBarsCount = 500; // Number of bars to start with
int InpStartingBars = InpStartingBarsCount; // Number of bars to start with
input double InpRangeSizePoints = 100.0; // Range size in points
double InpRangeSize = InpRangeSizePoints; // Range size in points
input string InpCustomSymbol = "RB"; // Custom symbol prefix
int InpBacklogMinutes = 7000000; // Number of minutes for historical tick data
int InpTickTolerance = 5; // Tolerance in seconds for tick processing
// Add enum for mode selection
enum ENUM_VERTICAL_LINES_MODE
{
HOURS_PERIOD_SEPARATOR = 1,
BARS_PERIOD_SEPARATOR
};
input group "Vertical Lines Settings"
input ENUM_VERTICAL_LINES_MODE InpVerticalLinesMode = BARS_PERIOD_SEPARATOR; // Vertical Lines Mode
input int InpVerticalLinesInterval = 360; // Interval Count
// Global variables
string g_customSymbol; // Full custom symbol name
long g_customChartID = 0; // Custom chart ID
MqlRates g_currentBar; // Current bar being formed
bool g_isNewBar = true; // Flag for new bar initialization
int g_digits; // Symbol digits
double g_point; // Symbol point size
bool g_initialized = false; // Initialization flag
int g_barCount = 0; // Counter for bar numbering
MqlRates g_ratesBuffer[]; // Buffer for collecting rates before update
datetime g_lastBarTime = 0; // Time of last completed bar
long g_lastProcessedTickTime = 0; // Time of last processed tick
double g_currentHigh; // Current bar's high price
double g_currentLow; // Current bar's low price
double g_upperThreshold; // Current bar's upper threshold
double g_lowerThreshold; // Current bar's lower threshold
int max_range = 3000; // Maximum point range
// Global variables for vertical lines
datetime g_lastLineTime = 0;
datetime g_trueTime = 0;
string g_linePrefix = "VertLine_";
// Global variable to store the previous background color
color g_previousBgColor = CLR_NONE;
// Global variables for bid line
string g_bidLineName = "CustomRangeBidLine";
datetime g_lastBidLineTime = 0;
bool InpDrawBidLine = true; // Draw Bid Line
color InpBidLineColor = clrRed; // Bid Line Color
ENUM_LINE_STYLE InpBidLineStyle = STYLE_SOLID; // Bid Line Style
int InpBidLineWidth = 1; // Bid Line Width
// Layout constants
const int PADDING = 5;
const int BUTTON_HEIGHT = 20;
const int VALUE_EDIT_WIDTH = 50;
const int M61_BUTTON_WIDTH = 40;
const int CLOSE_BUTTON_WIDTH = 20;
const int LABEL_WIDTH = 70;
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Validate inputs
if(InpStartingBars <= 0 || InpRangeSize <= 0 || InpTickTolerance <= 0 || StringLen(InpCustomSymbol) == 0)
{
Print("Error: Invalid input parameters");
return(INIT_PARAMETERS_INCORRECT);
}
if(InpRangeSize > max_range){
InpRangeSize = max_range;
Print("Range Size Adjusted to maximum value of ", max_range, " points");
}
if(InpStartingBars > 1000){
InpStartingBars = 1000;
Alert("Starting Bars Adjusted to maximum value of 1000 bars");
}
// Initialize with dummy tick to trigger sync
MqlTick dummy;
SymbolInfoTick(_Symbol, dummy);
// Calculate start time for historical data
datetime currentTime = TimeCurrent();
datetime startTime = currentTime - InpBacklogMinutes * 60;
g_lastProcessedTickTime = startTime * 1000;
// Copy historical ticks
MqlTick historical_ticks[];
ArrayResize(historical_ticks, 0);
int copied = CopyTicksRange(_Symbol, historical_ticks, COPY_TICKS_ALL, g_lastProcessedTickTime, currentTime * 1000);
// Initialize symbol-related variables
g_digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
g_point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
//g_lastProcessedTickTime = (TimeCurrent() - InpBacklogMinutes * 60) * 1000;
// Create custom symbol name
g_customSymbol = StringFormat("%s%s%s_%d", InpCustomSymbol, _Symbol, DoubleToString(InpRangeSize,2));
// Clean up any existing instance
CleanupResources();
// Create custom symbol with cleaned data
if(!CreateCustomSymbol())
{
Print("Failed to create custom symbol");
//return(INIT_FAILED);
}
// Open chart for custom symbol
SymbolSelect(g_customSymbol, true);
g_customChartID = ChartOpen(g_customSymbol, PERIOD_M1);
ChartSetInteger(g_customChartID,CHART_AUTOSCROLL,true);
ChartSetInteger(g_customChartID,CHART_SHIFT,true);
if(g_customChartID == 0)
{
Print("Failed to open custom symbol chart: ", GetLastError());
CleanupResources();
//return(INIT_FAILED);
}
IndicatorSetString(INDICATOR_SHORTNAME, "Range Bar NoGap");
// Create the background
ObjectCreate(0, "Background", OBJ_RECTANGLE_LABEL, 1, 0, 0);
ObjectSetInteger(0, "Background", OBJPROP_XDISTANCE, 0);
ObjectSetInteger(0, "Background", OBJPROP_YDISTANCE, 0);
ObjectSetInteger(0, "Background", OBJPROP_XSIZE, ChartGetInteger(0, CHART_WIDTH_IN_PIXELS));
ObjectSetInteger(0, "Background", OBJPROP_YSIZE, 25);
ObjectSetInteger(0, "Background", OBJPROP_BGCOLOR, clrBlack);
ObjectSetInteger(0, "Background", OBJPROP_BORDER_TYPE, BORDER_FLAT);
ObjectSetInteger(0, "Background", OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, "Background", OBJPROP_HIDDEN, true);
ObjectSetInteger(0, "Background", OBJPROP_SELECTABLE, false);
// Create "Range Bar" label
ObjectCreate(0, "RangeBarLabel", OBJ_LABEL, 1, 0, 0);
ObjectSetString(0, "RangeBarLabel", OBJPROP_TEXT, "Range Bars");
ObjectSetInteger(0, "RangeBarLabel", OBJPROP_XDISTANCE, PADDING);
ObjectSetInteger(0, "RangeBarLabel", OBJPROP_YDISTANCE, 5);
ObjectSetInteger(0, "RangeBarLabel", OBJPROP_COLOR, clrWhiteSmoke);
ObjectSetInteger(0, "RangeBarLabel", OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, "RangeBarLabel", OBJPROP_FONTSIZE, 8);
ObjectSetInteger(0, "RangeBarLabel", OBJPROP_HIDDEN, true);
ObjectSetInteger(0, "RangeBarLabel", OBJPROP_SELECTABLE, false);
// Create edit box (immediately after label)
ObjectCreate(0, "ValueEdit", OBJ_EDIT, 1, 0, 0);
ObjectSetString(0, "ValueEdit", OBJPROP_TEXT, "100");
ObjectSetInteger(0, "ValueEdit", OBJPROP_XDISTANCE, LABEL_WIDTH + PADDING);
ObjectSetInteger(0, "ValueEdit", OBJPROP_YDISTANCE, 3);
ObjectSetInteger(0, "ValueEdit", OBJPROP_XSIZE, VALUE_EDIT_WIDTH);
ObjectSetInteger(0, "ValueEdit", OBJPROP_YSIZE, BUTTON_HEIGHT);
ObjectSetInteger(0, "ValueEdit", OBJPROP_BGCOLOR, clrBlack);
ObjectSetInteger(0, "ValueEdit", OBJPROP_COLOR, clrLimeGreen);
ObjectSetInteger(0, "ValueEdit", OBJPROP_BORDER_COLOR, clrDarkGray);
ObjectSetInteger(0, "ValueEdit", OBJPROP_ALIGN, ALIGN_CENTER);
ObjectSetInteger(0, "ValueEdit", OBJPROP_HIDDEN, true);
ObjectSetInteger(0, "ValueEdit", OBJPROP_SELECTABLE, false);
// Create M61 button (immediately after edit box)
ObjectCreate(0, "OpenChartButton", OBJ_BUTTON, 1, 0, 0);
ObjectSetString(0, "OpenChartButton", OBJPROP_TEXT, "GTC");
ObjectSetInteger(0, "OpenChartButton", OBJPROP_XDISTANCE, LABEL_WIDTH + VALUE_EDIT_WIDTH + PADDING * 2);
ObjectSetInteger(0, "OpenChartButton", OBJPROP_YDISTANCE, 3);
ObjectSetInteger(0, "OpenChartButton", OBJPROP_XSIZE, M61_BUTTON_WIDTH);
ObjectSetInteger(0, "OpenChartButton", OBJPROP_YSIZE, BUTTON_HEIGHT);
ObjectSetInteger(0, "OpenChartButton", OBJPROP_COLOR, clrLimeGreen);
ObjectSetInteger(0, "OpenChartButton", OBJPROP_BGCOLOR, clrBlack);
ObjectSetInteger(0, "OpenChartButton", OBJPROP_BORDER_COLOR, clrDarkGray);
ObjectSetInteger(0, "OpenChartButton", OBJPROP_STATE, false);
ObjectSetInteger(0, "OpenChartButton", OBJPROP_HIDDEN, true);
ObjectSetInteger(0, "OpenChartButton", OBJPROP_SELECTABLE, false);
// Create close button
CreateCloseButton();
ChartRedraw();
// Calculate historical data on first run
if(!g_initialized)
{
// Copy historical ticks
ArrayFree(historical_ticks);
copied = CopyTicksRange(_Symbol, historical_ticks, COPY_TICKS_ALL, g_lastProcessedTickTime, currentTime * 1000);
if(copied <= 0)
{
Print("Error copying historical ticks: ", GetLastError());
RemoveIndicator();
//return(INIT_FAILED);
}
// Process historical ticks without writing to custom symbol
ArrayFree(g_ratesBuffer); // clear previous bars
g_barCount = 0;
for(int i = 0; i < copied; i++)
{
if(historical_ticks[i].time_msc > g_lastProcessedTickTime)
{
if(ProcessHistoricalTick(historical_ticks[i])){
g_lastProcessedTickTime = historical_ticks[i].time_msc;
}
}
}
int size = ArraySize(g_ratesBuffer);
if(size >= InpStartingBars)
{
// Delete excess bars
ArrayRemove(g_ratesBuffer, 0, size-InpStartingBars);
// Write remnant processed bars to custom symbol
if(CustomRatesUpdate(g_customSymbol, g_ratesBuffer))
{
g_initialized = true;
}
}
else if(size > 0 && size < InpStartingBars){
Alert("Failed to initialize required number of bars! Tick data available not sufficient for chosen range size");
Print("Continuing with ", size, " Bars");
// Write remnant processed bars to custom symbol
if(CustomRatesUpdate(g_customSymbol, g_ratesBuffer))
{
g_initialized = true;
}
}
if(!g_initialized)
{
Alert("Failed to initialize required number of bars! Sufficient tick data unavailable. Else, reduce the value of InpStartingBarsCount &/or InpRangeSizePoints");
RemoveIndicator();
//return(INIT_FAILED);
}
}
ChartRedraw(g_customChartID);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
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[])
{
if(rates_total <= 0)
return(0);
// Calculate historical data on first run
if(g_initialized)
{
// Process regular ticks
MqlTick current_ticks[];
ArrayResize(current_ticks, 0);
int copied = CopyTicksRange(_Symbol, current_ticks, COPY_TICKS_ALL,
(g_lastProcessedTickTime - InpTickTolerance * 1000),
TimeCurrent() * 1000);
if(copied > 0)
{
for(int i = 0; i < copied; i++)
{
if(current_ticks[i].time_msc > g_lastProcessedTickTime)
{
if(ProcessTick(current_ticks[i])){
g_lastProcessedTickTime = current_ticks[i].time_msc;
}
}
}
}
}
// Calculate historical data on first run
if(!g_initialized)
{
// Calculate start time for historical data
datetime currentTime = TimeCurrent();
datetime startTime = currentTime - InpBacklogMinutes * 60;
g_lastProcessedTickTime = startTime * 1000;
// Copy historical ticks
MqlTick historical_ticks[];
ArrayResize(historical_ticks, 0);
int copied = CopyTicksRange(_Symbol, historical_ticks, COPY_TICKS_ALL, g_lastProcessedTickTime, currentTime * 1000);
if(copied <= 0)
{
Print("Error copying historical ticks: ", GetLastError());
RemoveIndicator();
}
// Process historical ticks without writing to custom symbol
ArrayFree(g_ratesBuffer); // clear previous bars
g_barCount = 0;
for(int i = 0; i < copied; i++)
{
if(historical_ticks[i].time_msc > g_lastProcessedTickTime)
{
if(ProcessHistoricalTick(historical_ticks[i])){
g_lastProcessedTickTime = historical_ticks[i].time_msc;
}
}
}
int size = ArraySize(g_ratesBuffer);
if(size >= InpStartingBars)
{
// Delete excess bars
ArrayRemove(g_ratesBuffer, 0, size-InpStartingBars);
// Write remnant processed bars to custom symbol
if(CustomRatesUpdate(g_customSymbol, g_ratesBuffer))
{
g_initialized = true;
}
}
else if(size > 0 && size < InpStartingBars){
Alert("Failed to initialize required number of bars! Tick data available not sufficient for chosen range size");
Print("Continuing with ", size, " Bars");
// Write remnant processed bars to custom symbol
if(CustomRatesUpdate(g_customSymbol, g_ratesBuffer))
{
g_initialized = true;
}
}
if(!g_initialized)
{
Alert("Failed to initialize required number of bars! Tick data available not sufficient for chosen range size");
RemoveIndicator();
}
}
ChartRedraw(g_customChartID);
return(rates_total);
}
//==================================================================//
//HISTORICAL TICK
//==================================================================//
//+------------------------------------------------------------------+
//| Process historical tick |
//+------------------------------------------------------------------+
bool ProcessHistoricalTick(const MqlTick &tick)
{
double price = tick.bid;
if(g_isNewBar)
{
// Initialize new bar
InitializeNewHistoricalBar(tick, price);
}
// Update current bar values
UpdateCurrentHistoricalBar(price);
// Write true time for period separator
g_trueTime = tick.time;
// Save for the first time, the last line time for hour mode calculations
if(g_lastLineTime == 0)
{
MqlDateTime time;
TimeToStruct(g_trueTime, time);
if(time.hour == 0){
g_lastLineTime = g_trueTime;
}
}
// Check thresholds
if(price > g_upperThreshold)
{
g_currentBar.close = g_upperThreshold;
g_currentBar.high = g_upperThreshold;
CompleteCurrentHistoricalBar();
InitializeNewHistoricalBar(tick, g_upperThreshold);
return ProcessHistoricalTick(tick);
}
else if(price < g_lowerThreshold)
{
g_currentBar.close = g_lowerThreshold;
g_currentBar.low = g_lowerThreshold;
CompleteCurrentHistoricalBar();
InitializeNewHistoricalBar(tick, g_lowerThreshold);
return ProcessHistoricalTick(tick);
}
/*/ Update custom symbol//????
MqlRates tempBuffer[1];
tempBuffer[0] = g_currentBar;
CustomRatesUpdate(g_customSymbol, tempBuffer);///???*/
return true;
}
//+------------------------------------------------------------------+
//| Initialize new bar |
//+------------------------------------------------------------------+
void InitializeNewHistoricalBar(const MqlTick &tick, double price)
{
datetime tickTime = tick.time;
if(g_barCount == 0)
{
g_currentBar.time = (datetime)(MathFloor((double)tickTime / 60.0) * 60);
}
else
{
g_currentBar.time = g_lastBarTime + 60;
}
g_currentBar.open = price;
g_currentBar.high = price;
g_currentBar.low = price;
g_currentBar.close = price;
g_currentBar.tick_volume = 1;
g_currentBar.real_volume = 1;
g_currentBar.spread = 0;
g_currentHigh = price;
g_currentLow = price;
g_lowerThreshold = g_currentHigh - (InpRangeSize * g_point);
g_upperThreshold = g_currentLow + (InpRangeSize * g_point);
g_isNewBar = false;
g_barCount++;
ArrayResize(g_ratesBuffer, ArraySize(g_ratesBuffer) + 1);
g_ratesBuffer[ArraySize(g_ratesBuffer) - 1] = g_currentBar;
}
//+------------------------------------------------------------------+
//| Update current bar values |
//+------------------------------------------------------------------+
void UpdateCurrentHistoricalBar(double price)
{
if(price > g_currentHigh)
{
g_currentHigh = price;
g_lowerThreshold = g_currentHigh - (InpRangeSize * g_point);
}
if(price < g_currentLow)
{
g_currentLow = price;
g_upperThreshold = g_currentLow + (InpRangeSize * g_point);
}
g_currentBar.high = g_currentHigh;
g_currentBar.low = g_currentLow;
g_currentBar.close = price;
g_currentBar.tick_volume++;
}
//+------------------------------------------------------------------+
//| Complete current bar |
//+------------------------------------------------------------------+
void CompleteCurrentHistoricalBar()
{
g_lastBarTime = g_currentBar.time;
if(ArraySize(g_ratesBuffer) > 0)
{
g_ratesBuffer[ArraySize(g_ratesBuffer) - 1] = g_currentBar;
}
// Check if we need to draw a vertical line
CheckAndDrawVerticalLine(g_trueTime, g_currentBar.time);
g_isNewBar = true;
}
//==================================================================//
//INDIVIDUAL TICK
//==================================================================//
//+------------------------------------------------------------------+
//| Process individual tick |
//+------------------------------------------------------------------+
bool ProcessTick(const MqlTick &tick)
{
double price = tick.bid;
// Draw bid line if enabled
if(InpDrawBidLine)
{
// Only redraw if time has changed to avoid excessive drawing
if(tick.time > g_lastBidLineTime)
{
DrawBidLine(price);
g_lastBidLineTime = tick.time;
}
}
if(g_isNewBar)
{
// Initialize new bar
InitializeNewBar(tick, price);
}
// Update current bar values
UpdateCurrentBar(price);
// Write true time for period separator
g_trueTime = tick.time;
// Save for the first time, the last line time for hour mode calculations
if(g_lastLineTime == 0)
{
MqlDateTime time;
TimeToStruct(g_trueTime, time);
if(time.hour == 0){
g_lastLineTime = g_trueTime;
}
}
// Check thresholds
if(price > g_upperThreshold)
{
g_currentBar.close = g_upperThreshold;
g_currentBar.high = g_upperThreshold;
CompleteCurrentBar();
InitializeNewBar(tick, g_upperThreshold);
return ProcessTick(tick);
}
else if(price < g_lowerThreshold)
{
g_currentBar.close = g_lowerThreshold;
g_currentBar.low = g_lowerThreshold;
CompleteCurrentBar();
InitializeNewBar(tick, g_lowerThreshold);
return ProcessTick(tick);
}
// Update custom symbol//????
MqlRates tempBuffer[1];
tempBuffer[0] = g_currentBar;
CustomRatesUpdate(g_customSymbol, tempBuffer);///???
return true;
}
//+------------------------------------------------------------------+
//| Initialize new bar |
//+------------------------------------------------------------------+
void InitializeNewBar(const MqlTick &tick, double price)
{
datetime tickTime = tick.time;
if(g_barCount == 0)
{
g_currentBar.time = (datetime)(MathFloor((double)tickTime / 60.0) * 60);
}
else
{
g_currentBar.time = g_lastBarTime + 60;
}
g_currentBar.open = price;
g_currentBar.high = price;
g_currentBar.low = price;
g_currentBar.close = price;
g_currentBar.tick_volume = 1;
g_currentBar.real_volume = 1;
g_currentBar.spread = 0;
g_currentHigh = price;
g_currentLow = price;
g_lowerThreshold = g_currentHigh - (InpRangeSize * g_point);
g_upperThreshold = g_currentLow + (InpRangeSize * g_point);
g_isNewBar = false;
g_barCount++;
ArrayResize(g_ratesBuffer, ArraySize(g_ratesBuffer) + 1);
g_ratesBuffer[ArraySize(g_ratesBuffer) - 1] = g_currentBar;
}
//+------------------------------------------------------------------+
//| Update current bar values |
//+------------------------------------------------------------------+
void UpdateCurrentBar(double price)
{
if(price > g_currentHigh)
{
g_currentHigh = price;
g_lowerThreshold = g_currentHigh - (InpRangeSize * g_point);
}
if(price < g_currentLow)
{
g_currentLow = price;
g_upperThreshold = g_currentLow + (InpRangeSize * g_point);
}
g_currentBar.high = g_currentHigh;
g_currentBar.low = g_currentLow;
g_currentBar.close = price;
g_currentBar.tick_volume++;
}
//+------------------------------------------------------------------+
//| Complete current bar |
//+------------------------------------------------------------------+
void CompleteCurrentBar()
{
g_lastBarTime = g_currentBar.time;
if(ArraySize(g_ratesBuffer) > 0)
{
g_ratesBuffer[ArraySize(g_ratesBuffer) - 1] = g_currentBar;
CustomRatesUpdate(g_customSymbol, g_ratesBuffer);
}
// Check if we need to draw a vertical line
CheckAndDrawVerticalLine(g_trueTime, g_currentBar.time);
g_isNewBar = true;
}
//+------------------------------------------------------------------+
//| Create custom symbol with clean data |
//+------------------------------------------------------------------+
bool CreateCustomSymbol()
{
bool custom = true;
if(SymbolExist(g_customSymbol, custom))
{
// Close all charts with this symbol
long chartId = ChartFirst();
while(chartId >= 0)
{
if(ChartSymbol(chartId) == g_customSymbol)
ChartClose(chartId);
chartId = ChartNext(chartId);
}
// Clear OHLC data
MqlRates empty_rates[];
//CustomRatesUpdate(g_customSymbol, empty_rates);
// Delete rates
CustomRatesDelete(g_customSymbol, 0, LONG_MAX);
}
else
{
// Create new custom symbol
if(!CustomSymbolCreate(g_customSymbol, "RangeBars", _Symbol))
{
Print("Error creating custom symbol: ", GetLastError());
return false;
}
}
SymbolSelect(g_customSymbol, true);
return true;
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
RemoveIndicator();
}
//+------------------------------------------------------------------+
//| Cleanup resources |
//+------------------------------------------------------------------+
void CleanupResources()
{
// Close all charts with this symbol
long chartId = ChartFirst();
while(chartId >= 0)
{
if(ChartSymbol(chartId) == g_customSymbol)
ChartClose(chartId);
chartId = ChartNext(chartId);
}
// Remove symbol from Market Watch and delete
bool custom = true;
if(SymbolExist(g_customSymbol, custom))
{
SymbolSelect(g_customSymbol, false);
// Clear OHLC data
MqlRates empty_rates[];
CustomRatesUpdate(g_customSymbol, empty_rates);
CustomRatesDelete(g_customSymbol, 0, LONG_MAX);
CustomSymbolDelete(g_customSymbol);
}
// Reset variables
g_customChartID = 0;
g_initialized = false;
g_barCount = 0;
g_lastBarTime = 0;
ArrayFree(g_ratesBuffer);
}
//+------------------------------------------------------------------+
//| ChartEvent function |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
// Handle button clicks
if(id == CHARTEVENT_OBJECT_CLICK)
{
if(sparam == "OpenChartButton")
{
ChartSetInteger(g_customChartID, CHART_BRING_TO_TOP, true);
// Find chart with this symbol
bool charton = false;
long chartId = ChartFirst();
while(chartId >= 0)
{
if(ChartSymbol(chartId) == g_customSymbol){
charton = true;
break;
}
chartId = ChartNext(chartId);
}
if(!charton){
g_customChartID = ChartOpen(g_customSymbol, PERIOD_M1);
}
ChartSetInteger(g_customChartID, CHART_BRING_TO_TOP, true);
// ObjectSetInteger(0, "OpenChartButton", OBJPROP_STATE, false);
ChartRedraw();
}
else if(sparam == "CloseButton")
{
// Remove the indicator
int window = ChartWindowFind(0,"Range Bar NoGap");
IndicatorRelease(ChartIndicatorGet(0,window, "Range Bar NoGap"));
ChartIndicatorDelete(0,window,"Range Bar NoGap");
}
}
// Handle edit box input
if((id == CHARTEVENT_KEYDOWN && (int)lparam == KEY_ENTER) || (id == CHARTEVENT_OBJECT_ENDEDIT && sparam == "ValueEdit"))
{
string text = ObjectGetString(0, "ValueEdit", OBJPROP_TEXT);
double oldValue = InpRangeSize;
double newValue = StringToDouble(text);
if(newValue > 0 && newValue <= max_range)
{
InpRangeSize = newValue;
}
else{
Alert("Ensure value change is greater than 0 and not greater than max_range");
}
ResetCustom();
}
if(id == CHARTEVENT_CHART_CHANGE)
{
CreateCloseButton();
}
// Check for chart background colour change to set up of inverse colour for vertical lines
long bgColorValue = 0;
ChartGetInteger(g_customChartID, CHART_COLOR_BACKGROUND, 0, bgColorValue);
color currentBgColor = (color)bgColorValue;
// If this is the first check, initialize previous color and return false
if(g_previousBgColor == CLR_NONE)
{
g_previousBgColor = currentBgColor;
return;
}
if(currentBgColor != g_previousBgColor){
g_previousBgColor = currentBgColor;
ResetCustom();
}
}
//+------------------------------------------------------------------+
//| Create or update close button |
//+------------------------------------------------------------------+
void CreateCloseButton()
{
int chartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
// Always recreate the close button to ensure proper positioning
if(ObjectFind(0, "CloseButton") >= 0)
ObjectDelete(0, "CloseButton");
ObjectCreate(0, "CloseButton", OBJ_BUTTON, 1, 0, 0);
ObjectSetString(0, "CloseButton", OBJPROP_TEXT, "×");
ObjectSetInteger(0, "CloseButton", OBJPROP_XDISTANCE, chartWidth - CLOSE_BUTTON_WIDTH - PADDING);
ObjectSetInteger(0, "CloseButton", OBJPROP_YDISTANCE, 3);
ObjectSetInteger(0, "CloseButton", OBJPROP_XSIZE, CLOSE_BUTTON_WIDTH);
ObjectSetInteger(0, "CloseButton", OBJPROP_YSIZE, BUTTON_HEIGHT);
ObjectSetInteger(0, "CloseButton", OBJPROP_COLOR, clrWhite);
ObjectSetInteger(0, "CloseButton", OBJPROP_BGCOLOR, clrRed);
ObjectSetInteger(0, "CloseButton", OBJPROP_BORDER_COLOR, clrDarkGray);
ObjectSetInteger(0, "CloseButton", OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, "CloseButton", OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
ObjectSetInteger(0, "CloseButton", OBJPROP_HIDDEN, false);
ObjectSetInteger(0, "CloseButton", OBJPROP_SELECTABLE, false);// Hide the line's label on the horizontal axis
// Update background width
ObjectSetInteger(0, "Background", OBJPROP_XSIZE, chartWidth);
ChartRedraw(0);
}
//+------------------------------------------------------------------+
//| Reset Custom Symbol & params |
//+------------------------------------------------------------------+
void ResetCustom()
{
g_customChartID = 0; // Custom chart ID
ZeroMemory(g_currentBar); // Current bar being formed
g_isNewBar = true; // Flag for new bar initialization
g_initialized = false; // Initialization flag
g_barCount = 0; // Counter for bar numbering
ZeroMemory(g_ratesBuffer); // Buffer for collecting rates before update
g_lastBarTime = 0; // Time of last completed bar
g_lastProcessedTickTime = 0; // Time of last processed tick
g_lastLineTime = 0;
g_trueTime = 0;
// Clean up any existing instance
CleanupResources();
// Create custom symbol with cleaned data
if(!CreateCustomSymbol())
{
Print("Failed to create custom symbol");
}
// Open chart for custom symbol
SymbolSelect(g_customSymbol, true);
g_customChartID = ChartOpen(g_customSymbol, PERIOD_M1);
if(g_customChartID == 0)
{
Print("Failed to open custom symbol chart: ", GetLastError());
ResetCustom();
}
if(!(g_previousBgColor == CLR_NONE)){
ChartSetInteger(g_customChartID, CHART_COLOR_BACKGROUND, g_previousBgColor);
}
// Calculate historical data on first run
if(g_initialized)
{
// Process regular ticks
MqlTick current_ticks[];
ArrayResize(current_ticks, 0);
int copied = CopyTicksRange(_Symbol, current_ticks, COPY_TICKS_ALL,
(g_lastProcessedTickTime - InpTickTolerance * 1000),
TimeCurrent() * 1000);
if(copied > 0)
{
for(int i = 0; i < copied; i++)
{
if(current_ticks[i].time_msc > g_lastProcessedTickTime)
{
if(ProcessTick(current_ticks[i])){
g_lastProcessedTickTime = current_ticks[i].time_msc;
}
}
}
}
}
// Calculate historical data on first run
if(!g_initialized)
{
// Calculate start time for historical data
datetime currentTime = TimeCurrent();
datetime startTime = currentTime - InpBacklogMinutes * 60;
g_lastProcessedTickTime = startTime * 1000;
// Copy historical ticks
MqlTick historical_ticks[];
ArrayResize(historical_ticks, 0);
int copied = CopyTicksRange(_Symbol, historical_ticks, COPY_TICKS_ALL, g_lastProcessedTickTime, currentTime * 1000);
if(copied <= 0)
{
Print("Error copying historical ticks: ", GetLastError());
RemoveIndicator();
}
// Process historical ticks without writing to custom symbol
ArrayFree(g_ratesBuffer); // clear previous bars
g_barCount = 0;
for(int i = 0; i < copied; i++)
{
if(historical_ticks[i].time_msc > g_lastProcessedTickTime)
{
if(ProcessHistoricalTick(historical_ticks[i])){
g_lastProcessedTickTime = historical_ticks[i].time_msc;
}
}
}
int size = ArraySize(g_ratesBuffer);
if(size >= InpStartingBars)
{
// Delete excess bars
ArrayRemove(g_ratesBuffer, 0, size-InpStartingBars);
// Write remnant processed bars to custom symbol
if(CustomRatesUpdate(g_customSymbol, g_ratesBuffer))
{
g_initialized = true;
}
}
else if(size > 0 && size < InpStartingBars){
Alert("Failed to initialize required number of bars! Tick data available not sufficient for chosen range size");
Print("Continuing with ", size, " Bars");
// Write remnant processed bars to custom symbol
if(CustomRatesUpdate(g_customSymbol, g_ratesBuffer))
{
g_initialized = true;
}
}
if(!g_initialized)
{
Alert("Failed to initialize required number of bars! Tick data available not sufficient for chosen range size");
RemoveIndicator();
}
}
ChartRedraw(g_customChartID);
}
//+------------------------------------------------------------------+
//| Remove indicator completely |
//+------------------------------------------------------------------+
void RemoveIndicator()
{
//CleanupResources();
ObjectDelete(0, "Background");
ObjectDelete(0, "OpenChartButton");
ObjectDelete(0, "ValueEdit");
ObjectDelete(0, "CloseButton");
// Remove the indicator
int window = ChartWindowFind(0,"Range Bar NoGap");
IndicatorRelease(ChartIndicatorGet(0,window, "Range Bar NoGap"));
ChartIndicatorDelete(0,window,"Range Bar NoGap");
ChartRedraw();
}
//+------------------------------------------------------------------+
//| Check if vertical line should be drawn and draw it if needed |
//+------------------------------------------------------------------+
void CheckAndDrawVerticalLine(datetime trueTime, datetime barTime)
{
bool shouldDrawLine = false;
switch(InpVerticalLinesMode)
{
case HOURS_PERIOD_SEPARATOR:
// Calculate hours since first bar
shouldDrawLine = (g_lastLineTime > 0) && ((trueTime-g_lastLineTime) > InpVerticalLinesInterval*3600);
break;
case BARS_PERIOD_SEPARATOR:
shouldDrawLine = (g_barCount % InpVerticalLinesInterval == 0);
break;
}
if(shouldDrawLine)
{
g_lastLineTime = trueTime;
CustomRatesUpdate(g_customSymbol, g_ratesBuffer);
DrawVerticalLine(barTime);
ChartRedraw(g_customChartID);
}
}
//+------------------------------------------------------------------+
//| Draw a vertical line on the custom chart |
//+------------------------------------------------------------------+
void DrawVerticalLine(datetime lineTime)
{
if(g_customChartID == 0)
return;
// Create a unique name for the line
string lineName = g_linePrefix + TimeToString(lineTime);
// Get line color (inverse of background if requested)
color lineColor = GetInverseColor(g_customChartID);
// Draw the vertical line
if(!ObjectCreate(g_customChartID, lineName, OBJ_VLINE, 0, lineTime, 0))
{
Print("Failed to create vertical line: ", GetLastError());
return;
}
// Set line properties
ObjectSetInteger(g_customChartID, lineName, OBJPROP_COLOR, lineColor);
ObjectSetInteger(g_customChartID, lineName, OBJPROP_STYLE, STYLE_DASH);
ObjectSetInteger(g_customChartID, lineName, OBJPROP_WIDTH, 1);
ObjectSetInteger(g_customChartID, lineName, OBJPROP_BACK, false);
ObjectSetInteger(g_customChartID, lineName, OBJPROP_SELECTABLE, false);
ObjectSetInteger(g_customChartID, lineName, OBJPROP_SELECTED, false);
ObjectSetInteger(g_customChartID, lineName, OBJPROP_HIDDEN, true);
ObjectSetInteger(g_customChartID, lineName, OBJPROP_ZORDER, 0);
}
//+------------------------------------------------------------------+
//| Get inverse color of chart background |
//+------------------------------------------------------------------+
color GetInverseColor(long chartID)
{
// Get chart background color
long bgColorValue = 0;
ChartGetInteger(chartID, CHART_COLOR_BACKGROUND, 0, bgColorValue);
color bgColor = (color)bgColorValue;
// Extract RGB components
int red = bgColor & 0xFF;
int green = (bgColor >> 8) & 0xFF;
int blue = (bgColor >> 16) & 0xFF;
// Invert RGB components
red = 255 - red;
green = 255 - green;
blue = 255 - blue;
// Combine inverted components
color inverseColor = (color)((blue << 16) | (green << 8) | red);
return inverseColor;
}
//+------------------------------------------------------------------+
//| Draw a bid line on the custom chart |
//+------------------------------------------------------------------+
void DrawBidLine(double price)
{
if(g_customChartID == 0)
return;
// Delete existing line
ObjectDelete(g_customChartID, g_bidLineName);
// Create a new horizontal line
if(!ObjectCreate(g_customChartID, g_bidLineName, OBJ_HLINE, 0, 0, price))
{
Print("Failed to create bid line: ", GetLastError());
return;
}
// Set line properties
ObjectSetInteger(g_customChartID, g_bidLineName, OBJPROP_COLOR, InpBidLineColor);
ObjectSetInteger(g_customChartID, g_bidLineName, OBJPROP_STYLE, InpBidLineStyle);
ObjectSetInteger(g_customChartID, g_bidLineName, OBJPROP_WIDTH, InpBidLineWidth);
ObjectSetInteger(g_customChartID, g_bidLineName, OBJPROP_BACK, false);
ObjectSetInteger(g_customChartID, g_bidLineName, OBJPROP_SELECTABLE, false);
ObjectSetInteger(g_customChartID, g_bidLineName, OBJPROP_SELECTED, false);
ObjectSetInteger(g_customChartID, g_bidLineName, OBJPROP_HIDDEN, false);
// Hide the line's label on the price axis
ObjectSetString(g_customChartID, g_bidLineName, OBJPROP_TEXT, "");
}
//+------------------------------------------------------------------+