| //+------------------------------------------------------------------+
|
| //| AdaptiveEngine.mqh - NUCLEAR GRADE Universal Auto-Detection |
|
| //| Automatically adapts to ANY symbol and timeframe |
|
| //+------------------------------------------------------------------+
|
|
|
| //+------------------------------------------------------------------+
|
| //| Symbol Profile Structure - Comprehensive Analysis |
|
| //+------------------------------------------------------------------+
|
| struct SymbolProfile {
|
| string symbol;
|
| double avg_atr_ratio; // ATR/Price ratio for volatility scaling
|
| double avg_spread_pct; // Spread as % of price
|
| double volatility_percentile; // Current volatility vs historical
|
| double liquidity_score; // Volume-based liquidity assessment
|
| double optimal_sl_mult; // Auto-calculated optimal SL
|
| double optimal_tp_mult; // Auto-calculated optimal TP
|
| double risk_multiplier; // Auto-calculated risk adjustment
|
| double timeframe_mult; // Timeframe-specific multiplier
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| Baseline Symbol Configurations (Fallback Only) |
|
| //+------------------------------------------------------------------+
|
| struct SymbolConfig {
|
| string symbol;
|
| double base_sl_mult;
|
| double base_tp_mult;
|
| double volatility_class;
|
| double spread_threshold;
|
| };
|
|
|
| SymbolConfig g_symbol_configs[] = {
|
| {"EURUSD", 150.0, 300.0, 1.0, 0.00020},
|
| {"GBPUSD", 180.0, 360.0, 1.2, 0.00025},
|
| {"USDJPY", 120.0, 240.0, 0.8, 0.00015},
|
| {"XAUUSD", 350.0, 700.0, 3.0, 0.50},
|
| {"AUDUSD", 150.0, 300.0, 1.1, 0.00022},
|
| {"USDCAD", 140.0, 280.0, 1.0, 0.00020},
|
| {"GBPJPY", 200.0, 400.0, 1.5, 0.00030},
|
| {"EURJPY", 160.0, 320.0, 1.2, 0.00025},
|
| {"BTCUSD", 800.0, 1600.0, 5.0, 10.0},
|
| {"USOIL", 500.0, 1000.0, 2.5, 0.05},
|
| {"NGAS", 400.0, 800.0, 3.5, 0.01}
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| UNIVERSAL AUTO-DETECTION ENGINE - Works on ANY Symbol/Timeframe |
|
| //+------------------------------------------------------------------+
|
| class CUniversalDetector {
|
| private:
|
| SymbolProfile m_profile;
|
| string m_symbol;
|
| ENUM_TIMEFRAMES m_timeframe;
|
|
|
| public:
|
| CUniversalDetector() {}
|
|
|
| // Analyze any symbol and timeframe - returns optimized parameters
|
| SymbolProfile AnalyzeSymbol(const string symbol, const ENUM_TIMEFRAMES tf) {
|
| m_symbol = symbol;
|
| m_timeframe = tf;
|
|
|
| // Real-time ATR analysis
|
| int atr_handle = iATR(symbol, tf, 14);
|
| double atr_buffer[];
|
| ArraySetAsSeries(atr_buffer, true);
|
| double atr = 0.0;
|
| if(CopyBuffer(atr_handle, 0, 0, 1, atr_buffer) > 0) {
|
| atr = atr_buffer[0];
|
| }
|
| IndicatorRelease(atr_handle);
|
|
|
| // Current price for ratio calculation
|
| double price = SymbolInfoDouble(symbol, SYMBOL_BID);
|
| if(price <= 0) price = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
|
|
| m_profile.symbol = symbol;
|
| m_profile.avg_atr_ratio = (price > 0) ? (atr / price) : 0.01;
|
|
|
| // Spread analysis
|
| double spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * SymbolInfoDouble(symbol, SYMBOL_POINT);
|
| m_profile.avg_spread_pct = (price > 0) ? ((spread / price) * 100.0) : 0.001;
|
|
|
| // DEBUG: Log raw spread values
|
| PrintFormat("[DEBUG-SPREAD] %s | SpreadPoints=%d | Point=%.8f | RawSpread=%.8f | Price=%.4f | SpreadPct=%.6f",
|
| symbol,
|
| (int)SymbolInfoInteger(symbol, SYMBOL_SPREAD),
|
| SymbolInfoDouble(symbol, SYMBOL_POINT),
|
| spread,
|
| price,
|
| m_profile.avg_spread_pct);
|
|
|
| // Volatility percentile (20-period lookback)
|
| m_profile.volatility_percentile = CalculateVolatilityPercentile(symbol, tf, 20);
|
|
|
| // Liquidity score (volume-based)
|
| m_profile.liquidity_score = CalculateLiquidityScore(symbol, tf);
|
|
|
| // Timeframe multiplier
|
| m_profile.timeframe_mult = GetTimeframeMultiplier(tf);
|
|
|
| // Auto-calculate optimal parameters
|
| m_profile.optimal_sl_mult = CalculateOptimalSL(m_profile);
|
| m_profile.optimal_tp_mult = CalculateOptimalTP(m_profile);
|
| m_profile.risk_multiplier = CalculateOptimalRisk(m_profile);
|
|
|
| return m_profile;
|
| }
|
|
|
| SymbolProfile GetProfile() { return m_profile; }
|
|
|
| private:
|
| // Calculate optimal SL based on symbol characteristics
|
| double CalculateOptimalSL(const SymbolProfile& profile) {
|
| double base_sl = 150.0; // Default for EURUSD-like pairs
|
|
|
| // ATR-based scaling: higher volatility = wider SL
|
| double atr_scaling = 1.0 + (profile.avg_atr_ratio * 50.0);
|
| if(atr_scaling > 5.0) atr_scaling = 5.0;
|
|
|
| // Spread adjustment
|
| double spread_adj = MathMax(1.0, profile.avg_spread_pct * 20.0);
|
| if(spread_adj > 3.0) spread_adj = 3.0;
|
|
|
| // Timeframe scaling
|
| double tf_mult = profile.timeframe_mult;
|
|
|
| // Combined calculation
|
| double optimal_sl = base_sl * atr_scaling * spread_adj * tf_mult;
|
|
|
| // Clamp to reasonable range (50-2000 pips)
|
| return MathMax(50.0, MathMin(2000.0, optimal_sl));
|
| }
|
|
|
| // Calculate optimal TP based on SL and volatility
|
| double CalculateOptimalTP(const SymbolProfile& profile) {
|
| double sl = CalculateOptimalSL(profile);
|
| double base_ratio = 2.0; // TP/SL ratio
|
|
|
| // Volatility bonus: higher volatility = higher TP potential
|
| double volatility_bonus = MapRange(profile.volatility_percentile, 0, 100, 0.0, 1.0);
|
|
|
| // Liquidity bonus: higher liquidity = tighter TP
|
| double liquidity_bonus = MapRange(profile.liquidity_score, 0, 100, 0.5, 0.0);
|
|
|
| double tp_ratio = base_ratio + volatility_bonus + liquidity_bonus;
|
| return sl * tp_ratio;
|
| }
|
|
|
| // Calculate optimal risk multiplier (AGGRESSIVE profile)
|
| double CalculateOptimalRisk(const SymbolProfile& profile) {
|
| double base_risk = 1.5; // AGGRESSIVE: 1.5%
|
|
|
| // Reduce risk for high volatility
|
| double volatility_penalty = MapRange(profile.volatility_percentile, 0, 100, 1.2, 0.6);
|
|
|
| // Reduce risk for wide spreads
|
| double spread_penalty = MapRange(profile.avg_spread_pct, 0, 0.1, 1.0, 0.5);
|
| if(spread_penalty < 0.5) spread_penalty = 0.5;
|
|
|
| double optimal_risk = base_risk * volatility_penalty * spread_penalty;
|
|
|
| // Clamp to AGGRESSIVE range (0.5% - 3.0%)
|
| return MathMax(0.5, MathMin(3.0, optimal_risk));
|
| }
|
|
|
| // Calculate volatility percentile
|
| double CalculateVolatilityPercentile(const string symbol, const ENUM_TIMEFRAMES tf, const int lookback) {
|
| int atr_handle = iATR(symbol, tf, 14);
|
| double atr_values[];
|
| ArraySetAsSeries(atr_values, true);
|
|
|
| if(CopyBuffer(atr_handle, 0, 0, lookback, atr_values) < lookback) {
|
| IndicatorRelease(atr_handle);
|
| return 50.0; // Default to median
|
| }
|
|
|
| double current_atr = atr_values[0];
|
| int count_below = 0;
|
|
|
| for(int i = 0; i < lookback; i++) {
|
| if(atr_values[i] <= current_atr) count_below++;
|
| }
|
|
|
| IndicatorRelease(atr_handle);
|
| return (double)count_below / (double)lookback * 100.0;
|
| }
|
|
|
| // Calculate liquidity score based on volume
|
| double CalculateLiquidityScore(const string symbol, const ENUM_TIMEFRAMES tf) {
|
| long volume[];
|
| ArraySetAsSeries(volume, true);
|
|
|
| if(CopyTickVolume(symbol, tf, 0, 20, volume) < 20) {
|
| return 50.0; // Default to median
|
| }
|
|
|
| double avg_volume = 0.0;
|
| for(int i = 0; i < 20; i++) {
|
| avg_volume += (double)volume[i];
|
| }
|
| avg_volume /= 20.0;
|
|
|
| double current_volume = (double)volume[0];
|
| double ratio = (avg_volume > 0) ? (current_volume / avg_volume) : 1.0;
|
|
|
| return MathMin(100.0, ratio * 50.0);
|
| }
|
|
|
| // Timeframe multiplier
|
| double GetTimeframeMultiplier(const ENUM_TIMEFRAMES tf) {
|
| switch(tf) {
|
| case PERIOD_M1: return 0.3;
|
| case PERIOD_M5: return 0.5;
|
| case PERIOD_M15: return 0.75;
|
| case PERIOD_M30: return 1.0;
|
| case PERIOD_H1: return 1.5;
|
| case PERIOD_H4: return 2.5;
|
| case PERIOD_D1: return 4.0;
|
| case PERIOD_W1: return 8.0;
|
| default: return 1.0;
|
| }
|
| }
|
|
|
| // Map value from input range to output range
|
| double MapRange(const double value, const double in_min, const double in_max,
|
| const double out_min, const double out_max) {
|
| if(in_max == in_min) return out_min;
|
| double mapped = out_min + (value - in_min) * (out_max - out_min) / (in_max - in_min);
|
| return MathMax(out_min, MathMin(out_max, mapped));
|
| }
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| LIVE ADAPTATION ENGINE - Evolves Parameters Based on Performance|
|
| //+------------------------------------------------------------------+
|
| class CLiveAdaptation {
|
| private:
|
| struct PerformanceMetrics {
|
| double rolling_pf; // Rolling profit factor
|
| double rolling_wr; // Rolling win rate
|
| double rolling_dd; // Rolling max drawdown
|
| double rolling_vol; // Rolling volatility
|
| int trade_count; // Total trades
|
| datetime last_update; // Last update time
|
| };
|
|
|
| PerformanceMetrics m_metrics;
|
|
|
| public:
|
| CLiveAdaptation() {
|
| m_metrics.rolling_pf = 1.0;
|
| m_metrics.rolling_wr = 0.5;
|
| m_metrics.rolling_dd = 0.0;
|
| m_metrics.rolling_vol = 1.0;
|
| m_metrics.trade_count = 0;
|
| m_metrics.last_update = 0;
|
| }
|
|
|
| // Update from live trading performance
|
| void UpdateFromLiveTrading(const double pf, const double wr, const double dd, const double vol) {
|
| m_metrics.rolling_pf = pf;
|
| m_metrics.rolling_wr = wr;
|
| m_metrics.rolling_dd = dd;
|
| m_metrics.rolling_vol = vol;
|
| m_metrics.trade_count++;
|
| m_metrics.last_update = TimeCurrent();
|
| }
|
|
|
| // Calculate adaptive adjustment multiplier
|
| double CalculateAdaptiveAdjustment() {
|
| if(m_metrics.trade_count < 10) return 1.0; // Need minimum sample
|
|
|
| // Multi-factor adjustment
|
| double pf_component = MapRange(m_metrics.rolling_pf, 1.9, 3.0, 0.8, 1.2);
|
| double wr_component = MapRange(m_metrics.rolling_wr, 0.5, 1.0, 0.9, 1.1);
|
| double dd_component = MapRange(m_metrics.rolling_dd, 0.0, 0.05, 1.2, 0.7);
|
| double vol_component = MapRange(m_metrics.rolling_vol, 0.5, 2.0, 1.1, 0.9);
|
|
|
| // Weighted average
|
| double adjustment = (pf_component * 0.4 + wr_component * 0.3 + dd_component * 0.2 + vol_component * 0.1);
|
|
|
| // Clamp to reasonable range (0.7 - 1.3)
|
| return MathMax(0.7, MathMin(1.3, adjustment));
|
| }
|
|
|
| double GetProfitFactor() { return m_metrics.rolling_pf; }
|
| double GetWinRate() { return m_metrics.rolling_wr; }
|
| double GetDrawdown() { return m_metrics.rolling_dd; }
|
| int GetTradeCount() { return m_metrics.trade_count; }
|
|
|
| private:
|
| double MapRange(const double value, const double in_min, const double in_max,
|
| const double out_min, const double out_max) {
|
| if(in_max == in_min) return out_min;
|
| double mapped = out_min + (value - in_min) * (out_max - out_min) / (in_max - in_min);
|
| return MathMax(out_min, MathMin(out_max, mapped));
|
| }
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| ENHANCED ADAPTIVE ENGINE - Integration Layer |
|
| //+------------------------------------------------------------------+
|
| class CAdaptiveEngine {
|
| private:
|
| double m_current_atr;
|
| double m_current_spread;
|
| double m_volatility_percentile;
|
| double m_spread_percentile;
|
| CUniversalDetector m_detector;
|
| SymbolProfile m_current_profile;
|
|
|
| public:
|
| CAdaptiveEngine() {
|
| m_current_atr = 0.0;
|
| m_current_spread = 0.0;
|
| m_volatility_percentile = 50.0;
|
| m_spread_percentile = 50.0;
|
| }
|
|
|
| // Calculate symbol-specific multipliers using UNIVERSAL DETECTOR
|
| void CalculateDynamicParameters(const string symbol, const ENUM_TIMEFRAMES timeframe) {
|
| // Use nuclear-grade universal detector
|
| m_current_profile = m_detector.AnalyzeSymbol(symbol, timeframe);
|
|
|
| // Legacy compatibility - extract key metrics
|
| int atr_handle = iATR(symbol, timeframe, 14);
|
| double atr_buffer[];
|
| ArraySetAsSeries(atr_buffer, true);
|
| if(CopyBuffer(atr_handle, 0, 0, 1, atr_buffer) > 0) {
|
| m_current_atr = atr_buffer[0];
|
| }
|
| IndicatorRelease(atr_handle);
|
|
|
| m_current_spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * SymbolInfoDouble(symbol, SYMBOL_POINT);
|
| m_volatility_percentile = m_current_profile.volatility_percentile;
|
| m_spread_percentile = m_current_profile.avg_spread_pct;
|
|
|
| // Log auto-detection results
|
| PrintFormat("🎯 [AUTO-DETECT] %s %s | SL=%.1f TP=%.1f Risk=%.2f%% | ATR Ratio=%.4f Spread=%.3f%% Vol=%d%%",
|
| symbol, EnumToString(timeframe),
|
| m_current_profile.optimal_sl_mult,
|
| m_current_profile.optimal_tp_mult,
|
| m_current_profile.risk_multiplier,
|
| m_current_profile.avg_atr_ratio,
|
| m_current_profile.avg_spread_pct * 100,
|
| (int)m_current_profile.volatility_percentile);
|
| }
|
|
|
| // Get adjusted stop loss in pips - USES UNIVERSAL DETECTOR RESULTS
|
| double GetDynamicSL(const string symbol, const ENUM_TIMEFRAMES timeframe) {
|
| // Return optimal SL calculated by universal detector
|
| if(m_current_profile.symbol == symbol) {
|
| return m_current_profile.optimal_sl_mult;
|
| }
|
|
|
| // Fallback: recalculate
|
| SymbolProfile profile = m_detector.AnalyzeSymbol(symbol, timeframe);
|
| return profile.optimal_sl_mult;
|
| }
|
|
|
| // Get adjusted take profit in pips - USES UNIVERSAL DETECTOR RESULTS
|
| double GetDynamicTP(const string symbol, const ENUM_TIMEFRAMES timeframe) {
|
| // Return optimal TP calculated by universal detector
|
| if(m_current_profile.symbol == symbol) {
|
| return m_current_profile.optimal_tp_mult;
|
| }
|
|
|
| // Fallback: recalculate
|
| SymbolProfile profile = m_detector.AnalyzeSymbol(symbol, timeframe);
|
| return profile.optimal_tp_mult;
|
| }
|
|
|
| // Get risk-adjusted position size - USES UNIVERSAL DETECTOR RESULTS
|
| double GetDynamicRisk(const string risk_profile) {
|
| // Use optimal risk from universal detector (already tuned for AGGRESSIVE)
|
| if(m_current_profile.symbol != "") {
|
| return m_current_profile.risk_multiplier;
|
| }
|
|
|
| // Fallback to manual calculation
|
| double base_risk = 1.0;
|
| if(risk_profile == "CONSERVATIVE") base_risk = 0.5;
|
| else if(risk_profile == "MODERATE") base_risk = 1.0;
|
| else if(risk_profile == "AGGRESSIVE") base_risk = 1.5;
|
|
|
| // Reduce risk in high volatility
|
| double volatility_penalty = MapRange(m_volatility_percentile, 0, 100, 1.2, 0.6);
|
|
|
| return base_risk * volatility_penalty;
|
| }
|
|
|
| // Get current symbol profile
|
| SymbolProfile GetCurrentProfile() {
|
| return m_current_profile;
|
| }
|
|
|
| private:
|
| double GetSymbolBaseSL(const string symbol) {
|
| for(int i = 0; i < ArraySize(g_symbol_configs); i++) {
|
| if(StringFind(symbol, g_symbol_configs[i].symbol) >= 0) {
|
| return g_symbol_configs[i].base_sl_mult;
|
| }
|
| }
|
| return 10.0; // Default fallback
|
| }
|
|
|
| double GetSymbolBaseTP(const string symbol) {
|
| for(int i = 0; i < ArraySize(g_symbol_configs); i++) {
|
| if(StringFind(symbol, g_symbol_configs[i].symbol) >= 0) {
|
| return g_symbol_configs[i].base_tp_mult;
|
| }
|
| }
|
| return 20.0; // Default fallback
|
| }
|
|
|
| double GetTimeframeMultiplier(const int timeframe) {
|
| switch(timeframe) {
|
| case PERIOD_M1: return 0.25;
|
| case PERIOD_M5: return 0.5;
|
| case PERIOD_M15: return 0.75;
|
| case PERIOD_M30: return 1.0;
|
| case PERIOD_H1: return 1.5;
|
| case PERIOD_H4: return 2.5;
|
| case PERIOD_D1: return 5.0;
|
| default: return 1.0;
|
| }
|
| }
|
|
|
| double CalculateATRPercentile(const string symbol, const ENUM_TIMEFRAMES tf, const int lookback) {
|
| int atr_handle = iATR(symbol, tf, 14);
|
| double atr_values[];
|
| ArraySetAsSeries(atr_values, true);
|
|
|
| if(CopyBuffer(atr_handle, 0, 0, lookback, atr_values) < lookback) {
|
| IndicatorRelease(atr_handle);
|
| return 50.0; // Default to median on error
|
| }
|
|
|
| double current_atr = atr_values[0];
|
| IndicatorRelease(atr_handle);
|
|
|
| return CalculatePercentile(atr_values, current_atr);
|
| }
|
|
|
| double CalculateSpreadPercentile(const string symbol, const int lookback) {
|
| double spread_values[];
|
| ArrayResize(spread_values, lookback);
|
|
|
| for(int i = 0; i < lookback; i++) {
|
| spread_values[i] = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * SymbolInfoDouble(symbol, SYMBOL_POINT);
|
| }
|
|
|
| return CalculatePercentile(spread_values,
|
| SymbolInfoInteger(symbol, SYMBOL_SPREAD) * SymbolInfoDouble(symbol, SYMBOL_POINT));
|
| }
|
|
|
| double CalculatePercentile(const double &values[], const double current_value) {
|
| int count = 0;
|
| for(int i = 0; i < ArraySize(values); i++) {
|
| if(values[i] <= current_value) count++;
|
| }
|
| return (double)count / ArraySize(values) * 100.0;
|
| }
|
|
|
| double MapRange(const double value, const double in_min, const double in_max,
|
| const double out_min, const double out_max) {
|
| return out_min + (value - in_min) * (out_max - out_min) / (in_max - in_min);
|
| }
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| Machine Learning Adaptation Engine |
|
| //+------------------------------------------------------------------+
|
| class CMLAdaptationEngine {
|
| private:
|
| double m_recent_pf;
|
| double m_recent_wr;
|
| double m_recent_dd;
|
| double m_recent_vol;
|
|
|
| public:
|
| CMLAdaptationEngine() {
|
| m_recent_pf = 1.2;
|
| m_recent_wr = 0.5;
|
| m_recent_dd = 0.1;
|
| m_recent_vol = 1.0;
|
| }
|
|
|
| // Calculate ML-based parameter adjustment
|
| double CalculateAdjustment(const double profit_factor, const double win_rate,
|
| const double max_drawdown, const double volatility) {
|
|
|
| // Performance score (0-100)
|
| double perf_score = 0.0;
|
| perf_score += profit_factor * 25.0; // Max 50
|
| perf_score += win_rate * 50.0; // Max 50
|
| perf_score -= max_drawdown * 200.0; // Penalty for drawdown
|
| perf_score = MathMax(0.0, MathMin(100.0, perf_score));
|
|
|
| // Volatility adjustment factor
|
| double vol_factor = MapRange(volatility, 0.5, 3.0, 1.2, 0.8);
|
|
|
| // Combined adjustment
|
| double adjustment = 1.0 + (perf_score - 50.0) / 100.0 * vol_factor;
|
| return MathMax(0.5, MathMin(2.0, adjustment));
|
| }
|
|
|
| // Get recent performance metrics
|
| double GetRecentProfitFactor(const int lookback_trades) {
|
| // Implementation would query KnowledgeBase or trade history
|
| return 1.2 + (MathRand() % 100 - 50) / 500.0; // Simulated: 1.0 to 1.4
|
| }
|
|
|
| double GetRecentWinRate(const int lookback_trades) {
|
| // Implementation would query KnowledgeBase or trade history
|
| return 0.45 + (MathRand() % 20) / 100.0; // Simulated: 0.45 to 0.65
|
| }
|
|
|
| double GetRecentMaxDrawdown(const int lookback_trades) {
|
| // Implementation would query KnowledgeBase or trade history
|
| return 0.05 + (MathRand() % 15) / 100.0; // Simulated: 0.05 to 0.20
|
| }
|
|
|
| double GetRecentVolatility(const int lookback_bars) {
|
| // Calculate recent volatility from price data
|
| return 1.0 + (MathRand() % 100 - 50) / 100.0; // Simulated: 0.5 to 1.5
|
| }
|
|
|
| private:
|
| double MapRange(const double value, const double in_min, const double in_max,
|
| const double out_min, const double out_max) {
|
| if(in_max == in_min) return out_min;
|
| double mapped = out_min + (value - in_min) * (out_max - out_min) / (in_max - in_min);
|
| return MathMax(out_min, MathMin(out_max, mapped));
|
| }
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| Helper Functions |
|
| //+------------------------------------------------------------------+
|
| double GetSymbolSLMultiplier(const string symbol) {
|
| for(int i = 0; i < ArraySize(g_symbol_configs); i++) {
|
| if(StringFind(symbol, g_symbol_configs[i].symbol) >= 0) {
|
| return g_symbol_configs[i].base_sl_mult;
|
| }
|
| }
|
| return 10.0; // Default fallback
|
| }
|
|
|
| double GetSymbolTPMultiplier(const string symbol) {
|
| for(int i = 0; i < ArraySize(g_symbol_configs); i++) {
|
| if(StringFind(symbol, g_symbol_configs[i].symbol) >= 0) {
|
| return g_symbol_configs[i].base_tp_mult;
|
| }
|
| }
|
| return 20.0; // Default fallback
|
| }
|