//+------------------------------------------------------------------+ //| Rangebar3.2.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 // Sets the indicator window height to match buttons // Input parameters input int InpBacklogMinutes = 60; // Historical data minutes 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 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 the 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 // Constants for layout 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(InpBacklogMinutes <= 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); g_lastProcessedTickTime = (TimeCurrent() - InpBacklogMinutes * 60) * 1000; // 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); } // 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); } g_initialized = true; // Set indicator window properties 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); 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); } //+------------------------------------------------------------------+ //| 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); } // Symbol exists - clear its historical data SymbolSelect(g_customSymbol, false); if(!CustomRatesDelete(g_customSymbol, 0, LONG_MAX)) { Print("Error deleting custom symbol rates: ", GetLastError()); return false; } return true; } else { // 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); // 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_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); }