Re: Custom Symbols in MT5: Building Synthetic Pairs and Spread Charts
Posted: Wed Oct 07, 2026 5:15 pm
The Pro MQL5 Spread Engine
Code: Select all
//+------------------------------------------------------------------+
//| Pro_SyntheticSpreadFeeder.mq5 |
//| Institutional Grade Custom Symbol Engine |
//+------------------------------------------------------------------+
#property copyright "Quantitative Developer"
#property version "2.00"
#property strict
//--- Input Parameters
sinput string MainSettings = "--- Symbol Configuration ---";
input string InpSymbolA = "EURUSD"; // Leg A (Base)
input string InpSymbolB = "GBPUSD"; // Leg B (Subtract)
input string InpCustomSymbol = "EUR_GBP_SPREAD"; // Custom Symbol Name
input string InpCustomPath = "Custom\\Spreads\\EUR_GBP_SPREAD"; // Market Watch Path
sinput string HistSettings = "--- Historical Data ---";
input int InpHistoryDays = 30; // Days of History to Backfill
//--- Global Variables
double g_last_bid = 0;
double g_last_ask = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// 1. Ensure market watch visibility and data synchronization
SymbolSelect(InpSymbolA, true);
SymbolSelect(InpSymbolB, true);
// 2. Initialize or verify Custom Symbol
if(!SymbolInfoInteger(InpCustomSymbol, SYMBOL_CUSTOM))
{
Print("Creating Pro Custom Symbol: ", InpCustomSymbol);
if(!CustomSymbolCreate(InpCustomSymbol, InpCustomPath, InpSymbolA))
{
Print("Critial Error: Creation failed. Code: ", GetLastError());
return(INIT_FAILED);
}
}
// 3. Set Professional Symbol Properties (UI/UX)
CustomSymbolSetString(InpCustomSymbol, SYMBOL_DESCRIPTION, "Synthetic Spread: " + InpSymbolA + " - " + InpSymbolB);
CustomSymbolSetInteger(InpCustomSymbol, SYMBOL_BACKGROUND_COLOR, clrDarkSlateBlue);
SymbolSelect(InpCustomSymbol, true);
// 4. Generate Historical Data if missing
datetime start_time = TimeCurrent() - (InpHistoryDays * 86400);
GenerateHistoricalData(start_time);
EventSetTimer(1); // 1-second timer for HUD updates
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
EventKillTimer();
Comment(""); // Clear HUD
}
//+------------------------------------------------------------------+
//| Expert tick function (Real-time injection) |
//+------------------------------------------------------------------+
void OnTick()
{
MqlTick tickA, tickB;
if(SymbolInfoTick(InpSymbolA, tickA) && SymbolInfoTick(InpSymbolB, tickB))
{
// Pro Spread Math
double syntheticAsk = tickA.ask - tickB.bid;
double syntheticBid = tickA.bid - tickB.ask;
// CPU Optimization: Only inject if price actually moved
if(syntheticAsk == g_last_ask && syntheticBid == g_last_bid) return;
g_last_ask = syntheticAsk;
g_last_bid = syntheticBid;
MqlTick customTick[1];
long latest_time_msc = MathMax(tickA.time_msc, tickB.time_msc);
customTick[0].time_msc = latest_time_msc;
customTick[0].time = (datetime)(latest_time_msc / 1000);
int digits = (int)SymbolInfoInteger(InpCustomSymbol, SYMBOL_DIGITS);
customTick[0].bid = NormalizeDouble(syntheticBid, digits);
customTick[0].ask = NormalizeDouble(syntheticAsk, digits);
customTick[0].flags = TICK_FLAG_BID | TICK_FLAG_ASK;
CustomTicksAdd(InpCustomSymbol, customTick);
}
}
//+------------------------------------------------------------------+
//| Timer function (Updates the HUD) |
//+------------------------------------------------------------------+
void OnTimer()
{
string hud = "=== PRO DATA FEEDER ACTIVE ===\n";
hud += "Symbol: " + InpCustomSymbol + "\n";
hud += "Current Spread Bid: " + DoubleToString(g_last_bid, 5) + "\n";
hud += "Current Spread Ask: " + DoubleToString(g_last_ask, 5) + "\n";
hud += "Data Stream: SYNCHRONIZED";
Comment(hud);
}
//+------------------------------------------------------------------+
//| Core Engine: Historical M1 Bar Generation |
//+------------------------------------------------------------------+
void GenerateHistoricalData(datetime from_date)
{
MqlRates ratesA[], ratesB[], customRates[];
// Check if we already have data to prevent redundant heavy processing
int existing_bars = SeriesInfoInteger(InpCustomSymbol, PERIOD_M1, SERIES_BARS_COUNT);
if(existing_bars > 100)
{
Print("History already exists. Resuming real-time feed.");
return;
}
Print("Downloading historical data...");
int copiedA = CopyRates(InpSymbolA, PERIOD_M1, from_date, TimeCurrent(), ratesA);
int copiedB = CopyRates(InpSymbolB, PERIOD_M1, from_date, TimeCurrent(), ratesB);
if(copiedA < 1 || copiedB < 1)
{
Print("Error: Could not download history. Check broker connection.");
return;
}
Print("Synchronizing ", copiedA, " bars. This may take a moment...");
ArrayResize(customRates, copiedA);
int custom_idx = 0;
int digits = (int)SymbolInfoInteger(InpCustomSymbol, SYMBOL_DIGITS);
// Loop through Base Leg (A) and find matching timestamps in Leg B
for(int i = 0; i < copiedA; i++)
{
// Binary search or loop to find matching time in B. For simplicity, we loop (B is usually synced with A)
for(int j = 0; j < copiedB; j++)
{
if(ratesB[j].time == ratesA[i].time)
{
customRates[custom_idx].time = ratesA[i].time;
// PRO SPREAD MATHEMATICS
// Open = Open A - Open B
// High = Highest possible diff = High A - Low B
// Low = Lowest possible diff = Low A - High B
// Close = Close A - Close B
customRates[custom_idx].open = NormalizeDouble(ratesA[i].open - ratesB[j].open, digits);
customRates[custom_idx].high = NormalizeDouble(ratesA[i].high - ratesB[j].low, digits);
customRates[custom_idx].low = NormalizeDouble(ratesA[i].low - ratesB[j].high, digits);
customRates[custom_idx].close= NormalizeDouble(ratesA[i].close - ratesB[j].close, digits);
customRates[custom_idx].tick_volume = ratesA[i].tick_volume + ratesB[j].tick_volume;
customRates[custom_idx].spread = 0;
customRates[custom_idx].real_volume = 0;
custom_idx++;
break;
}
}
}
// Inject the synchronized historical array directly into MT5's database
if(custom_idx > 0)
{
ArrayResize(customRates, custom_idx);
int added = CustomRatesUpdate(InpCustomSymbol, customRates);
Print("Successfully injected ", added, " historical M1 bars.");
}
}