Files
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

492 lines
37 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.
//+------------------------------------------------------------------+
//| 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 // 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
datetime startDate = 0; // Beginning of time
datetime endDate = TimeCurrent() + 365 * 24 * 60 * 60; // One year into the future to ensure all data is cleared
SymbolSelect(g_customSymbol, false);
if(!CustomRatesDelete(g_customSymbol, startDate, endDate))
{
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);
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);
}