mirror of
https://github.com/chrisnov-it/quantumbotx.git
synced 2026-07-28 03:07:53 +00:00
7b74743d58
Refactor the JavaScript code to use a single modal for both forex and stock profiles. Update error handling to display server error messages. Remove unused app.py and fetch.py files. Adjust various routes and strategies for consistency.
145 lines
5.5 KiB
Python
145 lines
5.5 KiB
Python
# core/utils/mt5.py (VERSI FINAL LENGKAP)
|
|
|
|
import MetaTrader5 as mt5
|
|
from datetime import datetime, timedelta
|
|
import pandas as pd
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Definisikan konstanta di satu tempat
|
|
TIMEFRAME_MAP = {
|
|
"M1": mt5.TIMEFRAME_M1, "M5": mt5.TIMEFRAME_M5, "M15": mt5.TIMEFRAME_M15,
|
|
"H1": mt5.TIMEFRAME_H1, "H4": mt5.TIMEFRAME_H4, "D1": mt5.TIMEFRAME_D1,
|
|
"W1": mt5.TIMEFRAME_W1, "MN1": mt5.TIMEFRAME_MN1
|
|
}
|
|
|
|
def initialize_mt5(account, password, server):
|
|
"""Login ke MetaTrader 5."""
|
|
if not mt5.initialize(login=account, password=password, server=server):
|
|
logger.error(f"Inisialisasi atau Login MT5 gagal: {mt5.last_error()}")
|
|
return False
|
|
logger.info(f"Berhasil login ke MT5 ({account}) di server {server}")
|
|
return True
|
|
|
|
def get_account_info_mt5():
|
|
"""Mengambil informasi akun (saldo, equity, profit) dari MT5."""
|
|
try:
|
|
info = mt5.account_info()
|
|
if info:
|
|
return info._asdict()
|
|
else:
|
|
logger.warning(f"Gagal mengambil info akun. Error: {mt5.last_error()}")
|
|
return None
|
|
except Exception as e:
|
|
logger.error(f"Error saat get_account_info_mt5: {e}", exc_info=True)
|
|
return None
|
|
|
|
def get_rates_mt5(symbol: str, timeframe: int, count: int = 100):
|
|
"""Mengambil data harga historis (rates) dari MT5 dalam bentuk DataFrame."""
|
|
try:
|
|
rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, count)
|
|
if rates is None or len(rates) == 0:
|
|
logger.warning(f"Gagal mengambil data harga untuk {symbol} (Timeframe: {timeframe}).")
|
|
return pd.DataFrame()
|
|
|
|
df = pd.DataFrame(rates)
|
|
df['time'] = pd.to_datetime(df['time'], unit='s')
|
|
|
|
df.set_index('time', inplace=True) # Jadikan kolom 'time' sebagai index DataFrame
|
|
|
|
return df
|
|
except Exception as e:
|
|
logger.error(f"Error saat get_rates_mt5 untuk {symbol}: {e}", exc_info=True)
|
|
return pd.DataFrame()
|
|
|
|
def get_open_positions_mt5():
|
|
"""Mengambil semua posisi trading yang sedang terbuka dari akun MT5."""
|
|
try:
|
|
positions = mt5.positions_get()
|
|
if positions is None:
|
|
return []
|
|
# Mengubah tuple objek menjadi list dictionary
|
|
return [pos._asdict() for pos in positions]
|
|
except Exception as e:
|
|
logger.error(f"Error saat get_open_positions_mt5: {e}", exc_info=True)
|
|
return []
|
|
|
|
def get_trade_history_mt5(days: int = 30):
|
|
"""Mengambil riwayat transaksi yang sudah ditutup dari MT5."""
|
|
try:
|
|
from_date = datetime.now() - timedelta(days=days)
|
|
deals = mt5.history_deals_get(from_date, datetime.now())
|
|
if deals is None:
|
|
logger.warning("Gagal mengambil histori deals dari MT5.")
|
|
return []
|
|
# Filter hanya deal penutupan (entry == 1) dan konversi ke dict
|
|
closed_deals = [d._asdict() for d in deals if d.entry == 1]
|
|
return closed_deals
|
|
except Exception as e:
|
|
logger.error(f"Error saat get_trade_history_mt5: {e}", exc_info=True)
|
|
return []
|
|
|
|
def get_todays_profit_mt5():
|
|
"""Menghitung total profit dari histori trading hari ini."""
|
|
try:
|
|
from_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
to_date = datetime.now()
|
|
|
|
deals = mt5.history_deals_get(from_date, to_date)
|
|
|
|
if deals is None:
|
|
logger.warning("Gagal mengambil histori deals untuk profit hari ini.")
|
|
return 0.0
|
|
|
|
# Gunakan generator expression untuk efisiensi dan filter deal penutupan
|
|
return sum(d.profit for d in deals if d.entry == 1)
|
|
except Exception as e:
|
|
logger.error(f"Error saat get_todays_profit_mt5: {e}", exc_info=True)
|
|
return 0.0
|
|
|
|
def find_mt5_symbol(base_symbol: str) -> str | None:
|
|
"""
|
|
Mencari nama simbol yang benar di MT5 berdasarkan nama dasar.
|
|
Fungsi ini mencoba mencocokkan variasi umum (suffix, prefix, nama alternatif)
|
|
dan memastikan simbol tersebut terlihat di Market Watch.
|
|
|
|
Args:
|
|
base_symbol (str): Nama simbol dasar (misal, "XAUUSD", "EURUSD").
|
|
|
|
Returns:
|
|
str | None: Nama simbol yang valid dan terlihat di MT5, atau None jika tidak ditemukan.
|
|
"""
|
|
import re
|
|
base_symbol_cleaned = re.sub(r'[^A-Z0-9]', '', base_symbol.upper())
|
|
|
|
try:
|
|
all_symbols = mt5.symbols_get()
|
|
if all_symbols is None:
|
|
logger.error("Gagal mengambil daftar simbol dari MT5.")
|
|
return None
|
|
except Exception as e:
|
|
logger.error(f"Error saat mengambil daftar simbol dari MT5: {e}")
|
|
return None
|
|
|
|
visible_symbols = {s.name for s in all_symbols if s.visible}
|
|
|
|
# 1. Cek kecocokan langsung (paling umum)
|
|
if base_symbol_cleaned in visible_symbols:
|
|
logger.info(f"Simbol '{base_symbol_cleaned}' ditemukan secara langsung.")
|
|
return base_symbol_cleaned
|
|
|
|
# 2. Buat pola regex untuk mencari variasi
|
|
pattern = re.compile(f"^[a-zA-Z]*{base_symbol_cleaned}[a-zA-Z0-9._-]*$", re.IGNORECASE)
|
|
|
|
# Cari di antara simbol yang terlihat
|
|
for symbol_name in visible_symbols:
|
|
if pattern.match(symbol_name):
|
|
logger.info(f"Variasi simbol '{symbol_name}' ditemukan untuk basis '{base_symbol_cleaned}'.")
|
|
if mt5.symbol_select(symbol_name, True):
|
|
return symbol_name
|
|
else:
|
|
logger.warning(f"Simbol '{symbol_name}' ditemukan tapi gagal diaktifkan.")
|
|
|
|
logger.warning(f"Tidak ada variasi simbol yang valid dan terlihat untuk '{base_symbol}' ditemukan di Market Watch.")
|
|
return None |