//+------------------------------------------------------------------+ //| 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 //+------------------------------------------------------------------+ //| 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