916 lines
32 KiB
MQL5
916 lines
32 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| 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");
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|