Supports MT5 and forex trading.

Signed-off-by: TIANHE <TIANHE@GMAIL.COM>
This commit is contained in:
TIANHE
2026-01-13 04:01:42 +08:00
parent 7527d73f25
commit e095f9be7e
21 changed files with 2717 additions and 51 deletions
@@ -4,6 +4,7 @@ Translate a strategy signal into a direct-exchange order call.
Supports:
- Crypto exchanges: Binance, OKX, Bitget, Bybit, Coinbase, Kraken, KuCoin, Gate, Bitfinex
- Traditional brokers: Interactive Brokers (IBKR) for US/HK stocks
- Forex brokers: MetaTrader 5 (MT5)
"""
from __future__ import annotations
@@ -28,6 +29,9 @@ from app.services.live_trading.bitfinex import BitfinexClient, BitfinexDerivativ
# Lazy import IBKR
IBKRClient = None
# Lazy import MT5
MT5Client = None
def _signal_to_sides(signal_type: str) -> Tuple[str, str, bool]:
"""
@@ -169,6 +173,24 @@ def place_order_from_signal(
exchange_config=exchange_config,
)
# Check for MT5 client (lazy import to avoid circular dependency)
global MT5Client
if MT5Client is None:
try:
from app.services.mt5_trading import MT5Client as _MT5Client
MT5Client = _MT5Client
except ImportError:
pass
if MT5Client is not None and isinstance(client, MT5Client):
return _place_mt5_order(
client=client,
signal_type=signal_type,
symbol=symbol,
amount=qty,
exchange_config=exchange_config,
)
raise LiveTradingError(f"Unsupported client type: {type(client)}")
@@ -228,3 +250,56 @@ def _place_ibkr_order(
)
def _place_mt5_order(
client,
*,
signal_type: str,
symbol: str,
amount: float,
exchange_config: Optional[Dict[str, Any]] = None,
) -> LiveOrderResult:
"""
Place order via MT5 for forex trading.
Signal mapping for forex:
- open_long / add_long -> BUY
- close_long / reduce_long -> SELL
- open_short / add_short -> SELL
- close_short / reduce_short -> BUY
"""
sig = (signal_type or "").strip().lower()
# Determine action based on signal
if sig in ("open_long", "add_long"):
action = "buy"
elif sig in ("close_long", "reduce_long"):
action = "sell"
elif sig in ("open_short", "add_short"):
action = "sell"
elif sig in ("close_short", "reduce_short"):
action = "buy"
else:
raise LiveTradingError(f"Unsupported signal_type for MT5: {signal_type}")
# Place market order
result = client.place_market_order(
symbol=symbol,
side=action,
volume=amount,
comment="QuantDinger",
)
# Convert MT5Client result to LiveOrderResult format
return LiveOrderResult(
success=result.success,
exchange_order_id=str(result.order_id) if result.order_id else "",
filled=result.filled,
avg_price=result.price,
raw={
"status": result.status,
"message": result.message,
"deal_id": result.deal_id,
"raw": result.raw,
},
)
@@ -4,6 +4,7 @@ Factory for direct exchange clients.
Supports:
- Crypto exchanges: Binance, OKX, Bitget, Bybit, Coinbase, Kraken, KuCoin, Gate, Bitfinex
- Traditional brokers: Interactive Brokers (IBKR) for US/HK stocks
- Forex brokers: MetaTrader 5 (MT5)
"""
from __future__ import annotations
@@ -28,6 +29,10 @@ from app.services.live_trading.bitfinex import BitfinexClient, BitfinexDerivativ
IBKRClient = None
IBKRConfig = None
# Lazy import MT5 to avoid ImportError if MetaTrader5 not installed
MT5Client = None
MT5Config = None
def _get(cfg: Dict[str, Any], *keys: str) -> str:
for k in keys:
@@ -109,10 +114,18 @@ def create_client(exchange_config: Dict[str, Any], *, market_type: str = "swap")
return BitfinexClient(api_key=api_key, secret_key=secret_key, base_url=base_url)
return BitfinexDerivativesClient(api_key=api_key, secret_key=secret_key, base_url=base_url)
# Traditional brokers (IBKR for US/HK stocks)
# Traditional brokers (IBKR for US/HK stocks only)
if exchange_id == "ibkr":
# Note: Market category validation should be done at the caller level
# This factory only creates clients based on exchange_id
return create_ibkr_client(exchange_config)
# Forex brokers (MT5 for Forex only)
if exchange_id == "mt5":
# Note: Market category validation should be done at the caller level
# This factory only creates clients based on exchange_id
return create_mt5_client(exchange_config)
raise LiveTradingError(f"Unsupported exchange_id: {exchange_id}")
@@ -159,3 +172,56 @@ def create_ibkr_client(exchange_config: Dict[str, Any]):
return client
def create_mt5_client(exchange_config: Dict[str, Any]):
"""
Create MT5 client for forex trading.
exchange_config should contain:
- mt5_login: MT5 account number
- mt5_password: MT5 password
- mt5_server: Broker server name (e.g., "ICMarkets-Demo")
- mt5_terminal_path: Optional path to terminal64.exe
"""
global MT5Client, MT5Config
# Lazy import to avoid ImportError if MetaTrader5 not installed
if MT5Client is None or MT5Config is None:
try:
from app.services.mt5_trading import MT5Client as _MT5Client, MT5Config as _MT5Config
MT5Client = _MT5Client
MT5Config = _MT5Config
except ImportError:
raise LiveTradingError(
"MT5 trading requires MetaTrader5 library. Run: pip install MetaTrader5\n"
"Note: This library only works on Windows."
)
login = int(exchange_config.get("mt5_login") or 0)
password = str(exchange_config.get("mt5_password") or "").strip()
server = str(exchange_config.get("mt5_server") or "").strip()
terminal_path = str(exchange_config.get("mt5_terminal_path") or "").strip()
if not login or not password or not server:
raise LiveTradingError("MT5 requires login, password, and server")
config = MT5Config(
login=login,
password=password,
server=server,
terminal_path=terminal_path,
)
client = MT5Client(config)
# Connect immediately
if not client.connect():
raise LiveTradingError(
"Failed to connect to MT5 terminal. Please check:\n"
"1. MT5 terminal is running\n"
"2. Credentials are correct\n"
"3. You are on Windows"
)
return client