Files
RangeBars/Range_Bars.mq5
Valentine Chibuike Ozoigboanugo c8e3e30849 Initial commit: RangeBars
Proprietary trading software. © Teenodi Ltd. All rights reserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 15:32:09 +02:00

1058 lines
80 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//+------------------------------------------------------------------+
//| 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, "");
}
//+------------------------------------------------------------------+