Files

581 lines
42 KiB
Plaintext
Raw Permalink Normal View History

2026-06-14 15:32:09 +02:00
//+------------------------------------------------------------------+
//| RangeBar3.mq5 |
//| Copyright 2025, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright Valenteeno 2025"
#property link ""
#property version "1.00"
#property indicator_separate_window
#property indicator_buffers 0
#property indicator_plots 0
#property indicator_height 25
// Input parameters
input int InpStartingBars = 100; // Number of bars to start with
input double InpRangeSizePoints = 10.0; // Range size in points
input string InpCustomSymbol = "RB"; // Custom symbol prefix
input int InpTickTolerance = 5; // Tolerance in seconds for tick processing
// Global variables
int InpBacklogMinutes = 525600; // One year in minutes for historical data
string g_customSymbol; // Full custom symbol name
long g_customChartID = 0; // Custom chart ID
datetime g_startTime; // Starting time for processing
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 in milliseconds
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
double InpRangeSize = InpRangeSizePoints; // Range size in points
MqlRates g_historicalBars[]; // Buffer for historical bars calculation
// 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);
}
// Initialize symbol-related variables
g_digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
g_point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
// Create custom symbol name
g_customSymbol = StringFormat("%s%s_%d", InpCustomSymbol, _Symbol);
// Clean up any existing instance
CleanupResources();
// Create new custom symbol
if(!CreateCustomSymbol())
{
Print("Failed to create custom symbol");
return(INIT_FAILED);
}
// Initialize historical data
if(!InitializeHistoricalData())
{
Print("Failed to initialize historical data");
return(INIT_FAILED);
}
// 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());
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();
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(!g_initialized || rates_total <= 0)
return(0);
// Calculate start time for tick copying
long startTime = g_lastProcessedTickTime - (InpTickTolerance * 1000);
// Copy ticks
MqlTick ticks[];
int copied = CopyTicksRange(_Symbol, ticks, COPY_TICKS_ALL, startTime, 0);
if(copied <= 0)
{
Print("Error copying ticks: ", GetLastError());
return(0);
}
// Check if we have complete tick data
if(ticks[0].time_msc > g_lastProcessedTickTime)
{
Print("Incomplete tick data");
return(0);
}
// Find first unprocessed tick
int firstUnprocessedIndex = 0;
for(int i = 0; i < copied; i++)
{
if(ticks[i].time_msc > g_lastProcessedTickTime)
{
firstUnprocessedIndex = i;
break;
}
}
// Process ticks
for(int i = firstUnprocessedIndex; i < copied; i++)
{
ProcessTick(ticks[i]);
g_lastProcessedTickTime = ticks[i].time_msc;
}
// Update chart
//if chart is not found, no need. We'd need a push button to goto chart again
ChartRedraw(g_customChartID);
// Update close button position on each calculation
//CreateCloseButton();
return(rates_total);
}
//+------------------------------------------------------------------+
//| Initialize historical data |
//+------------------------------------------------------------------+
bool InitializeHistoricalData()
{
datetime startTime = TimeCurrent() - InpBacklogMinutes * 60;
MqlTick ticks[];
int copied = CopyTicksRange(_Symbol, ticks, COPY_TICKS_ALL, startTime * 1000, TimeCurrent() * 1000);
if(copied <= 0)
{
Print("Error copying historical ticks: ", GetLastError());
return false;
}
// Calculate historical bars without writing to custom symbol
ArrayResize(g_historicalBars, 0);
MqlRates tempBar;
bool isNewBar = true;
for(int i = 0; i < copied; i++)
{
double price = ticks[i].bid;
if(isNewBar)
{
// Initialize new bar
tempBar.time = (datetime)(MathFloor((double)ticks[i].time / 60.0) * 60);
tempBar.open = price;
tempBar.high = price;
tempBar.low = price;
tempBar.close = price;
tempBar.tick_volume = 1;
tempBar.real_volume = 1;
tempBar.spread = 0;
g_currentHigh = price;
g_currentLow = price;
g_lowerThreshold = g_currentHigh - InpRangeSize * g_point;
g_upperThreshold = g_currentLow + InpRangeSize * g_point;
isNewBar = false;
}
// Update bar values
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;
}
tempBar.high = g_currentHigh;
tempBar.low = g_currentLow;
tempBar.close = price;
tempBar.tick_volume++;
// Check if bar should be completed
if(price >= g_upperThreshold || price <= g_lowerThreshold)
{
// Add completed bar to historical buffer
int size = ArraySize(g_historicalBars);
ArrayResize(g_historicalBars, size + 1);
g_historicalBars[size] = tempBar;
// Check if we have enough bars
if(size + 1 >= InpStartingBars)
{
// Write historical bars to custom symbol
CustomRatesUpdate(g_customSymbol, g_historicalBars);
g_initialized = true;
g_lastProcessedTickTime = ticks[i].time_msc;
return true;
}
isNewBar = true;
}
}
return false;
}
//+------------------------------------------------------------------+
//| Process individual tick |
//+------------------------------------------------------------------+
bool ProcessTick(const MqlTick &tick)
{
double price = tick.bid;
if(g_isNewBar)
{
// Initialize new bar
InitializeNewBar(tick, price);
}
// Update current bar values
UpdateCurrentBar(price);
// Check thresholds
if(price >= g_upperThreshold)
{
CompleteCurrentBar();
InitializeNewBar(tick, g_upperThreshold);
return ProcessTick(tick);
}
else if(price <= 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);
}
g_isNewBar = true;
}
//+------------------------------------------------------------------+
//| Create custom symbol |
//+------------------------------------------------------------------+
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 historical data before deleting
datetime startDate = 0;
datetime endDate = TimeCurrent() + 365 * 24 * 60 * 60;
CustomRatesDelete(g_customSymbol, startDate, endDate);
// Remove symbol and recreate
SymbolSelect(g_customSymbol, false);
CustomSymbolDelete(g_customSymbol);
}
// Create new custom symbol
if(!CustomSymbolCreate(g_customSymbol, "RangeBars", _Symbol))
{
Print("Error creating custom symbol: ", GetLastError());
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
CleanupResources();
ObjectDelete(0, "Background");
ObjectDelete(0, "OpenChartButton");
ObjectDelete(0, "ValueEdit");
ObjectDelete(0, "CloseButton");
ChartRedraw();
}
//+------------------------------------------------------------------+
//| 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);
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_OBJECT_ENDEDIT && sparam == "ValueEdit")
{
string text = ObjectGetString(0, "ValueEdit", OBJPROP_TEXT);
double oldValue = InpRangeSize;
double newValue = StringToDouble(text);
if(newValue > 0)
{
InpRangeSize = newValue;
}
}
if(id == CHARTEVENT_CHART_CHANGE)
{
CreateCloseButton();
}
}
//+------------------------------------------------------------------+
//| 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);
// Update background width
ObjectSetInteger(0, "Background", OBJPROP_XSIZE, chartWidth);
ChartRedraw(0);
}