//+------------------------------------------------------------------+ //| PythonBridgeEA.mq5 | //| MQL5 Client for AFML Bridge | //+------------------------------------------------------------------+ #property copyright "AFML Integration" #property version "1.00" #property strict #include //--- Input parameters input string PythonHost = "127.0.0.1"; // Python server host (NO http://) input int PythonPort = 5056; // Python server port input double RiskPercent = 1.0; // Risk per trade (%) input int MagicNumber = 12345; // EA magic number input bool EnableTrading = true; // Enable actual trading input int HeartbeatInterval = 5000; // Heartbeat interval (ms) input int DataSendInterval = 1000; // Market data send interval (ms) //--- Global variables int socket_handle = INVALID_HANDLE; CTrade trade; datetime last_heartbeat = 0; datetime last_data_send = 0; bool is_connected = false; //--- Message buffer uchar message_buffer[]; int buffer_size = 0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Setup trade object trade.SetExpertMagicNumber(MagicNumber); trade.SetDeviationInPoints(10); trade.SetTypeFilling(ORDER_FILLING_FOK); // Initialize buffer ArrayResize(message_buffer, 0); // Connect to Python bridge if(!ConnectToPython()) { Print("Failed to connect to Python bridge - will retry"); } Print("Python Bridge EA initialized successfully"); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { DisconnectFromPython(); Print("Python Bridge EA deinitialized"); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { static datetime last_reconnect = 0; // Check connection if(!is_connected) { // Try reconnect every 5 seconds if(TimeCurrent() - last_reconnect >= 5) { Print("Attempting to reconnect to Python..."); ConnectToPython(); last_reconnect = TimeCurrent(); } return; } // Send heartbeat if(TimeCurrent() - last_heartbeat >= HeartbeatInterval / 1000) { SendHeartbeat(); last_heartbeat = TimeCurrent(); } // Send market data if(TimeCurrent() - last_data_send >= DataSendInterval / 1000) { SendMarketData(); last_data_send = TimeCurrent(); } // Check for incoming signals ProcessIncomingMessages(); } //+------------------------------------------------------------------+ //| Connect to Python bridge server | //+------------------------------------------------------------------+ bool ConnectToPython() { if(socket_handle != INVALID_HANDLE) { SocketClose(socket_handle); } // Create socket socket_handle = SocketCreate(); if(socket_handle == INVALID_HANDLE) { return false; } // Connect to server ResetLastError(); if(!SocketConnect(socket_handle, PythonHost, PythonPort, 5000)) { int error = GetLastError(); Print("Failed to connect to Python server at ", PythonHost, ":", PythonPort, " error=", error); if(error == 4014) { Print("SocketConnect is not allowed. Open Tools > Options > Expert Advisors, ", "enable Algo Trading, and add 127.0.0.1 to the allowed WebRequest/Socket URLs. ", "Also make sure this file is attached as an Expert Advisor, not an Indicator or Tester debug run."); } SocketClose(socket_handle); socket_handle = INVALID_HANDLE; return false; } is_connected = true; Print("Connected to Python bridge at ", PythonHost, ":", PythonPort); Print("Socket handle: ", socket_handle); Print("Testing initial communication..."); // Test the connection immediately SendHeartbeat(); // Request any pending signals RequestPendingSignals(); return true; } //+------------------------------------------------------------------+ //| Disconnect from Python bridge | //+------------------------------------------------------------------+ void DisconnectFromPython() { if(socket_handle != INVALID_HANDLE) { SocketClose(socket_handle); socket_handle = INVALID_HANDLE; } is_connected = false; } //+------------------------------------------------------------------+ //| Escape string for JSON | //+------------------------------------------------------------------+ string JsonEscape(string value) { StringReplace(value, "\\", "\\\\"); StringReplace(value, "\"", "\\\""); StringReplace(value, "\r", "\\r"); StringReplace(value, "\n", "\\n"); StringReplace(value, "\t", "\\t"); return value; } //+------------------------------------------------------------------+ //| JSON pair helpers | //+------------------------------------------------------------------+ string JsonString(string key, string value) { return "\"" + key + "\":\"" + JsonEscape(value) + "\""; } string JsonNumber(string key, double value) { return "\"" + key + "\":" + DoubleToString(value, 8); } //+------------------------------------------------------------------+ //| Extract a simple string value from JSON | //+------------------------------------------------------------------+ string JsonGetString(string json, string key, string default_value = "") { string marker = "\"" + key + "\""; int pos = StringFind(json, marker); if(pos < 0) return default_value; pos = StringFind(json, ":", pos + StringLen(marker)); if(pos < 0) return default_value; pos++; while(pos < StringLen(json) && StringGetCharacter(json, pos) <= 32) pos++; if(pos >= StringLen(json) || StringGetCharacter(json, pos) != 34) return default_value; pos++; string out = ""; while(pos < StringLen(json)) { ushort ch = StringGetCharacter(json, pos); if(ch == 92) // backslash { pos++; if(pos < StringLen(json)) out += ShortToString(StringGetCharacter(json, pos)); } else if(ch == 34) // double quote return out; else out += ShortToString(ch); pos++; } return default_value; } //+------------------------------------------------------------------+ //| Extract a simple numeric value from JSON | //+------------------------------------------------------------------+ double JsonGetDouble(string json, string key, double default_value = 0.0) { string marker = "\"" + key + "\""; int pos = StringFind(json, marker); if(pos < 0) return default_value; pos = StringFind(json, ":", pos + StringLen(marker)); if(pos < 0) return default_value; pos++; while(pos < StringLen(json) && StringGetCharacter(json, pos) <= 32) pos++; int start = pos; while(pos < StringLen(json)) { ushort ch = StringGetCharacter(json, pos); if((ch >= 48 && ch <= 57) || ch == 45 || ch == 43 || ch == 46 || ch == 101 || ch == 69) pos++; else break; } if(pos <= start) return default_value; return StringToDouble(StringSubstr(json, start, pos - start)); } //+------------------------------------------------------------------+ //| Send JSON message to Python with length prefix | //+------------------------------------------------------------------+ bool SendMessage(string json_message) { if(socket_handle == INVALID_HANDLE || !is_connected) { return false; } // Convert string to UTF-8 byte array uchar data[]; int str_len = StringToCharArray(json_message, data, 0, WHOLE_ARRAY, CP_UTF8); // Remove null terminator if present if(str_len > 0 && data[str_len - 1] == 0) str_len--; ArrayResize(data, str_len); // Create length prefix (4 bytes, little-endian) uint length = str_len; uchar length_bytes[4]; length_bytes[0] = (uchar)(length & 0xFF); length_bytes[1] = (uchar)((length >> 8) & 0xFF); length_bytes[2] = (uchar)((length >> 16) & 0xFF); length_bytes[3] = (uchar)((length >> 24) & 0xFF); // Send length prefix if(SocketSend(socket_handle, length_bytes, 4) != 4) { is_connected = false; return false; } // Send data if(SocketSend(socket_handle, data, str_len) != str_len) { is_connected = false; return false; } return true; } //+------------------------------------------------------------------+ //| Send heartbeat to Python | //+------------------------------------------------------------------+ void SendHeartbeat() { string json = "{"; json += JsonString("type", "heartbeat") + ","; json += JsonString("timestamp", TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS)); json += "}"; SendMessage(json); } //+------------------------------------------------------------------+ //| Send market data to Python | //+------------------------------------------------------------------+ void SendMarketData() { MqlTick tick; if(!SymbolInfoTick(_Symbol, tick)) { return; } string json = "{"; json += JsonString("type", "market_data") + ","; json += JsonString("timestamp", TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS)) + ","; json += JsonString("symbol", _Symbol) + ","; json += JsonNumber("bid", tick.bid) + ","; json += JsonNumber("ask", tick.ask) + ","; json += JsonNumber("volume", (double)tick.volume) + ","; json += JsonNumber("spread", (tick.ask - tick.bid) / _Point); // Add recent bars as array MqlRates rates[]; int copied = CopyRates(_Symbol, PERIOD_CURRENT, 0, 20, rates); if(copied > 0) { json += ",\"bars\":["; for(int i = 0; i < copied; i++) { if(i > 0) json += ","; json += "{"; json += JsonString("time", TimeToString(rates[i].time, TIME_DATE | TIME_SECONDS)) + ","; json += JsonNumber("open", rates[i].open) + ","; json += JsonNumber("high", rates[i].high) + ","; json += JsonNumber("low", rates[i].low) + ","; json += JsonNumber("close", rates[i].close) + ","; json += JsonNumber("volume", (double)rates[i].tick_volume); json += "}"; } json += "]"; } json += "}"; SendMessage(json); } //+------------------------------------------------------------------+ //| Request pending signals from Python | //+------------------------------------------------------------------+ void RequestPendingSignals() { string json = "{"; json += JsonString("type", "request_signals") + ","; json += JsonString("timestamp", TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS)); json += "}"; SendMessage(json); } //+------------------------------------------------------------------+ //| Process incoming messages from Python | //+------------------------------------------------------------------+ void ProcessIncomingMessages() { if(socket_handle == INVALID_HANDLE || !is_connected) { return; } // Read available data uchar temp_buffer[4096]; int received = SocketRead(socket_handle, temp_buffer, 4096, 0); if(received == -1) { int error = GetLastError(); if(error == 4014) { // Normal - no data available yet return; } else if(error != 0) { is_connected = false; } return; } if(received == 0) { return; } // Append to buffer int old_size = buffer_size; buffer_size += received; ArrayResize(message_buffer, buffer_size); ArrayCopy(message_buffer, temp_buffer, old_size, 0, received); // Process complete messages while(buffer_size >= 4) { // Read length prefix (little-endian) uint message_length = message_buffer[0] | (message_buffer[1] << 8) | (message_buffer[2] << 16) | (message_buffer[3] << 24); // Check if we have complete message if(buffer_size < (int)(4 + message_length)) { break; // Wait for more data } // Extract message uchar msg_data[]; ArrayResize(msg_data, (int)message_length); ArrayCopy(msg_data, message_buffer, 0, 4, (int)message_length); string message = CharArrayToString(msg_data, 0, (int)message_length, CP_UTF8); // Remove processed message from buffer int remaining = buffer_size - (4 + (int)message_length); if(remaining > 0) { uchar temp[]; ArrayResize(temp, remaining); ArrayCopy(temp, message_buffer, 0, 4 + (int)message_length, remaining); ArrayResize(message_buffer, remaining); ArrayCopy(message_buffer, temp, 0, 0, remaining); buffer_size = remaining; } else { ArrayResize(message_buffer, 0); buffer_size = 0; } // Process message ProcessMessage(message); } } //+------------------------------------------------------------------+ //| Process single JSON message from Python | //+------------------------------------------------------------------+ void ProcessMessage(string json_string) { string msg_type = JsonGetString(json_string, "type", ""); if(msg_type == "signal") { ProcessSignal(json_string); } else if(msg_type == "heartbeat_response") { // Connection alive - no action needed } else { Print("Unknown message type: ", msg_type); } } //+------------------------------------------------------------------+ //| Process trading signal from Python | //+------------------------------------------------------------------+ void ProcessSignal(string json_string) { // Check if data field exists if(StringFind(json_string, "\"data\"") < 0) { Print("Signal message missing 'data' field"); return; } // Extract signal fields with defaults string signal_type = JsonGetString(json_string, "signal_type", ""); string symbol = JsonGetString(json_string, "symbol", ""); double entry_price = JsonGetDouble(json_string, "entry_price", 0.0); double stop_loss = JsonGetDouble(json_string, "stop_loss", 0.0); double take_profit = JsonGetDouble(json_string, "take_profit", 0.0); double position_size = JsonGetDouble(json_string, "position_size", 0.0); string strategy_name = JsonGetString(json_string, "strategy_name", "unknown"); if(signal_type == "" || symbol == "") { Print("Missing required fields in signal data"); return; } Print("Received ", signal_type, " signal for ", symbol, " from strategy: ", strategy_name); if(!EnableTrading) { Print("Trading disabled - signal ignored"); SendExecutionReport(signal_type, "ignored", 0.0); return; } // Execute signal bool result = false; double execution_price = 0.0; if(signal_type == "BUY") { result = ExecuteBuy(symbol, position_size, stop_loss, take_profit); execution_price = SymbolInfoDouble(symbol, SYMBOL_ASK); } else if(signal_type == "SELL") { result = ExecuteSell(symbol, position_size, stop_loss, take_profit); execution_price = SymbolInfoDouble(symbol, SYMBOL_BID); } else if(signal_type == "CLOSE") { result = ClosePosition(symbol); execution_price = 0.0; } // Send execution report string status = result ? "executed" : "failed"; SendExecutionReport(signal_type, status, execution_price); } //+------------------------------------------------------------------+ //| Execute buy order | //+------------------------------------------------------------------+ bool ExecuteBuy(string symbol, double size, double sl, double tp) { double volume = CalculateVolume(size); double price = SymbolInfoDouble(symbol, SYMBOL_ASK); // Normalize SL/TP if(sl > 0) sl = NormalizeDouble(sl, _Digits); if(tp > 0) tp = NormalizeDouble(tp, _Digits); bool result = trade.Buy(volume, symbol, price, sl, tp, "Python Signal"); if(result) { Print("BUY order executed: ", volume, " lots at ", price); } else { Print("BUY order failed: ", trade.ResultRetcodeDescription()); } return result; } //+------------------------------------------------------------------+ //| Execute sell order | //+------------------------------------------------------------------+ bool ExecuteSell(string symbol, double size, double sl, double tp) { double volume = CalculateVolume(size); double price = SymbolInfoDouble(symbol, SYMBOL_BID); // Normalize SL/TP if(sl > 0) sl = NormalizeDouble(sl, _Digits); if(tp > 0) tp = NormalizeDouble(tp, _Digits); bool result = trade.Sell(volume, symbol, price, sl, tp, "Python Signal"); if(result) { Print("SELL order executed: ", volume, " lots at ", price); } else { Print("SELL order failed: ", trade.ResultRetcodeDescription()); } return result; } //+------------------------------------------------------------------+ //| Close position for symbol | //+------------------------------------------------------------------+ bool ClosePosition(string symbol) { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)) { if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { bool result = trade.PositionClose(ticket); if(result) { Print("Position closed: ", ticket); } else { Print("Failed to close position: ", trade.ResultRetcodeDescription()); } return result; } } } Print("No position found for ", symbol); return false; } //+------------------------------------------------------------------+ //| Calculate position volume based on risk | //+------------------------------------------------------------------+ double CalculateVolume(double position_size) { if(position_size > 0) { // Use provided size return NormalizeDouble(position_size, 2); } // Calculate based on risk percentage double balance = AccountInfoDouble(ACCOUNT_BALANCE); double risk_amount = balance * RiskPercent / 100.0; // Simple volume calculation (can be enhanced) double volume = 0.01; // Minimum volume // Normalize to broker's allowed values double min_volume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double max_volume = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); double volume_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); volume = MathMax(volume, min_volume); volume = MathMin(volume, max_volume); volume = MathFloor(volume / volume_step) * volume_step; return NormalizeDouble(volume, 2); } //+------------------------------------------------------------------+ //| Send execution report to Python | //+------------------------------------------------------------------+ void SendExecutionReport(string signal_type, string status, double execution_price) { string json = "{"; json += JsonString("type", "execution_report") + ","; json += JsonString("timestamp", TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS)) + ","; json += JsonString("signal_id", IntegerToString(TimeCurrent())) + ","; json += JsonString("signal_type", signal_type) + ","; json += JsonString("status", status) + ","; json += JsonNumber("execution_price", execution_price) + ","; json += JsonString("symbol", _Symbol) + ","; json += JsonNumber("magic_number", (double)MagicNumber); json += "}"; SendMessage(json); } //+------------------------------------------------------------------+