//+------------------------------------------------------------------+ //| Real-Time Entropy.mq5 | //| Git, Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/en/users/johnhlomohang/ | //+------------------------------------------------------------------+ #property copyright "Git, Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/johnhlomohang/" #property version "1.00" #include #include //--- Input parameters input string ServerURL = "http://127.0.0.1:5000/predict"; // Flask endpoint input int WindowSize = 50; // Number of prices in window input int TickCollection = 50; // Number of ticks to collect input double LotSize = 0.03; // Base lot size input int BaseStopLossPoints = 600; // Base SL in points input int BaseTakeProfitPoints = 2555; // Base TP in points input int MagicNumber = 20240417; // Magic number input int RequestTimeout = 3000; // WebRequest timeout input int MinMillisecondsBetweenRequests = 500; // Throttle requests input bool AdaptiveRiskEnabled = true; // Adaptive position sizing input double MinConfidenceThreshold = 0.4; // Minimum confidence to trade input bool AllowReversal = true; // Allow position reversal input int MinMinutesBetweenTrades = 5; // Minimum minutes between trades input int MaxPositions = 1; // Maximum concurrent positions input int SignalDebounceTicks = 10; // Ticks to wait before repeating same signal //--- Global objects CTrade TradeManager; CPositionInfo PositionInfo; MqlTick currentTick; MqlTick tickArray[]; // Rolling tick history double priceWindow[]; // Rolling price window datetime lastRequestTime = 0; datetime lastTradeTime = 0; string currentSignal = "HOLD"; string previousSignal = "HOLD"; string currentRegime = "NORMAL"; double currentConfidence = 0.0; double currentVolMultiplier = 1.0; double currentEntropy = 0.0; int tickCount = 0; int signalRepeatCount = 0; // Track same signal repetitions int positionCount = 0; // Current number of positions ulong openPositionTicket = 0; bool silentMode = false; datetime silentModeStart = 0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { TradeManager.SetExpertMagicNumber(MagicNumber); TradeManager.SetAsyncMode(false); TradeManager.SetDeviationInPoints(10); //--- Initialize arrays ArrayResize(tickArray, TickCollection); ArrayResize(priceWindow, WindowSize); //--- Fill initial data InitializeHistory(); CheckSymbolProperties(); CountCurrentPositions(); Print("╔══════════════════════════════════════════════════════════════╗"); Print("║ REAL-TIME ENTROPY INITIALIZED ║"); Print("╠══════════════════════════════════════════════════════════════╣"); Print("║ Symbol: ", _Symbol); Print("║ Window Size: ", WindowSize, " ticks"); Print("║ Min Minutes Between Trades: ", MinMinutesBetweenTrades); Print("║ Max Positions: ", MaxPositions); Print("║ Current Positions: ", positionCount); Print("║ Adaptive Risk: ", AdaptiveRiskEnabled ? "ENABLED" : "DISABLED"); Print("╚══════════════════════════════════════════════════════════════╝"); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Clear comment from chart Comment(""); //--- Remove all objects created by this EA CleanupChartObjects(); } //+------------------------------------------------------------------+ //| Count current positions for this symbol/magic | //+------------------------------------------------------------------+ void CountCurrentPositions() { positionCount = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { if(PositionInfo.SelectByIndex(i)) { if(PositionInfo.Symbol() == _Symbol && PositionInfo.Magic() == MagicNumber) positionCount++; } } } //+------------------------------------------------------------------+ //| Check if we can open a new position | //+------------------------------------------------------------------+ bool CanOpenNewPosition() { CountCurrentPositions(); //--- Check max positions limit if(positionCount >= MaxPositions) { //--- Only log once per minute to avoid spam static datetime lastLog = 0; if(TimeCurrent() - lastLog > 60) { Print("Max positions (", MaxPositions, ") reached. Skipping trade."); lastLog = TimeCurrent(); } return false; } return true; } //+------------------------------------------------------------------+ //| Check if position exists in same direction | //+------------------------------------------------------------------+ bool HasPositionInDirection(ENUM_ORDER_TYPE orderType) { ENUM_POSITION_TYPE posType = (orderType == ORDER_TYPE_BUY) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL; for(int i = PositionsTotal() - 1; i >= 0; i--) { if(PositionInfo.SelectByIndex(i)) { if(PositionInfo.Symbol() == _Symbol && PositionInfo.Magic() == MagicNumber && PositionInfo.PositionType() == posType) { return true; } } } return false; } //+------------------------------------------------------------------+ //| Check if position exists in opposite direction | //+------------------------------------------------------------------+ bool HasPositionInOppositeDirection(ENUM_ORDER_TYPE orderType) { ENUM_POSITION_TYPE oppositeType = (orderType == ORDER_TYPE_BUY) ? POSITION_TYPE_SELL : POSITION_TYPE_BUY; for(int i = PositionsTotal() - 1; i >= 0; i--) { if(PositionInfo.SelectByIndex(i)) { if(PositionInfo.Symbol() == _Symbol && PositionInfo.Magic() == MagicNumber && PositionInfo.PositionType() == oppositeType) { return true; } } } return false; } //+------------------------------------------------------------------+ //| Close all positions in opposite direction | //+------------------------------------------------------------------+ void CloseOppositePositions(ENUM_ORDER_TYPE newOrderType) { ENUM_POSITION_TYPE oppositeType = (newOrderType == ORDER_TYPE_BUY) ? POSITION_TYPE_SELL : POSITION_TYPE_BUY; for(int i = PositionsTotal() - 1; i >= 0; i--) { if(PositionInfo.SelectByIndex(i)) { if(PositionInfo.Symbol() == _Symbol && PositionInfo.Magic() == MagicNumber && PositionInfo.PositionType() == oppositeType) { ulong ticket = PositionInfo.Ticket(); if(TradeManager.PositionClose(ticket)) { Print("Closed opposite position. Ticket: ", ticket); } } } } } //+------------------------------------------------------------------+ //| Check symbol properties | //+------------------------------------------------------------------+ void CheckSymbolProperties() { double volMin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double volMax = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); double volStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); int stopLevel = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); Print("╔══════════════════════════════════════════════════════════════╗"); Print("║ SYMBOL PROPERTIES ║"); Print("╠══════════════════════════════════════════════════════════════╣"); Print("║ Min Volume: ", volMin); Print("║ Max Volume: ", volMax); Print("║ Volume Step: ", volStep); Print("║ Stop Level: ", stopLevel, " points"); Print("║ Base Lot Size: ", LotSize); Print("║ Validated Lot: ", NormalizeDouble(MathFloor(LotSize / volStep) * volStep, 2)); Print("╚══════════════════════════════════════════════════════════════╝"); } //+------------------------------------------------------------------+ //| Initialize historical data | //+------------------------------------------------------------------+ void InitializeHistory() { for(int i = 0; i < TickCollection; i++) { SymbolInfoTick(_Symbol, tickArray[i]); Sleep(10); } UpdatePriceWindow(); } //+------------------------------------------------------------------+ //| Update price window from tick array | //+------------------------------------------------------------------+ void UpdatePriceWindow() { int validTicks = 0; for(int i = 0; i < TickCollection; i++) { if(tickArray[i].time > 0 && tickArray[i].bid > 0) validTicks++; } if(validTicks < WindowSize) { for(int i = 0; i < WindowSize; i++) { if(i < validTicks) priceWindow[i] = tickArray[TickCollection - 1 - i].bid; else priceWindow[i] = iClose(_Symbol, PERIOD_H1, i - validTicks); } } else { for(int i = 0; i < WindowSize; i++) { priceWindow[i] = tickArray[TickCollection - 1 - i].bid; } } } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Get current tick if(!SymbolInfoTick(_Symbol, currentTick)) return; //--- Update tick array (shift left, add new at end) for(int i = 0; i < TickCollection - 1; i++) tickArray[i] = tickArray[i + 1]; tickArray[TickCollection - 1] = currentTick; tickCount++; //--- Update display UpdateDisplay(); //--- Throttle requests if(GetTickCount() - lastRequestTime < MinMillisecondsBetweenRequests) return; //--- Update price window UpdatePriceWindow(); //--- Calculate RSI double rsi = CalculateTickRSI(); //--- Build and send request string json = BuildJsonPayload(priceWindow, rsi); string response = SendRequest(json); if(response == "") return; //--- Parse response string oldSignal = currentSignal; if(!ParseServerResponse(response)) return; //--- Count positions CountCurrentPositions(); // --- SMART LOGGING (Silent Mode) --- if(positionCount > 0 && currentSignal == oldSignal && currentSignal != "HOLD") { //--- Same signal while position exists - SILENT MODE if(!silentMode) { silentMode = true; silentModeStart = TimeCurrent(); Print(" Silent mode activated - position open, signal unchanged"); } } else { if(silentMode) { silentMode = false; Print(" Silent mode deactivated after ", TimeCurrent() - silentModeStart, " seconds"); } //--- Normal logging PrintFormat("[TICK #%d] Signal: %s | Regime: %s | Conf: %.3f | Entropy: %.3f | Positions: %d", tickCount, currentSignal, currentRegime, currentConfidence, currentEntropy, positionCount); } //--- Check confidence threshold if(currentConfidence < MinConfidenceThreshold) { Comment("Signal: ", currentSignal, " | Confidence LOW: ", DoubleToString(currentConfidence, 3)); lastRequestTime = GetTickCount(); return; } //--- Check cooldown period if(lastTradeTime > 0 && TimeCurrent() - lastTradeTime < MinMinutesBetweenTrades * 60) { int secondsLeft = MinMinutesBetweenTrades * 60 - (int)(TimeCurrent() - lastTradeTime); Comment("Signal: ", currentSignal, " | COOLDOWN: ", secondsLeft, "s remaining"); lastRequestTime = GetTickCount(); return; } //--- Process signal ProcessSignal(); lastRequestTime = GetTickCount(); previousSignal = currentSignal; } //+------------------------------------------------------------------+ //| Calculate RSI from tick data | //+------------------------------------------------------------------+ double CalculateTickRSI() { int period = 14; int size = ArraySize(priceWindow); if(size < period + 1) return 50.0; double avgGain = 0, avgLoss = 0; for(int i = 1; i <= period; i++) { double change = priceWindow[size - i] - priceWindow[size - i - 1]; if(change > 0) avgGain += change; else avgLoss -= change; } avgGain /= period; avgLoss /= period; if(avgLoss == 0) return 100.0; double rs = avgGain / avgLoss; return 100.0 - (100.0 / (1.0 + rs)); } //+------------------------------------------------------------------+ //| Build JSON payload | //+------------------------------------------------------------------+ string BuildJsonPayload(const double &prices[], double rsi) { string json = "{\"prices\":["; int size = ArraySize(prices); for(int i = 0; i < size; i++) { json += DoubleToString(prices[i], _Digits); if(i < size - 1) json += ","; } json += "],\"rsi\":" + DoubleToString(rsi, 2) + "}"; return json; } //+------------------------------------------------------------------+ //| Send HTTP POST request | //+------------------------------------------------------------------+ string SendRequest(string json) { char postData[], resultData[]; int len = StringLen(json); ArrayResize(postData, len); for(int i = 0; i < len; i++) postData[i] = (char)StringGetCharacter(json, i); string headers = "Content-Type: application/json\r\n"; string responseHeaders; int res = WebRequest("POST", ServerURL, headers, RequestTimeout, postData, resultData, responseHeaders); if(res == -1 || res != 200) return ""; return CharArrayToString(resultData, 0, WHOLE_ARRAY, CP_UTF8); } //+------------------------------------------------------------------+ //| Parse JSON response | //+------------------------------------------------------------------+ bool ParseServerResponse(string json) { currentSignal = ExtractStringValue(json, "signal"); currentRegime = ExtractStringValue(json, "regime"); currentConfidence = ExtractDoubleValue(json, "confidence"); currentVolMultiplier = ExtractDoubleValue(json, "volatility_multiplier"); currentEntropy = ExtractDoubleValue(json, "entropy"); return (currentSignal != ""); } //+------------------------------------------------------------------+ //| Extract string value from JSON | //+------------------------------------------------------------------+ string ExtractStringValue(string json, string key) { string searchKey = "\"" + key + "\":"; int start = StringFind(json, searchKey); if(start >= 0) { start += StringLen(searchKey); while(start < StringLen(json) && (StringGetCharacter(json, start) == ' ' || StringGetCharacter(json, start) == '\t')) start++; if(StringGetCharacter(json, start) == '\"') { start++; int end = start; while(end < StringLen(json) && StringGetCharacter(json, end) != '\"') end++; if(end > start) return StringSubstr(json, start, end - start); } } return ""; } //+------------------------------------------------------------------+ //| Extract double value from JSON | //+------------------------------------------------------------------+ double ExtractDoubleValue(string json, string key) { string searchKey = "\"" + key + "\":"; int start = StringFind(json, searchKey); if(start >= 0) { start += StringLen(searchKey); while(start < StringLen(json) && (StringGetCharacter(json, start) == ' ' || StringGetCharacter(json, start) == '\t')) start++; int end = start; while(end < StringLen(json)) { ushort ch = StringGetCharacter(json, end); if(ch == ',' || ch == '}' || ch == ' ' || ch == '\n' || ch == '\r') break; end++; } if(end > start) return StringToDouble(StringSubstr(json, start, end - start)); } return 0.0; } //+------------------------------------------------------------------+ //| Process trading signal with position checks | //+------------------------------------------------------------------+ void ProcessSignal() { if(currentSignal == "HOLD") { //--- Manage open position (update SL/TP if regime changed) if(positionCount > 0) ManageOpenPosition(); return; } ENUM_ORDER_TYPE desiredOrder = (currentSignal == "BUY") ? ORDER_TYPE_BUY : ORDER_TYPE_SELL; // --- POSITION CHECKS --- //--- Check if we can open a new position if(!CanOpenNewPosition() && !HasPositionInOppositeDirection(desiredOrder)) { if(!silentMode) Print("Cannot open new position - max positions reached"); //--- Still manage existing position if(positionCount > 0) ManageOpenPosition(); return; } //--- Check if we already have a position in the SAME direction if(HasPositionInDirection(desiredOrder)) { //--- Manage existing position (update SL/TP) ManageOpenPosition(); if(!silentMode) { static datetime lastSameLog = 0; if(TimeCurrent() - lastSameLog > 60) { Print("Already have ", currentSignal, " position. Managing..."); lastSameLog = TimeCurrent(); } } return; } //--- Check for OPPOSITE position (reversal) if(HasPositionInOppositeDirection(desiredOrder)) { if(AllowReversal && currentConfidence > 0.6) { Print("REVERSAL SIGNAL - Closing opposite position(s)"); CloseOppositePositions(desiredOrder); Sleep(500); ExecuteAdaptiveTrade(desiredOrder, "TickEntropy REVERSE " + currentSignal); lastTradeTime = TimeCurrent(); openPositionTicket = TradeManager.ResultOrder(); } else { //--- Manage existing position despite opposite signal ManageOpenPosition(); if(!silentMode) Print("Reversal signal but confidence too low. Managing existing position."); } return; } //--- NO POSITION --- if(positionCount == 0) { if(lastTradeTime > 0 && TimeCurrent() - lastTradeTime < MinMinutesBetweenTrades * 60) { if(!silentMode) { int secondsLeft = MinMinutesBetweenTrades * 60 - (int)(TimeCurrent() - lastTradeTime); Print("Cooldown active: ", secondsLeft, "s remaining"); } return; } if(!CanOpenNewPosition()) return; Print("Opening NEW position: ", currentSignal); ExecuteAdaptiveTrade(desiredOrder, "TickEntropy " + currentSignal); lastTradeTime = TimeCurrent(); openPositionTicket = TradeManager.ResultOrder(); return; } } //+------------------------------------------------------------------+ //| Execute adaptive trade with proper lot size rounding | //+------------------------------------------------------------------+ void ExecuteAdaptiveTrade(ENUM_ORDER_TYPE orderType, string comment) { if(!SymbolInfoTick(_Symbol, currentTick)) return; //--- Get symbol volume properties double volumeMin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double volumeMax = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); double volumeStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); double adaptiveLot = LotSize; int adaptiveSL = BaseStopLossPoints; int adaptiveTP = BaseTakeProfitPoints; if(AdaptiveRiskEnabled) { if(currentRegime == "EXTREME_VOLATILITY") adaptiveLot = LotSize * 0.3; else if(currentRegime == "HIGH_VOLATILITY") adaptiveLot = LotSize * 0.6; else if(currentRegime == "LOW_VOLATILITY") adaptiveLot = LotSize * 1.3; adaptiveSL = (int)(BaseStopLossPoints * currentVolMultiplier); adaptiveTP = (int)(BaseTakeProfitPoints * currentVolMultiplier); } //--- Proper lot size rounding adaptiveLot = MathFloor(adaptiveLot / volumeStep) * volumeStep; adaptiveLot = MathMax(volumeMin, MathMin(volumeMax, adaptiveLot)); adaptiveLot = NormalizeDouble(adaptiveLot, 2); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); double stopLoss = 0.0, takeProfit = 0.0; double priceOpen = 0.0; if(orderType == ORDER_TYPE_BUY) { priceOpen = currentTick.ask; stopLoss = NormalizeDouble(currentTick.bid - adaptiveSL * point, digits); takeProfit = NormalizeDouble(currentTick.ask + adaptiveTP * point, digits); } else { priceOpen = currentTick.bid; stopLoss = NormalizeDouble(currentTick.ask + adaptiveSL * point, digits); takeProfit = NormalizeDouble(currentTick.bid - adaptiveTP * point, digits); } //--- Validate stop levels int stopLevel = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); double stopDist = stopLevel * point; if(orderType == ORDER_TYPE_BUY) { if(stopLoss > 0 && (priceOpen - stopLoss) < stopDist) { Print("❌ Stop Loss too close. Required: ", stopDist); return; } } else { if(stopLoss > 0 && (stopLoss - priceOpen) < stopDist) { Print("Stop Loss too close."); return; } } string tradeComment = comment + " | " + currentRegime; //--- Execute trade for(int attempt = 1; attempt <= 3; attempt++) { bool sent; if(orderType == ORDER_TYPE_BUY) sent = TradeManager.Buy(adaptiveLot, _Symbol, priceOpen, stopLoss, takeProfit, tradeComment); else sent = TradeManager.Sell(adaptiveLot, _Symbol, priceOpen, stopLoss, takeProfit, tradeComment); if(sent) { uint retcode = TradeManager.ResultRetcode(); if(retcode == TRADE_RETCODE_DONE || retcode == TRADE_RETCODE_DONE_PARTIAL) { Print("Trade executed. Ticket: ", TradeManager.ResultOrder(), " | Lot: ", DoubleToString(adaptiveLot, 2), " | SL: ", adaptiveSL, " | TP: ", adaptiveTP); CountCurrentPositions(); return; } else if(retcode == TRADE_RETCODE_INVALID_VOLUME) { Print("Invalid volume: ", adaptiveLot); return; } else if(retcode == TRADE_RETCODE_REQUOTE || retcode == TRADE_RETCODE_PRICE_CHANGED) { Print("Price changed. Retry ", attempt, "/3"); SymbolInfoTick(_Symbol, currentTick); Sleep(100); continue; } else { Print("Trade failed: ", TradeManager.ResultRetcodeDescription()); return; } } } Print("❌ Max retries reached."); } //+------------------------------------------------------------------+ //| Manage open position - Update SL/TP based on regime changes | //+------------------------------------------------------------------+ void ManageOpenPosition() { if(positionCount == 0) return; //--- Select the position if(!PositionInfo.SelectByTicket(openPositionTicket)) { //--- Try to find any position for this symbol/magic bool found = false; for(int i = PositionsTotal() - 1; i >= 0; i--) { if(PositionInfo.SelectByIndex(i)) { if(PositionInfo.Symbol() == _Symbol && PositionInfo.Magic() == MagicNumber) { openPositionTicket = PositionInfo.Ticket(); found = true; break; } } } if(!found) { openPositionTicket = 0; return; } } //--- Track last regime for this position static string lastRegimeForPosition = ""; static double lastVolMultiplier = 1.0; //--- Check if regime has changed significantly if(lastRegimeForPosition != currentRegime || MathAbs(lastVolMultiplier - currentVolMultiplier) > 0.2) { //--- Regime changed - adjust SL/TP if(AdaptiveRiskEnabled) { AdjustPositionStops(); lastRegimeForPosition = currentRegime; lastVolMultiplier = currentVolMultiplier; } } } //+------------------------------------------------------------------+ //| Adjust SL/TP for open position based on current regime | //+------------------------------------------------------------------+ void AdjustPositionStops() { if(!PositionInfo.SelectByTicket(openPositionTicket)) return; if(!SymbolInfoTick(_Symbol, currentTick)) return; double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); //--- Calculate new adaptive SL/TP distances int adaptiveSL = (int)(BaseStopLossPoints * currentVolMultiplier); int adaptiveTP = (int)(BaseTakeProfitPoints * currentVolMultiplier); double newSL = 0.0, newTP = 0.0; ENUM_POSITION_TYPE posType = PositionInfo.PositionType(); if(posType == POSITION_TYPE_BUY) { newSL = NormalizeDouble(currentTick.bid - adaptiveSL * point, digits); newTP = NormalizeDouble(currentTick.ask + adaptiveTP * point, digits); } else // SELL { newSL = NormalizeDouble(currentTick.ask + adaptiveSL * point, digits); newTP = NormalizeDouble(currentTick.bid - adaptiveTP * point, digits); } //--- Get current SL/TP double currentSL = PositionInfo.StopLoss(); double currentTP = PositionInfo.TakeProfit(); //--- Check if adjustment is significant (more than 10 points difference) double slDiff = MathAbs(newSL - currentSL) / point; double tpDiff = MathAbs(newTP - currentTP) / point; if(slDiff > 10 || tpDiff > 10) { //--- Don't tighten SL beyond current profitable level for buys if(posType == POSITION_TYPE_BUY) { if(newSL > currentTick.bid - 50 * point) // Don't set SL too close newSL = currentSL; if(newSL < currentSL) // Only move SL up (tighten), never down newSL = currentSL; } else // SELL { if(newSL < currentTick.ask + 50 * point) newSL = currentSL; if(newSL > currentSL) // Only move SL down (tighten), never up newSL = currentSL; } //--- Attempt to modify position if(TradeManager.PositionModify(openPositionTicket, newSL, newTP)) { Print("Regime changed to ", currentRegime, " - Adjusted SL/TP"); PrintFormat(" SL: %d → %d points | TP: %d → %d points", (int)(MathAbs(currentSL - PositionInfo.PriceOpen()) / point), adaptiveSL, (int)(MathAbs(currentTP - PositionInfo.PriceOpen()) / point), adaptiveTP); } } } //+------------------------------------------------------------------+ //| Update chart display | //+------------------------------------------------------------------+ void UpdateDisplay() { CountCurrentPositions(); string display = "=== TICK ENTROPY TRADER ===\n"; display += "Tick #: " + IntegerToString(tickCount) + "\n"; display += "Signal: " + currentSignal; if(signalRepeatCount > 0) display += " (x" + IntegerToString(signalRepeatCount) + ")"; display += "\n"; display += "Regime: " + currentRegime + "\n"; display += "Entropy: " + DoubleToString(currentEntropy, 4) + "\n"; display += "Confidence: " + DoubleToString(currentConfidence, 3) + "\n"; display += "Positions: " + IntegerToString(positionCount) + "/" + IntegerToString(MaxPositions) + "\n"; display += "Bid: " + DoubleToString(currentTick.bid, _Digits) + "\n"; if(PositionInfo.SelectByTicket(PositionInfo.Ticket())) { display += "\n--- ACTIVE POSITION ---\n"; display += "Type: " + EnumToString(PositionInfo.PositionType()) + "\n"; display += "Profit: " + DoubleToString(PositionInfo.Profit(), 2) + "\n"; } //--- Cooldown display if(lastTradeTime > 0) { int secondsSince = (int)(TimeCurrent() - lastTradeTime); if(secondsSince < MinMinutesBetweenTrades * 60) { int remaining = MinMinutesBetweenTrades * 60 - secondsSince; display += "\n⏳ Cooldown: " + IntegerToString(remaining) + "s\n"; } } Comment(display); } //+------------------------------------------------------------------+ //| Clean up all chart objects created by this EA | //+------------------------------------------------------------------+ void CleanupChartObjects() { int totalObjects = ObjectsTotal(0, 0, -1); int removedCount = 0; for(int i = totalObjects - 1; i >= 0; i--) { string objName = ObjectName(0, i, 0, -1); //--- Only remove objects with our prefix if(StringFind(objName, "Entropy_") == 0 || StringFind(objName, "TickEntropy_") == 0 || StringFind(objName, "EA_Info_") == 0) { if(ObjectDelete(0, objName)) removedCount++; } } if(removedCount > 0) Print("Removed ", removedCount, " chart objects"); } //+------------------------------------------------------------------+