diff --git a/core/mt5/trade.py b/core/mt5/trade.py index 7347fc2..bb26ea2 100644 --- a/core/mt5/trade.py +++ b/core/mt5/trade.py @@ -1,62 +1,112 @@ +# core/mt5/trade.py import MetaTrader5 as mt5 +import logging -def place_trade(symbol, trade_type, lot, sl_pips, tp_pips, magic_number): +logger = logging.getLogger(__name__) + +def place_trade(symbol, order_type, volume, sl_pips, tp_pips, magic): + """ + Menempatkan order trading ke MetaTrader 5 dengan logika yang lebih tangguh. + """ symbol_info = mt5.symbol_info(symbol) if symbol_info is None: - print(f"Gagal mendapatkan info untuk simbol {symbol}") + logger.error(f"Gagal mendapatkan info untuk simbol {symbol}, order dibatalkan.") return None - allowed_filling_modes = symbol_info.filling_modes - if mt5.ORDER_FILLING_FOK in allowed_filling_modes: - filling_type = mt5.ORDER_FILLING_FOK - elif mt5.ORDER_FILLING_IOC in allowed_filling_modes: + # --- PERBAIKAN UTAMA DI SINI --- + # 1. Gunakan atribut yang benar: 'filling_mode' (singular) + # 2. Pilih filling mode yang didukung. IOC lebih fleksibel daripada FOK. + # FOK (Fill Or Kill): Seluruh order harus terpenuhi, atau dibatalkan. + # IOC (Immediate Or Cancel): Bagian dari order bisa terpenuhi, sisanya dibatalkan. + + # Ambil filling mode yang diizinkan oleh simbol + allowed_filling_mode = symbol_info.filling_mode + + # Pilih tipe filling. Prioritaskan IOC jika tersedia, jika tidak, FOK. + filling_type = mt5.ORDER_FILLING_FOK # Default + if allowed_filling_mode & mt5.ORDER_FILLING_IOC: filling_type = mt5.ORDER_FILLING_IOC + elif allowed_filling_mode & mt5.ORDER_FILLING_FOK: + filling_type = mt5.ORDER_FILLING_FOK else: - filling_type = allowed_filling_modes[0] + logger.warning(f"Simbol {symbol} tidak mendukung FOK atau IOC. Menggunakan FOK sebagai fallback.") point = symbol_info.point - price = mt5.symbol_info_tick(symbol).ask if trade_type == mt5.ORDER_TYPE_BUY else mt5.symbol_info_tick(symbol).bid + price = mt5.symbol_info_tick(symbol).ask if order_type == mt5.ORDER_TYPE_BUY else mt5.symbol_info_tick(symbol).bid + + sl = 0.0 + tp = 0.0 + + if order_type == mt5.ORDER_TYPE_BUY: + sl = price - sl_pips * point if sl_pips > 0 else 0.0 + tp = price + tp_pips * point if tp_pips > 0 else 0.0 + elif order_type == mt5.ORDER_TYPE_SELL: + sl = price + sl_pips * point if sl_pips > 0 else 0.0 + tp = price - tp_pips * point if tp_pips > 0 else 0.0 request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, - "volume": lot, - "type": trade_type, + "volume": float(volume), + "type": order_type, "price": price, - "sl": price - sl_pips * point if trade_type == mt5.ORDER_TYPE_BUY else price + sl_pips * point, - "tp": price + tp_pips * point if trade_type == mt5.ORDER_TYPE_BUY else price - tp_pips * point, - "magic": magic_number, - "comment": "Trade dari Python Bot", + "sl": sl, + "tp": tp, + "magic": magic, + "comment": "QuantumBotX Trade", "type_time": mt5.ORDER_TIME_GTC, "type_filling": filling_type, } + + logger.info(f"Mengirim order: {request}") result = mt5.order_send(request) - if result.retcode != mt5.TRADE_RETCODE_DONE: - print(f"Order gagal: {result.comment}") + + if result is None: + logger.error(f"order_send gagal, last_error()={mt5.last_error()}") return None - print(f"Order terkirim. Ticket: {result.order}") + + if result.retcode != mt5.TRADE_RETCODE_DONE: + logger.error(f"Order GAGAL, retcode={result.retcode}, comment: {result.comment}") + logger.error(f"Request Gagal: {request}") + else: + logger.info(f"Order BERHASIL, ticket={result.order}, comment: {result.comment}") + return result + def close_trade(position): - trade_type = mt5.ORDER_TYPE_SELL if position.type == mt5.ORDER_TYPE_BUY else mt5.ORDER_TYPE_BUY - price = mt5.symbol_info_tick(position.symbol).bid if position.type == mt5.ORDER_TYPE_BUY else mt5.symbol_info_tick(position.symbol).ask + """ + Menutup posisi trading yang ada. + """ + if position is None: + return + + symbol = position.symbol + ticket = position.ticket + volume = position.volume + order_type = mt5.ORDER_TYPE_SELL if position.type == mt5.ORDER_TYPE_BUY else mt5.ORDER_TYPE_BUY + price = mt5.symbol_info_tick(symbol).bid if position.type == mt5.ORDER_TYPE_BUY else mt5.symbol_info_tick(symbol).ask + + symbol_info = mt5.symbol_info(symbol) + if symbol_info is None: + logger.error(f"Gagal mendapatkan info untuk simbol {symbol} saat menutup posisi, order dibatalkan.") + return None + + allowed_filling_mode = symbol_info.filling_mode + filling_type = mt5.ORDER_FILLING_FOK + if allowed_filling_mode & mt5.ORDER_FILLING_IOC: + filling_type = mt5.ORDER_FILLING_IOC request = { - "action": mt5.TRADE_ACTION_DEAL, - "symbol": position.symbol, - "volume": position.volume, - "type": trade_type, - "position": position.ticket, - "price": price, - "magic": position.magic, - "comment": "Auto close dari Python Bot", - "type_time": mt5.ORDER_TIME_GTC, - "type_filling": mt5.ORDER_FILLING_FOK, + "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": float(volume), + "type": order_type, "position": ticket, "price": price, "magic": position.magic, + "comment": "QuantumBotX Close", "type_time": mt5.ORDER_TIME_GTC, "type_filling": filling_type, } + logger.info(f"Menutup posisi: {request}") result = mt5.order_send(request) - if result.retcode != mt5.TRADE_RETCODE_DONE: - print(f"Gagal menutup posisi #{position.ticket}: {result.comment}") - return False - print(f"Posisi #{position.ticket} berhasil ditutup.") - return True + + if result and result.retcode == mt5.TRADE_RETCODE_DONE: + logger.info(f"Posisi {ticket} berhasil ditutup.") + else: + logger.error(f"Gagal menutup posisi, retcode={result.retcode if result else 'N/A'}, comment: {result.comment if result else mt5.last_error()}") \ No newline at end of file