//+------------------------------------------------------------------+ //| ERMT_6.9-DualDash.mq5| //| Enterprise Risk Management Terminal v6.9 | //| Complete Working Implementation | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Institutional Trading Systems" #property link "https://example.com" #property version "6.903" #property description "Enterprise Risk Management Terminal - Dual Dashboard Edition" //+------------------------------------------------------------------+ //| Include Files | //+------------------------------------------------------------------+ //--- Include base MQL5 classes #include #include //--- Include system modules #include "Modules/DataTypes.mqh" #include "Modules/Utilities.mqh" #include "Modules/RiskManager.mqh" #include "Modules/TradeManager.mqh" #include "Modules/TechnicalAnalysis.mqh" #include "Modules/EntrySystem.mqh" #include "Modules/Dashboard.mqh" // Original Dashboard #include "Modules/Dashboard_SelfContained.mqh" // New Self-Contained Dashboard #include "Modules/ExternalTradeManager.mqh" //+------------------------------------------------------------------+ //| Input Parameters - Complete Set | //+------------------------------------------------------------------+ // Risk Management Parameters input double InpMaxRiskPerTrade = 1.0; // Maximum Risk Per Trade (%) input double InpMaxDailyLoss = 5.0; // Maximum Daily Loss (%) input double InpMaxDrawdown = 20.0; // Maximum Drawdown (%) input double InpMaxLeverage = 10.0; // Maximum Leverage input int InpMaxPositions = 5; // Maximum Concurrent Positions input double InpMaxCorrelation = 0.7; // Maximum Correlation Threshold // Trade Management Parameters input ENUM_TRADE_MANAGEMENT_MODE InpManagementMode = MANAGEMENT_BASIC; // Management Mode input ENUM_SL_MODE InpSLMode = SL_ATR; // Stop Loss Mode input ENUM_TP_MODE InpTPMode = TP_RISK_REWARD; // Take Profit Mode input double InpRiskRewardRatio = 2.0; // Risk/Reward Ratio input bool InpUseTrailingStop = true; // Use Trailing Stop input double InpTrailingDistance = 50; // Trailing Distance (points) input bool InpMoveToBreakeven = true; // Move to Breakeven input double InpBreakevenTrigger = 30; // Breakeven Trigger (points) // Position Sizing Parameters input ENUM_POSITION_SIZING InpSizingMethod = POSITION_SIZE_RISK; // Position Sizing Method input double InpFixedLotSize = 0.01; // Fixed Lot Size input double InpRiskPercent = 1.0; // Risk Percentage for Sizing // Entry System Parameters input ENUM_ENTRY_MODE InpEntryMode = ENTRY_MANUAL; // Entry Mode input ENUM_ENTRY_STRATEGY InpEntryStrategy = ENTRY_MA_CROSS; // Entry Strategy input int InpMAPeriodFast = 10; // Fast MA Period input int InpMAPeriodSlow = 20; // Slow MA Period input int InpMomentumPeriod = 14; // Momentum Period input double InpBreakoutThreshold = 1.5; // Breakout Threshold (ATR) input int InpMinSignalStrength = 5; // Minimum Signal Strength (1-10) // Dashboard Parameters input int InpOriginalDashboardX = 10; // Original Dashboard X Position input int InpOriginalDashboardY = 30; // Original Dashboard Y Position input int InpDualDashboardX = 350; // Dual Dashboard X Position input int InpDualDashboardY = 30; // Dual Dashboard Y Position input int InpDashboardWidth = 320; // Dashboard Width input int InpDashboardHeight = 200; // Dashboard Height input bool InpShowOriginalDashboard = true; // Show Original Dashboard input bool InpShowDualDashboard = true; // Show Dual Dashboard input ENUM_DASH_THEME InpDashboardTheme = DASH_THEME_DARK; // Dashboard Theme // System Parameters input int InpMagicNumber = 123456; // Magic Number input string InpCommentPrefix = "ERMT_"; // Trade Comment Prefix input int InpUpdateFrequency = 1; // Update Frequency (seconds) input bool InpEnableAlerts = true; // Enable Alerts input bool InpEnableNotifications = false; // Enable Push Notifications input bool InpEnableEmails = false; // Enable Email Alerts input ENUM_LOG_LEVEL InpLogLevel = LOG_INFO; // Logging Level // Time Filter Parameters input bool InpUseTimeFilter = false; // Use Time Filter input int InpStartHour = 8; // Trading Start Hour input int InpEndHour = 20; // Trading End Hour input bool InpTradeOnMonday = true; // Trade on Monday input bool InpTradeOnFriday = true; // Trade on Friday input bool InpCloseOnFriday = false; // Close All on Friday //+------------------------------------------------------------------+ //| Global Variables and Objects | //+------------------------------------------------------------------+ // Core System Objects CRiskManager g_RiskManager; // Risk manager instance CDashboardSelfContained g_Dashboard; // Original dashboard CDashboardSelfContained g_DualDashboard; // Dual dashboard CUtilities* g_pUtils; // Utilities pointer // Data Storage ManagedTrade g_ManagedTrades[]; // Array of managed trades RiskMetrics g_RiskMetrics; // Current risk metrics PerformanceMetrics g_Performance; // Performance metrics MarketConditions g_MarketConditions; // Current market conditions // System State Variables bool g_IsInitialized = false; // System initialized flag bool g_IsTrading = false; // Trading enabled flag bool g_InPosition = false; // Currently in position flag datetime g_LastUpdateTime = 0; // Last update timestamp datetime g_LastTradeTime = 0; // Last trade timestamp int g_ConsecutiveLosses = 0; // Consecutive losses counter double g_DailyProfit = 0; // Today's profit/loss double g_SessionHigh = 0; // Session high balance double g_SessionLow = 0; // Session low balance // Performance Tracking double g_StartingBalance = 0; // Starting balance double g_PeakBalance = 0; // Peak balance for DD calc int g_TotalTrades = 0; // Total trades counter int g_WinningTrades = 0; // Winning trades counter int g_LosingTrades = 0; // Losing trades counter //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Initialize logging CLogger::Init(InpLogLevel, true, true); CLogger::Log(LOG_INFO, "==========================================="); CLogger::Log(LOG_INFO, "ERMT 6.9 Dual Dashboard - Initializing"); CLogger::Log(LOG_INFO, "==========================================="); // Create utilities instance g_pUtils = new CUtilities(); if(g_pUtils == NULL) { CLogger::Log(LOG_ERROR, "Failed to create Utilities instance"); return INIT_FAILED; } // Initialize Risk Parameters RiskParameters riskParams; riskParams.maxRiskPerTrade = InpMaxRiskPerTrade; riskParams.maxDailyRisk = InpMaxDailyLoss; riskParams.maxDrawdown = InpMaxDrawdown; riskParams.maxLeverage = InpMaxLeverage; riskParams.maxPositions = InpMaxPositions; riskParams.maxCorrelation = InpMaxCorrelation; riskParams.marginCallLevel = 50.0; riskParams.stopOutLevel = 30.0; riskParams.useVolatilityAdjustment = true; riskParams.volatilityMultiplier = 1.5; riskParams.useTimeFilter = InpUseTimeFilter; riskParams.startHour = InpStartHour; riskParams.endHour = InpEndHour; riskParams.closeOnFriday = InpCloseOnFriday; riskParams.tradeOnMonday = InpTradeOnMonday; // Initialize Risk Manager if(!g_RiskManager.Initialize(g_pUtils, riskParams)) { CLogger::Log(LOG_ERROR, "Failed to initialize Risk Manager"); delete g_pUtils; return INIT_FAILED; } // Initialize Dashboards if(InpShowOriginalDashboard) { if(!g_Dashboard.Create("Original", InpOriginalDashboardX, InpOriginalDashboardY, InpDashboardWidth, InpDashboardHeight)) { CLogger::Log(LOG_WARNING, "Failed to create original dashboard"); } else { g_Dashboard.SetTheme(InpDashboardTheme); CLogger::Log(LOG_INFO, "Original dashboard created successfully"); } } if(InpShowDualDashboard) { if(!g_DualDashboard.Create("Dual", InpDualDashboardX, InpDualDashboardY, InpDashboardWidth, InpDashboardHeight)) { CLogger::Log(LOG_WARNING, "Failed to create dual dashboard"); } else { g_DualDashboard.SetTheme(InpDashboardTheme); g_DualDashboard.SetCompact(true); // Make dual dashboard compact CLogger::Log(LOG_INFO, "Dual dashboard created successfully"); } } // Initialize tracking variables g_StartingBalance = AccountInfoDouble(ACCOUNT_BALANCE); g_PeakBalance = g_StartingBalance; g_SessionHigh = g_StartingBalance; g_SessionLow = g_StartingBalance; g_LastUpdateTime = TimeCurrent(); // Initialize arrays ArrayResize(g_ManagedTrades, 0); // Set timer for updates EventSetTimer(InpUpdateFrequency); // Set initialization flag g_IsInitialized = true; g_IsTrading = true; CLogger::Log(LOG_INFO, StringFormat("Starting Balance: %.2f", g_StartingBalance)); CLogger::Log(LOG_INFO, "ERMT 6.9 Initialized Successfully"); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Kill timer EventKillTimer(); // Log final statistics LogFinalStatistics(); // Destroy dashboards if(InpShowOriginalDashboard) { g_Dashboard.Destroy(); } if(InpShowDualDashboard) { g_DualDashboard.Destroy(); } // Clean up Risk Manager g_RiskManager.Deinitialize(); // Delete utilities if(g_pUtils != NULL) { delete g_pUtils; g_pUtils = NULL; } // Log shutdown reason string reasonStr = GetDeInitReasonText(reason); CLogger::Log(LOG_INFO, "ERMT 6.9 Shutdown: " + reasonStr); // Deinitialize logger CLogger::Deinit(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(!g_IsInitialized || !g_IsTrading) return; // Check if market is open if(!IsMarketOpen()) return; // Update market conditions UpdateMarketConditions(); // Update managed trades UpdateManagedTrades(); // Check for risk events CheckRiskEvents(); // Process existing positions ProcessExistingPositions(); // Check for new entry signals (if enabled) if(InpEntryMode != ENTRY_MANUAL) { CheckEntrySignals(); } } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { if(!g_IsInitialized) return; // Update performance metrics UpdatePerformanceMetrics(); // Update risk metrics g_RiskMetrics = g_RiskManager.GetCurrentRiskMetrics(); // Update dashboards UpdateDashboards(); // Check for daily reset CheckDailyReset(); g_LastUpdateTime = TimeCurrent(); } //+------------------------------------------------------------------+ //| Update Market Conditions | //+------------------------------------------------------------------+ void UpdateMarketConditions() { g_MarketConditions.volatility = g_RiskManager.GetVolatility(_Symbol); g_MarketConditions.atr = g_RiskManager.GetATR(_Symbol, PERIOD_CURRENT, 14); g_MarketConditions.spread = (SymbolInfoDouble(_Symbol, SYMBOL_ASK) - SymbolInfoDouble(_Symbol, SYMBOL_BID)) / SymbolInfoDouble(_Symbol, SYMBOL_POINT); // Determine market condition if(g_MarketConditions.volatility > g_MarketConditions.atr * 1.5) { g_MarketConditions.condition = MARKET_VOLATILE; } else if(g_MarketConditions.volatility < g_MarketConditions.atr * 0.5) { g_MarketConditions.condition = MARKET_QUIET; } else { g_MarketConditions.condition = MARKET_RANGING; } g_MarketConditions.lastUpdate = TimeCurrent(); } //+------------------------------------------------------------------+ //| Update Managed Trades | //+------------------------------------------------------------------+ void UpdateManagedTrades() { ArrayResize(g_ManagedTrades, 0); int count = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { if(PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { ManagedTrade trade; trade.ticket = PositionGetInteger(POSITION_TICKET); trade.symbol = PositionGetString(POSITION_SYMBOL); trade.type = (int)PositionGetInteger(POSITION_TYPE); trade.volume = PositionGetDouble(POSITION_VOLUME); trade.openPrice = PositionGetDouble(POSITION_PRICE_OPEN); trade.stopLoss = PositionGetDouble(POSITION_SL); trade.takeProfit = PositionGetDouble(POSITION_TP); trade.profit = PositionGetDouble(POSITION_PROFIT); trade.commission = PositionGetDouble(POSITION_COMMISSION); trade.swap = PositionGetDouble(POSITION_SWAP); trade.openTime = (datetime)PositionGetInteger(POSITION_TIME); trade.magic = (int)PositionGetInteger(POSITION_MAGIC); trade.comment = PositionGetString(POSITION_COMMENT); trade.isManaged = true; trade.slMode = InpSLMode; trade.tpMode = InpTPMode; ArrayResize(g_ManagedTrades, count + 1); g_ManagedTrades[count] = trade; count++; } } } g_InPosition = (count > 0); } //+------------------------------------------------------------------+ //| Check Risk Events | //+------------------------------------------------------------------+ void CheckRiskEvents() { if(g_RiskManager.IsRiskLimitExceeded()) { ENUM_RISK_ACTION action = g_RiskManager.EvaluateRiskAction(); if(action != ACTION_NONE) { g_RiskManager.ExecuteRiskAction(action); // Stop trading if critical if(action == ACTION_CLOSE_ALL || action == ACTION_STOP_TRADING) { g_IsTrading = false; CloseAllPositions(); } } } } //+------------------------------------------------------------------+ //| Process Existing Positions | //+------------------------------------------------------------------+ void ProcessExistingPositions() { for(int i = 0; i < ArraySize(g_ManagedTrades); i++) { // Process trailing stop if(InpUseTrailingStop) { ProcessTrailingStop(g_ManagedTrades[i]); } // Process breakeven if(InpMoveToBreakeven && !g_ManagedTrades[i].isBreakeven) { ProcessBreakeven(g_ManagedTrades[i]); } } } //+------------------------------------------------------------------+ //| Process Trailing Stop | //+------------------------------------------------------------------+ void ProcessTrailingStop(ManagedTrade &trade) { if(!PositionSelectByTicket(trade.ticket)) return; double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT); double currentSL = PositionGetDouble(POSITION_SL); double point = SymbolInfoDouble(trade.symbol, SYMBOL_POINT); double trailDistance = InpTrailingDistance * point; bool isBuy = (trade.type == POSITION_TYPE_BUY); double newSL = 0; if(isBuy) { newSL = currentPrice - trailDistance; if(newSL > currentSL && newSL < currentPrice) { ModifyPosition(trade.ticket, newSL, trade.takeProfit); } } else { newSL = currentPrice + trailDistance; if((currentSL == 0 || newSL < currentSL) && newSL > currentPrice) { ModifyPosition(trade.ticket, newSL, trade.takeProfit); } } } //+------------------------------------------------------------------+ //| Process Breakeven | //+------------------------------------------------------------------+ void ProcessBreakeven(ManagedTrade &trade) { if(!PositionSelectByTicket(trade.ticket)) return; double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT); double openPrice = trade.openPrice; double point = SymbolInfoDouble(trade.symbol, SYMBOL_POINT); double triggerDistance = InpBreakevenTrigger * point; bool isBuy = (trade.type == POSITION_TYPE_BUY); if(isBuy) { if(currentPrice >= openPrice + triggerDistance) { double newSL = openPrice + point; // Small profit to cover costs if(trade.stopLoss < newSL) { if(ModifyPosition(trade.ticket, newSL, trade.takeProfit)) { trade.isBreakeven = true; CLogger::Log(LOG_INFO, StringFormat("Position %d moved to breakeven", trade.ticket)); } } } } else { if(currentPrice <= openPrice - triggerDistance) { double newSL = openPrice - point; if(trade.stopLoss == 0 || trade.stopLoss > newSL) { if(ModifyPosition(trade.ticket, newSL, trade.takeProfit)) { trade.isBreakeven = true; CLogger::Log(LOG_INFO, StringFormat("Position %d moved to breakeven", trade.ticket)); } } } } } //+------------------------------------------------------------------+ //| Modify Position | //+------------------------------------------------------------------+ bool ModifyPosition(ulong ticket, double sl, double tp) { MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_SLTP; request.position = ticket; request.sl = NormalizeDouble(sl, _Digits); request.tp = NormalizeDouble(tp, _Digits); if(!OrderSend(request, result)) { CLogger::Log(LOG_ERROR, StringFormat("Failed to modify position %d: %s", ticket, GetLastErrorString())); return false; } return true; } //+------------------------------------------------------------------+ //| Check Entry Signals | //+------------------------------------------------------------------+ void CheckEntrySignals() { // This is a placeholder for entry signal logic // Implement based on selected strategy if(!g_RiskManager.CanOpenNewPosition(_Symbol, 0.01)) return; // Entry logic would go here based on InpEntryStrategy } //+------------------------------------------------------------------+ //| Update Performance Metrics | //+------------------------------------------------------------------+ void UpdatePerformanceMetrics() { double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE); double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY); // Update account values g_Performance.currentBalance = currentBalance; g_Performance.currentEquity = currentEquity; g_Performance.startingBalance = g_StartingBalance; g_Performance.netProfit = currentBalance - g_StartingBalance; // Update peak balance if(currentBalance > g_PeakBalance) { g_PeakBalance = currentBalance; } g_Performance.peakBalance = g_PeakBalance; // Calculate drawdown if(g_PeakBalance > 0) { g_Performance.currentDrawdown = g_PeakBalance - currentBalance; g_Performance.currentDrawdownPercent = (g_Performance.currentDrawdown / g_PeakBalance) * 100; } // Update trade statistics g_Performance.totalTrades = g_TotalTrades; g_Performance.winningTrades = g_WinningTrades; g_Performance.losingTrades = g_LosingTrades; // Calculate win rate if(g_TotalTrades > 0) { g_Performance.winRate = ((double)g_WinningTrades / g_TotalTrades) * 100; } g_Performance.lastUpdateTime = TimeCurrent(); } //+------------------------------------------------------------------+ //| Update Dashboards | //+------------------------------------------------------------------+ void UpdateDashboards() { if(InpShowOriginalDashboard) { g_Dashboard.Update(g_RiskMetrics, g_Performance); } if(InpShowDualDashboard) { g_DualDashboard.Update(g_RiskMetrics, g_Performance); } } //+------------------------------------------------------------------+ //| Check Daily Reset | //+------------------------------------------------------------------+ void CheckDailyReset() { static datetime lastResetDate = 0; MqlDateTime current; TimeToStruct(TimeCurrent(), current); datetime currentDate = StringToTime(StringFormat("%04d.%02d.%02d", current.year, current.mon, current.day)); if(currentDate != lastResetDate) { g_DailyProfit = 0; lastResetDate = currentDate; CLogger::Log(LOG_INFO, "Daily metrics reset"); } } //+------------------------------------------------------------------+ //| Check if Market is Open | //+------------------------------------------------------------------+ bool IsMarketOpen() { if(!InpUseTimeFilter) return true; MqlDateTime current; TimeToStruct(TimeCurrent(), current); // Check day of week if(current.day_of_week == 0 || current.day_of_week == 6) return false; if(current.day_of_week == 1 && !InpTradeOnMonday) return false; if(current.day_of_week == 5 && !InpTradeOnFriday) return false; // Check time if(current.hour < InpStartHour || current.hour >= InpEndHour) return false; return true; } //+------------------------------------------------------------------+ //| Close All Positions | //+------------------------------------------------------------------+ void CloseAllPositions() { CLogger::Log(LOG_WARNING, "Closing all positions"); for(int i = PositionsTotal() - 1; i >= 0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { if(PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_DEAL; request.position = PositionGetInteger(POSITION_TICKET); request.symbol = PositionGetString(POSITION_SYMBOL); request.volume = PositionGetDouble(POSITION_VOLUME); request.type = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; request.price = (request.type == ORDER_TYPE_SELL) ? SymbolInfoDouble(request.symbol, SYMBOL_BID) : SymbolInfoDouble(request.symbol, SYMBOL_ASK); request.deviation = 10; request.magic = InpMagicNumber; OrderSend(request, result); } } } } //+------------------------------------------------------------------+ //| Get Deinit Reason Text | //+------------------------------------------------------------------+ string GetDeInitReasonText(int reason) { switch(reason) { case REASON_PROGRAM: return "Program terminated"; case REASON_REMOVE: return "Program removed from chart"; case REASON_RECOMPILE: return "Program recompiled"; case REASON_CHARTCHANGE: return "Chart symbol or period changed"; case REASON_CHARTCLOSE: return "Chart closed"; case REASON_PARAMETERS: return "Parameters changed"; case REASON_ACCOUNT: return "Account changed"; default: return "Unknown reason"; } } //+------------------------------------------------------------------+ //| Get Last Error String | //+------------------------------------------------------------------+ string GetLastErrorString() { int error = GetLastError(); return StringFormat("Error %d: %s", error, ErrorDescription(error)); } //+------------------------------------------------------------------+ //| Error Description | //+------------------------------------------------------------------+ string ErrorDescription(int error_code) { // Add common error descriptions switch(error_code) { case 0: return "No error"; case 4756: return "Trade request sending failed"; case 4757: return "Trade request canceled by timeout"; case 10004: return "Requote"; case 10006: return "Request rejected"; case 10007: return "Request canceled by trader"; case 10010: return "Only part of the request was completed"; case 10011: return "Request processing error"; case 10012: return "Request canceled by timeout"; case 10013: return "Invalid request"; case 10014: return "Invalid volume"; case 10015: return "Invalid price"; case 10016: return "Invalid stops"; case 10017: return "Trade disabled"; case 10018: return "Market closed"; case 10019: return "Not enough money"; case 10020: return "Prices changed"; case 10021: return "No quotes to process request"; case 10022: return "Invalid expiration date"; case 10023: return "Order state changed"; case 10024: return "Too frequent requests"; case 10025: return "No changes in request"; case 10026: return "Autotrading disabled by server"; case 10027: return "Autotrading disabled by client terminal"; default: return "Unknown error"; } } //+------------------------------------------------------------------+ //| Log Final Statistics | //+------------------------------------------------------------------+ void LogFinalStatistics() { double finalBalance = AccountInfoDouble(ACCOUNT_BALANCE); double totalProfit = finalBalance - g_StartingBalance; double profitPercent = (g_StartingBalance > 0) ? (totalProfit / g_StartingBalance) * 100 : 0; CLogger::Log(LOG_INFO, "==========================================="); CLogger::Log(LOG_INFO, "FINAL STATISTICS"); CLogger::Log(LOG_INFO, "==========================================="); CLogger::Log(LOG_INFO, StringFormat("Starting Balance: %.2f", g_StartingBalance)); CLogger::Log(LOG_INFO, StringFormat("Final Balance: %.2f", finalBalance)); CLogger::Log(LOG_INFO, StringFormat("Total Profit/Loss: %.2f (%.2f%%)", totalProfit, profitPercent)); CLogger::Log(LOG_INFO, StringFormat("Total Trades: %d", g_TotalTrades)); CLogger::Log(LOG_INFO, StringFormat("Winning Trades: %d", g_WinningTrades)); CLogger::Log(LOG_INFO, StringFormat("Losing Trades: %d", g_LosingTrades)); CLogger::Log(LOG_INFO, StringFormat("Win Rate: %.2f%%", g_Performance.winRate)); CLogger::Log(LOG_INFO, StringFormat("Max Drawdown: %.2f%%", g_Performance.maxDrawdownPercent)); CLogger::Log(LOG_INFO, "==========================================="); }