Files
quantumbotx/core/bots/controller.py
T
Reynov Christian ce22b46b48 feat: Implement Hybrid Strategy & Major Bug Fixes
Successfully run QuantumBotX Hybrid on XAUUSD after fixing critical bugs (KeyError, data fetching). Improved UI with scrollable modal and implemented graceful shutdown for the server. System is now more robust and user-friendly.
2025-08-01 01:16:39 +08:00

136 lines
5.4 KiB
Python

# core/bots/controller.py
import json
import logging
from core.db import queries
from .trading_bot import TradingBot
logger = logging.getLogger(__name__)
# Dictionary untuk menyimpan instance thread bot yang aktif
# Key: bot_id (int), Value: TradingBot instance
active_bots = {}
def ambil_semua_bot():
"""
Mengambil semua bot dari database saat aplikasi pertama kali dimulai.
Tidak memulai thread, hanya memuat konfigurasi.
"""
try:
all_bots_data = queries.get_all_bots()
if not all_bots_data:
logger.info("Database tidak memiliki bot untuk dimuat.")
return
logger.info(f"Memuat {len(all_bots_data)} bot dari database. Memeriksa bot yang perlu diaktifkan ulang...")
for bot_data in all_bots_data:
if bot_data.get('status') == 'Aktif':
logger.info(f"Bot ID {bot_data['id']} ({bot_data['name']}) memiliki status 'Aktif'. Mencoba memulai ulang...")
mulai_bot(bot_data['id'])
except Exception as e:
logger.error(f"Gagal memuat bot dari database saat startup: {e}", exc_info=True)
def mulai_bot(bot_id: int):
"""Memulai thread untuk bot yang dipilih."""
if bot_id in active_bots and active_bots[bot_id].is_alive():
return True, f"Bot {bot_id} sudah berjalan."
bot_data = queries.get_bot_by_id(bot_id)
if not bot_data:
return False, f"Bot dengan ID {bot_id} tidak ditemukan."
# Ubah string JSON dari DB menjadi dictionary Python
params_dict = json.loads(bot_data.get('strategy_params', '{}'))
try:
bot_thread = TradingBot(
id=bot_data['id'], name=bot_data['name'], market=bot_data['market'],
lot_size=bot_data['lot_size'], sl_pips=bot_data['sl_pips'],
tp_pips=bot_data['tp_pips'], timeframe=bot_data['timeframe'],
check_interval=bot_data['check_interval_seconds'], strategy=bot_data['strategy'],
strategy_params=params_dict
)
bot_thread.start()
active_bots[bot_id] = bot_thread
queries.update_bot_status(bot_id, 'Aktif')
logger.info(f"Bot {bot_id} ({bot_data['name']}) berhasil dimulai.")
return True, f"Bot {bot_data['name']} berhasil dimulai."
except Exception as e:
logger.error(f"Gagal memulai bot {bot_id}: {e}", exc_info=True)
queries.update_bot_status(bot_id, 'Error')
return False, f"Gagal memulai bot: {e}"
def stop_bot(bot_id: int):
"""Menghentikan thread bot yang sedang berjalan."""
if bot_id in active_bots and active_bots[bot_id].is_alive():
bot_thread = active_bots[bot_id]
bot_thread.stop()
bot_thread.join(timeout=10) # Tunggu thread berhenti
del active_bots[bot_id]
queries.update_bot_status(bot_id, 'Dijeda')
logger.info(f"Bot {bot_id} berhasil dihentikan.")
return True, f"Bot {bot_thread.name} berhasil dihentikan."
# Jika bot tidak ada di memori tapi statusnya 'Aktif' di DB (state tidak konsisten)
queries.update_bot_status(bot_id, 'Dijeda')
return True, f"Bot {bot_id} dihentikan (state tidak konsisten telah diperbaiki)."
def perbarui_bot(bot_id: int, data: dict):
"""Memperbarui konfigurasi bot di database."""
bot_instance = active_bots.get(bot_id)
if bot_instance and bot_instance.is_alive():
logger.info(f"Menghentikan bot {bot_id} sementara untuk pembaruan.")
stop_bot(bot_id)
# --- PERBAIKAN DI SINI ---
# Ganti nama kunci 'check_interval_seconds' dari frontend
# menjadi 'interval' yang sesuai dengan kolom database.
if 'check_interval_seconds' in data:
data['interval'] = data.pop('check_interval_seconds')
# Ambil parameter kustom, ubah jadi string JSON, dan simpan
custom_params = data.pop('params', {})
data['strategy_params'] = json.dumps(custom_params)
# --- PERBAIKAN BARU: Filter data untuk mencegah TypeError ---
# Hanya teruskan argumen yang diharapkan oleh fungsi queries.update_bot
expected_args = [
'name', 'market', 'lot_size', 'sl_pips', 'tp_pips',
'timeframe', 'interval', 'strategy', 'strategy_params'
]
update_data = {key: data[key] for key in expected_args if key in data}
try:
# Gunakan dictionary yang sudah difilter
success = queries.update_bot(bot_id=bot_id, **update_data)
if success:
logger.info(f"Konfigurasi bot {bot_id} berhasil diperbarui di database.")
return True, "Bot berhasil diperbarui."
else:
return False, "Gagal memperbarui bot di database."
except Exception as e:
logger.error(f"Error saat memperbarui bot {bot_id} di DB: {e}", exc_info=True)
return False, str(e)
def hapus_bot(bot_id: int):
"""Menghentikan dan menghapus bot."""
stop_bot(bot_id) # Pastikan thread berhenti sebelum dihapus
return queries.delete_bot(bot_id)
def add_new_bot_to_controller(bot_id: int):
"""Menambahkan bot baru dan langsung memulainya jika statusnya 'Aktif'."""
bot_data = queries.get_bot_by_id(bot_id)
if bot_data and bot_data.get('status') == 'Aktif':
mulai_bot(bot_id)
def get_bot_instance_by_id(bot_id: int):
"""Mengembalikan instance thread bot yang aktif."""
return active_bots.get(bot_id)
def get_bot_analysis_data(bot_id: int):
"""Mengambil data analisis terakhir dari instance bot."""
bot = active_bots.get(bot_id)
if bot and hasattr(bot, 'last_analysis'):
return bot.last_analysis
return None