Article-22220-Real-Time-Ent.../Real-Time Entropy.mq5

916 lines
32 KiB
MQL5
Raw Permalink Normal View History

2026-06-02 15:05:50 +02:00
//+------------------------------------------------------------------+
//| 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 <Trade/Trade.mqh>
#include <Trade/PositionInfo.mqh>
//--- 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");
}
//+------------------------------------------------------------------+