mirror of
https://github.com/chrisnov-it/quantumbotx.git
synced 2026-07-27 18:57:47 +00:00
78737b6835
- Update `.gitignore` to include `lab/` for backtesting and raw data. - Refactor `app.py` to improve error handling and logging. - Remove deprecated files: `core/bot_logic.py`, `core/bots/base_bot.py`, `core/bots/manager.py`, `core/db/database.py`, `core/routes/api_analysis.py`, `core/routes/api_bots_analysis.py`, `core/strategies/logic_ma.py`, `core/strategies/logic_rsi.py`. - Modify multiple files to enhance bot management, routing, and strategy handling. - Update JavaScript and HTML templates for better UI and functionality.
145 lines
6.1 KiB
Python
145 lines
6.1 KiB
Python
# core/bots/trading_bot.py - VERSI GABUNGAN FINAL
|
|
|
|
import threading
|
|
import time
|
|
import logging
|
|
import MetaTrader5 as mt5
|
|
from core.strategies.strategy_map import STRATEGY_MAP
|
|
from core.mt5.trade import place_trade, close_trade
|
|
from core.utils.mt5 import TIMEFRAME_MAP # <-- Impor dari lokasi terpusat
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class TradingBot(threading.Thread):
|
|
def __init__(self, id, name, market, lot_size, sl_pips, tp_pips, timeframe, check_interval, strategy, status='Dijeda'):
|
|
super().__init__()
|
|
self.id = id
|
|
self.name = name
|
|
self.market = market
|
|
self.lot_size = lot_size
|
|
self.sl_pips = sl_pips
|
|
self.tp_pips = tp_pips
|
|
self.timeframe = timeframe
|
|
self.check_interval = check_interval
|
|
self.strategy_name = strategy
|
|
self.market_for_mt5 = self.market.replace('/', '') # Versi simbol yang bersih untuk MT5
|
|
self.status = status
|
|
|
|
self.last_analysis = {}
|
|
self._stop_event = threading.Event()
|
|
self.strategy_instance = None
|
|
# Gunakan map yang diimpor untuk menjaga konsistensi
|
|
self.tf_map = TIMEFRAME_MAP
|
|
|
|
def run(self):
|
|
"""Metode utama yang dijalankan oleh thread, kini dengan eksekusi trade."""
|
|
self.status = 'Aktif'
|
|
self.log_activity('START', f"Bot '{self.name}' dimulai dengan strategi {self.strategy_name}.") # <-- Menggunakan log_activity
|
|
|
|
try:
|
|
strategy_class = STRATEGY_MAP.get(self.strategy_name)
|
|
if not strategy_class:
|
|
raise ValueError(f"Strategi '{self.strategy_name}' tidak ditemukan.")
|
|
|
|
# --- PERBAIKAN: Inisialisasi kelas strategi dengan benar ---
|
|
self.strategy_instance = strategy_class(bot_instance=self)
|
|
|
|
except Exception as e:
|
|
self.log_activity('ERROR', f"Inisialisasi Gagal: {e}")
|
|
self.status = 'Error'
|
|
return
|
|
|
|
while not self._stop_event.is_set():
|
|
try:
|
|
if not mt5.symbol_select(self.market_for_mt5, True):
|
|
self.log_activity('WARNING', f"Gagal mengaktifkan simbol {self.market} (di MT5: {self.market_for_mt5}). Pastikan simbol ada di Market Watch.")
|
|
time.sleep(self.check_interval)
|
|
continue
|
|
|
|
symbol_info = mt5.symbol_info(self.market_for_mt5)
|
|
if not symbol_info:
|
|
self.log_activity('WARNING', f"Tidak dapat mengambil info untuk simbol {self.market} (di MT5: {self.market_for_mt5}).")
|
|
time.sleep(self.check_interval)
|
|
continue
|
|
|
|
# --- PERBAIKAN: Panggil metode analyze dari objek strategi ---
|
|
# Metode analyze tidak lagi memerlukan argumen
|
|
self.last_analysis = self.strategy_instance.analyze()
|
|
logger.info(f"Bot {self.id} [{self.strategy_name}] - Last Analysis: {self.last_analysis}")
|
|
signal = self.last_analysis.get("signal", "HOLD")
|
|
|
|
current_position = self._get_open_position()
|
|
|
|
self._handle_trade_signal(signal, current_position)
|
|
|
|
time.sleep(self.check_interval)
|
|
except Exception as e:
|
|
self.log_activity('ERROR', f"Error pada loop utama: {e}", exc_info=True)
|
|
time.sleep(self.check_interval * 2)
|
|
|
|
self.status = 'Dijeda'
|
|
self.log_activity('STOP', f"Bot '{self.name}' dihentikan.")
|
|
|
|
def stop(self):
|
|
"""Mengirim sinyal berhenti ke thread."""
|
|
self._stop_event.set()
|
|
|
|
def is_stopped(self):
|
|
"""Memeriksa apakah thread sudah diberi sinyal berhenti."""
|
|
return self._stop_event.is_set()
|
|
|
|
def log_activity(self, action, details, exc_info=False):
|
|
"""Mencatat aktivitas bot ke database dan file log."""
|
|
try:
|
|
from core.db.queries import add_history_log
|
|
add_history_log(self.id, action, details)
|
|
log_message = f"Bot {self.id} [{action}]: {details}"
|
|
if exc_info:
|
|
logger.error(log_message, exc_info=True)
|
|
else:
|
|
logger.info(log_message)
|
|
except Exception as e:
|
|
logger.error(f"Gagal mencatat riwayat untuk bot {self.id}: {e}")
|
|
|
|
def _get_open_position(self):
|
|
"""Mendapatkan posisi terbuka untuk bot ini berdasarkan magic number (ID bot)."""
|
|
try:
|
|
positions = mt5.positions_get(symbol=self.market_for_mt5)
|
|
if positions:
|
|
for pos in positions:
|
|
if pos.magic == self.id:
|
|
return pos
|
|
return None
|
|
except Exception as e:
|
|
self.log_activity('ERROR', f"Gagal mendapatkan posisi terbuka: {e}", exc_info=True)
|
|
return None
|
|
|
|
def _handle_trade_signal(self, signal, position):
|
|
"""Menangani sinyal trading: membuka, menutup, atau tidak melakukan apa-apa."""
|
|
# Logika untuk sinyal BUY
|
|
if signal == 'BUY':
|
|
# Jika ada posisi SELL, tutup dulu
|
|
if position and position.type == mt5.ORDER_TYPE_SELL:
|
|
self.log_activity('CLOSE SELL', "Menutup posisi JUAL untuk membuka posisi BELI.")
|
|
close_trade(position)
|
|
position = None # Reset posisi setelah ditutup
|
|
|
|
# Jika tidak ada posisi, buka posisi BUY baru
|
|
if not position:
|
|
self.log_activity('OPEN BUY', "Membuka posisi BELI berdasarkan sinyal.")
|
|
place_trade(self.market_for_mt5, mt5.ORDER_TYPE_BUY, self.lot_size, self.sl_pips, self.tp_pips, self.id)
|
|
|
|
# Logika untuk sinyal SELL
|
|
elif signal == 'SELL':
|
|
# Jika ada posisi BUY, tutup dulu
|
|
if position and position.type == mt5.ORDER_TYPE_BUY:
|
|
self.log_activity('CLOSE BUY', "Menutup posisi BELI untuk membuka posisi JUAL.")
|
|
close_trade(position)
|
|
position = None # Reset posisi setelah ditutup
|
|
|
|
# Jika tidak ada posisi, buka posisi SELL baru
|
|
if not position:
|
|
self.log_activity('OPEN SELL', "Membuka posisi JUAL berdasarkan sinyal.")
|
|
place_trade(self.market_for_mt5, mt5.ORDER_TYPE_SELL, self.lot_size, self.sl_pips, self.tp_pips, self.id)
|