598 lines
No EOL
19 KiB
MQL5
598 lines
No EOL
19 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| SymbolManager.mqh v1.0 |
|
|
//| Centralized Symbol Management & Caching |
|
|
//| Optimized for Multi-Symbol Trading |
|
|
//+------------------------------------------------------------------+
|
|
#ifndef SYMBOL_MANAGER_MQH
|
|
#define SYMBOL_MANAGER_MQH
|
|
|
|
#include "DataTypes.mqh"
|
|
#include <Trade/SymbolInfo.mqh>
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Symbol cache entry structure |
|
|
//+------------------------------------------------------------------+
|
|
struct SymbolCacheEntry
|
|
{
|
|
string symbol;
|
|
CSymbolInfo *info;
|
|
double point;
|
|
int digits;
|
|
double tick_value;
|
|
double tick_size;
|
|
double min_lot;
|
|
double max_lot;
|
|
double lot_step;
|
|
double min_stop_level;
|
|
double contract_size;
|
|
double margin_rate;
|
|
ENUM_SYMBOL_TRADE_EXECUTION execution_mode;
|
|
datetime last_update;
|
|
bool is_valid;
|
|
|
|
//--- Market hours
|
|
datetime session_open;
|
|
datetime session_close;
|
|
bool trading_allowed;
|
|
|
|
//--- Cached calculations
|
|
double pip_value;
|
|
double normalized_stop_level;
|
|
|
|
//--- Performance metrics
|
|
int access_count;
|
|
datetime last_access;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Symbol Manager Singleton Class |
|
|
//+------------------------------------------------------------------+
|
|
class CSymbolManager
|
|
{
|
|
private:
|
|
static CSymbolManager *m_instance;
|
|
|
|
//--- Symbol cache
|
|
SymbolCacheEntry m_cache[];
|
|
int m_cache_size;
|
|
int m_max_cache_size;
|
|
|
|
//--- Update intervals
|
|
int m_cache_ttl_seconds;
|
|
datetime m_last_cleanup;
|
|
|
|
//--- Statistics
|
|
int m_cache_hits;
|
|
int m_cache_misses;
|
|
|
|
//--- Private constructor for singleton
|
|
CSymbolManager();
|
|
|
|
//--- Helper methods
|
|
int FindCacheIndex(string symbol);
|
|
void UpdateCacheEntry(SymbolCacheEntry &entry, string symbol);
|
|
void CleanupCache();
|
|
double CalculatePipValue(string symbol);
|
|
|
|
public:
|
|
//--- Singleton access
|
|
static CSymbolManager* GetInstance();
|
|
static void DeleteInstance();
|
|
|
|
//--- Destructor
|
|
~CSymbolManager();
|
|
|
|
//--- Symbol information
|
|
bool GetSymbolInfo(string symbol, SymbolCacheEntry &info);
|
|
CSymbolInfo* GetSymbolInfoObject(string symbol);
|
|
|
|
//--- Quick access methods
|
|
double GetPoint(string symbol);
|
|
int GetDigits(string symbol);
|
|
double GetTickValue(string symbol);
|
|
double GetMinLot(string symbol);
|
|
double GetMaxLot(string symbol);
|
|
double GetLotStep(string symbol);
|
|
double GetMinStopLevel(string symbol);
|
|
double GetPipValue(string symbol);
|
|
double GetSpread(string symbol);
|
|
|
|
//--- Price operations
|
|
double NormalizePrice(string symbol, double price);
|
|
double NormalizeLots(string symbol, double lots);
|
|
double CalculateStopDistance(string symbol, double price1, double price2);
|
|
bool ValidateStopLevels(string symbol, double price, double sl, double tp);
|
|
|
|
//--- Multi-symbol operations
|
|
void RefreshAll();
|
|
int GetCachedSymbolCount() { return m_cache_size; }
|
|
void GetCachedSymbols(string &symbols[]);
|
|
|
|
//--- Market info
|
|
bool IsMarketOpen(string symbol);
|
|
double GetCurrentBid(string symbol);
|
|
double GetCurrentAsk(string symbol);
|
|
double GetCurrentPrice(string symbol, ENUM_POSITION_TYPE type);
|
|
|
|
//--- Statistics
|
|
void PrintStatistics();
|
|
double GetCacheHitRate() { return (m_cache_hits + m_cache_misses > 0) ?
|
|
(double)m_cache_hits / (m_cache_hits + m_cache_misses) * 100 : 0; }
|
|
};
|
|
|
|
//--- Static instance
|
|
CSymbolManager* CSymbolManager::m_instance = NULL;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get singleton instance |
|
|
//+------------------------------------------------------------------+
|
|
CSymbolManager* CSymbolManager::GetInstance()
|
|
{
|
|
if(m_instance == NULL)
|
|
m_instance = new CSymbolManager();
|
|
return m_instance;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Delete singleton instance |
|
|
//+------------------------------------------------------------------+
|
|
void CSymbolManager::DeleteInstance()
|
|
{
|
|
if(m_instance != NULL)
|
|
{
|
|
delete m_instance;
|
|
m_instance = NULL;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CSymbolManager::CSymbolManager()
|
|
{
|
|
m_cache_size = 0;
|
|
m_max_cache_size = 50;
|
|
m_cache_ttl_seconds = 60; // 1 minute cache
|
|
m_last_cleanup = 0;
|
|
m_cache_hits = 0;
|
|
m_cache_misses = 0;
|
|
|
|
ArrayResize(m_cache, m_max_cache_size);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
CSymbolManager::~CSymbolManager()
|
|
{
|
|
//--- Clean up symbol info objects
|
|
for(int i = 0; i < m_cache_size; i++)
|
|
{
|
|
if(m_cache[i].info != NULL)
|
|
{
|
|
delete m_cache[i].info;
|
|
m_cache[i].info = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get symbol information with caching |
|
|
//+------------------------------------------------------------------+
|
|
bool CSymbolManager::GetSymbolInfo(string symbol, SymbolCacheEntry &info)
|
|
{
|
|
//--- Find in cache
|
|
int index = FindCacheIndex(symbol);
|
|
|
|
if(index >= 0)
|
|
{
|
|
//--- Check if cache is still valid
|
|
if(TimeCurrent() - m_cache[index].last_update < m_cache_ttl_seconds)
|
|
{
|
|
info = m_cache[index];
|
|
m_cache[index].access_count++;
|
|
m_cache[index].last_access = TimeCurrent();
|
|
m_cache_hits++;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
//--- Update existing entry
|
|
UpdateCacheEntry(m_cache[index], symbol);
|
|
info = m_cache[index];
|
|
m_cache_hits++;
|
|
return m_cache[index].is_valid;
|
|
}
|
|
}
|
|
|
|
//--- Not in cache, add new entry
|
|
m_cache_misses++;
|
|
|
|
//--- Check if need to cleanup old entries
|
|
if(m_cache_size >= m_max_cache_size ||
|
|
TimeCurrent() - m_last_cleanup > 300) // Cleanup every 5 minutes
|
|
{
|
|
CleanupCache();
|
|
}
|
|
|
|
//--- Add new entry
|
|
if(m_cache_size < m_max_cache_size)
|
|
{
|
|
UpdateCacheEntry(m_cache[m_cache_size], symbol);
|
|
if(m_cache[m_cache_size].is_valid)
|
|
{
|
|
info = m_cache[m_cache_size];
|
|
m_cache_size++;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Find symbol in cache |
|
|
//+------------------------------------------------------------------+
|
|
int CSymbolManager::FindCacheIndex(string symbol)
|
|
{
|
|
for(int i = 0; i < m_cache_size; i++)
|
|
{
|
|
if(m_cache[i].symbol == symbol)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update cache entry |
|
|
//+------------------------------------------------------------------+
|
|
void CSymbolManager::UpdateCacheEntry(SymbolCacheEntry &entry, string symbol)
|
|
{
|
|
//--- Create or update symbol info object
|
|
if(entry.info == NULL)
|
|
entry.info = new CSymbolInfo();
|
|
|
|
if(!entry.info.Name(symbol))
|
|
{
|
|
entry.is_valid = false;
|
|
return;
|
|
}
|
|
|
|
//--- Refresh rates
|
|
entry.info.RefreshRates();
|
|
|
|
//--- Update basic info
|
|
entry.symbol = symbol;
|
|
entry.point = entry.info.Point();
|
|
entry.digits = entry.info.Digits();
|
|
entry.tick_value = entry.info.TickValue();
|
|
entry.tick_size = entry.info.TickSize();
|
|
entry.min_lot = entry.info.LotsMin();
|
|
entry.max_lot = entry.info.LotsMax();
|
|
entry.lot_step = entry.info.LotsStep();
|
|
entry.min_stop_level = entry.info.StopsLevel() * entry.point;
|
|
entry.contract_size = entry.info.ContractSize();
|
|
entry.margin_rate = entry.info.MarginInitial();
|
|
entry.execution_mode = entry.info.TradeExecution();
|
|
|
|
//--- Calculate derived values
|
|
entry.pip_value = CalculatePipValue(symbol);
|
|
entry.normalized_stop_level = entry.min_stop_level * 1.1; // 10% buffer
|
|
|
|
//--- Market hours
|
|
entry.trading_allowed = entry.info.TradeMode() != SYMBOL_TRADE_MODE_DISABLED;
|
|
|
|
//--- Update timestamps
|
|
entry.last_update = TimeCurrent();
|
|
entry.is_valid = true;
|
|
|
|
//--- Initialize counters if new
|
|
if(entry.access_count == 0)
|
|
{
|
|
entry.access_count = 1;
|
|
entry.last_access = TimeCurrent();
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate pip value |
|
|
//+------------------------------------------------------------------+
|
|
double CSymbolManager::CalculatePipValue(string symbol)
|
|
{
|
|
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
|
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
|
|
//--- Standard pip calculation
|
|
if(digits == 3 || digits == 5)
|
|
return point * 10;
|
|
else
|
|
return point;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Cleanup old cache entries |
|
|
//+------------------------------------------------------------------+
|
|
void CSymbolManager::CleanupCache()
|
|
{
|
|
datetime current_time = TimeCurrent();
|
|
int new_size = 0;
|
|
|
|
//--- Keep frequently accessed and recent entries
|
|
for(int i = 0; i < m_cache_size; i++)
|
|
{
|
|
bool keep = false;
|
|
|
|
//--- Keep if recently accessed
|
|
if(current_time - m_cache[i].last_access < 300) // 5 minutes
|
|
keep = true;
|
|
|
|
//--- Keep if frequently accessed
|
|
if(m_cache[i].access_count > 10)
|
|
keep = true;
|
|
|
|
//--- Move entry if keeping
|
|
if(keep && new_size != i)
|
|
{
|
|
m_cache[new_size] = m_cache[i];
|
|
m_cache[i].info = NULL; // Prevent double delete
|
|
}
|
|
else if(!keep && m_cache[i].info != NULL)
|
|
{
|
|
delete m_cache[i].info;
|
|
m_cache[i].info = NULL;
|
|
}
|
|
|
|
if(keep) new_size++;
|
|
}
|
|
|
|
m_cache_size = new_size;
|
|
m_last_cleanup = current_time;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get symbol info object |
|
|
//+------------------------------------------------------------------+
|
|
CSymbolInfo* CSymbolManager::GetSymbolInfoObject(string symbol)
|
|
{
|
|
SymbolCacheEntry info;
|
|
if(GetSymbolInfo(symbol, info))
|
|
return info.info;
|
|
return NULL;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Quick access methods |
|
|
//+------------------------------------------------------------------+
|
|
double CSymbolManager::GetPoint(string symbol)
|
|
{
|
|
SymbolCacheEntry info;
|
|
return GetSymbolInfo(symbol, info) ? info.point : 0;
|
|
}
|
|
|
|
int CSymbolManager::GetDigits(string symbol)
|
|
{
|
|
SymbolCacheEntry info;
|
|
return GetSymbolInfo(symbol, info) ? info.digits : 0;
|
|
}
|
|
|
|
double CSymbolManager::GetTickValue(string symbol)
|
|
{
|
|
SymbolCacheEntry info;
|
|
return GetSymbolInfo(symbol, info) ? info.tick_value : 0;
|
|
}
|
|
|
|
double CSymbolManager::GetMinLot(string symbol)
|
|
{
|
|
SymbolCacheEntry info;
|
|
return GetSymbolInfo(symbol, info) ? info.min_lot : 0.01;
|
|
}
|
|
|
|
double CSymbolManager::GetMaxLot(string symbol)
|
|
{
|
|
SymbolCacheEntry info;
|
|
return GetSymbolInfo(symbol, info) ? info.max_lot : 100;
|
|
}
|
|
|
|
double CSymbolManager::GetLotStep(string symbol)
|
|
{
|
|
SymbolCacheEntry info;
|
|
return GetSymbolInfo(symbol, info) ? info.lot_step : 0.01;
|
|
}
|
|
|
|
double CSymbolManager::GetMinStopLevel(string symbol)
|
|
{
|
|
SymbolCacheEntry info;
|
|
return GetSymbolInfo(symbol, info) ? info.normalized_stop_level : 0;
|
|
}
|
|
|
|
double CSymbolManager::GetPipValue(string symbol)
|
|
{
|
|
SymbolCacheEntry info;
|
|
return GetSymbolInfo(symbol, info) ? info.pip_value : 0;
|
|
}
|
|
|
|
double CSymbolManager::GetSpread(string symbol)
|
|
{
|
|
CSymbolInfo *info = GetSymbolInfoObject(symbol);
|
|
if(info != NULL)
|
|
{
|
|
info.RefreshRates();
|
|
return info.Spread() * info.Point();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Price operations |
|
|
//+------------------------------------------------------------------+
|
|
double CSymbolManager::NormalizePrice(string symbol, double price)
|
|
{
|
|
int digits = GetDigits(symbol);
|
|
return NormalizeDouble(price, digits);
|
|
}
|
|
|
|
double CSymbolManager::NormalizeLots(string symbol, double lots)
|
|
{
|
|
SymbolCacheEntry info;
|
|
if(!GetSymbolInfo(symbol, info))
|
|
return 0;
|
|
|
|
//--- Round to lot step
|
|
if(info.lot_step > 0)
|
|
lots = MathFloor(lots / info.lot_step) * info.lot_step;
|
|
|
|
//--- Apply limits
|
|
lots = MathMax(lots, info.min_lot);
|
|
lots = MathMin(lots, info.max_lot);
|
|
|
|
return NormalizeDouble(lots, 2);
|
|
}
|
|
|
|
double CSymbolManager::CalculateStopDistance(string symbol, double price1, double price2)
|
|
{
|
|
return MathAbs(price1 - price2);
|
|
}
|
|
|
|
bool CSymbolManager::ValidateStopLevels(string symbol, double price, double sl, double tp)
|
|
{
|
|
double min_stop = GetMinStopLevel(symbol);
|
|
|
|
//--- Check stop loss
|
|
if(sl > 0)
|
|
{
|
|
double sl_distance = MathAbs(price - sl);
|
|
if(sl_distance < min_stop)
|
|
return false;
|
|
}
|
|
|
|
//--- Check take profit
|
|
if(tp > 0)
|
|
{
|
|
double tp_distance = MathAbs(price - tp);
|
|
if(tp_distance < min_stop)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Refresh all cached symbols |
|
|
//+------------------------------------------------------------------+
|
|
void CSymbolManager::RefreshAll()
|
|
{
|
|
for(int i = 0; i < m_cache_size; i++)
|
|
{
|
|
UpdateCacheEntry(m_cache[i], m_cache[i].symbol);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get list of cached symbols |
|
|
//+------------------------------------------------------------------+
|
|
void CSymbolManager::GetCachedSymbols(string &symbols[])
|
|
{
|
|
ArrayResize(symbols, m_cache_size);
|
|
for(int i = 0; i < m_cache_size; i++)
|
|
{
|
|
symbols[i] = m_cache[i].symbol;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if market is open |
|
|
//+------------------------------------------------------------------+
|
|
bool CSymbolManager::IsMarketOpen(string symbol)
|
|
{
|
|
SymbolCacheEntry info;
|
|
if(!GetSymbolInfo(symbol, info))
|
|
return false;
|
|
|
|
return info.trading_allowed && info.info.SessionTrade(SYMBOL_SESSION_TRADE);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get current bid price |
|
|
//+------------------------------------------------------------------+
|
|
double CSymbolManager::GetCurrentBid(string symbol)
|
|
{
|
|
CSymbolInfo *info = GetSymbolInfoObject(symbol);
|
|
if(info != NULL)
|
|
{
|
|
info.RefreshRates();
|
|
return info.Bid();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get current ask price |
|
|
//+------------------------------------------------------------------+
|
|
double CSymbolManager::GetCurrentAsk(string symbol)
|
|
{
|
|
CSymbolInfo *info = GetSymbolInfoObject(symbol);
|
|
if(info != NULL)
|
|
{
|
|
info.RefreshRates();
|
|
return info.Ask();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get current price based on position type |
|
|
//+------------------------------------------------------------------+
|
|
double CSymbolManager::GetCurrentPrice(string symbol, ENUM_POSITION_TYPE type)
|
|
{
|
|
return (type == POSITION_TYPE_BUY) ? GetCurrentBid(symbol) : GetCurrentAsk(symbol);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Print cache statistics |
|
|
//+------------------------------------------------------------------+
|
|
void CSymbolManager::PrintStatistics()
|
|
{
|
|
Print("=== Symbol Manager Statistics ===");
|
|
Print("Cached Symbols: ", m_cache_size);
|
|
Print("Cache Hits: ", m_cache_hits);
|
|
Print("Cache Misses: ", m_cache_misses);
|
|
Print("Hit Rate: ", DoubleToString(GetCacheHitRate(), 2), "%");
|
|
|
|
//--- Most accessed symbols
|
|
Print("\nMost Accessed Symbols:");
|
|
for(int i = 0; i < MathMin(5, m_cache_size); i++)
|
|
{
|
|
int max_access = 0;
|
|
int max_index = -1;
|
|
|
|
for(int j = 0; j < m_cache_size; j++)
|
|
{
|
|
if(m_cache[j].access_count > max_access)
|
|
{
|
|
bool already_printed = false;
|
|
for(int k = 0; k < i; k++)
|
|
{
|
|
if(m_cache[j].symbol == m_cache[k].symbol)
|
|
{
|
|
already_printed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!already_printed)
|
|
{
|
|
max_access = m_cache[j].access_count;
|
|
max_index = j;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(max_index >= 0)
|
|
{
|
|
Print(" ", m_cache[max_index].symbol,
|
|
" - Accesses: ", m_cache[max_index].access_count);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // SYMBOL_MANAGER_MQH |