Vendor-agnostic order routing layer between Nautilus strategies and multiple MT5 brokers. File-bridge EA, swap-point pattern, OCO that survives hedging-account quirks.
Every MT5 broker behaves differently. FundedNext and E8 use hedging accounts that silently ignore the position= field in OCO closes — submitting TRADE_ACTION_PENDING opens an opposing position instead of closing one, leaving naked exposure that survives the day. Wine-hosted MT5 terminals on Linux VPS die within minutes if you forget DISPLAY=:1 XAUTHORITY=... in the systemd unit. Single-process bridges create a single point of failure. The stack needs a routing layer that abstracts all of this.
┌────────────────────────────────────┐
│ Nautilus strategy node │
│ (TRB London / Tokyo cells) │
└────────────────┬───────────────────┘
│ submit_order
▼
┌────────────────────────────────────┐
│ ExecutionService daemon │
│ (out-of-process, supervised) │
└────────────────┬───────────────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐
│rpyc │ │file- │ │ direct│
│relay │ │bridge │ │ MT5 │
│:18812 │ │ EA │ │ python│
└───┬───┘ └───┬───┘ └───┬───┘
│ │ │
▼ ▼ ▼
per-book STONEDESK_BRIDGE_DIR isolation
(LF / FN / E8 / Derrick)
│
▼
┌──────────────┐
│ Wine MT5 │
│ + custom EA │
└──────────────┘
- File-bridge EA (MQL5): watches
orders_in/directory, executes via TRADE_ACTION_SLTP for hedging-account OCO closes, writes acks toorders_ack/. - Python relay (rpyc :18812): alternate transport for brokers that allow direct MT5 process control (LF).
- Swap-point pattern: single attribute on
self._submit_mt5— duck-typed mt5 module is a drop-in replacement. Switching transports is one line. - STONEDESK_BRIDGE_DIR per book: default
/var/lib/stonedesk/ordersdoesn't match EA'sMQL5/Files— override per book or orders silently never reach EA.
OCO close on hedging brokers — TRADE_ACTION_SLTP
// nautilus-mt5-journal — fixed in commit dc46f36
def close_oco(self, position_id, sl_price, tp_price):
# WRONG (silently opens opposing position on FN/E8):
# request = {"action": TRADE_ACTION_PENDING, "position": position_id, ...}
# RIGHT:
request = {
"action": mt5.TRADE_ACTION_SLTP,
"position": position_id,
"sl": sl_price,
"tp": tp_price,
}
return self.mt5.order_send(request)
Swap-point pattern — single attribute, duck-typed module
# router config picks transport at startup
if config.transport == "file_bridge":
self._submit_mt5 = FileBridgeMT5(orders_dir=os.environ["STONEDESK_BRIDGE_DIR"])
elif config.transport == "rpyc":
self._submit_mt5 = rpyc.connect("localhost", 18812).root
else:
import MetaTrader5 as mt5
self._submit_mt5 = mt5
# strategy code only knows self._submit_mt5.order_send(req)
MQL5 EA — atomic file write (string by value, not by ref)
// MQL5 'const string &' rejects rvalues — error 200 on inline build
// WRONG: AtomicWrite("/path", BuildPayload());
// RIGHT: pass string by value
bool AtomicWrite(const string path, const string payload) {
string tmp = path + ".tmp";
int h = FileOpen(tmp, FILE_WRITE | FILE_TXT);
if (h == INVALID_HANDLE) return false;
FileWriteString(h, payload);
FileClose(h);
return FileMove(tmp, 0, path, FILE_REWRITE);
}
- OCO TRADE_ACTION_SLTP fix eliminated naked-position incidents on FN/E8 hedging accounts.
- File-bridge swap-point pattern enabled zero-downtime transport migration between rpyc and file modes.
- Preflight session-aware check skips tick-freshness during Fri-22 → Sun-22 UTC weekend closure; killed systemd-restart TEMPFAIL loops.