Files
quantumbotx/core/backtesting/engine.py
T
Reynov Christian c78fe82e8b Refactor app and templates for backtesting and active page tracking
- Update `app.py` to include backtesting routes and improve blueprint registration.
- Modify templates to include an active page parameter for navigation highlighting.
- Remove unused `crypto_data.py` and `api_crypto.py`.
- Add new backtesting-related files and templates.
2025-08-02 21:26:22 +08:00

123 lines
5.5 KiB
Python

# core/backtesting/engine.py
from core.strategies.strategy_map import STRATEGY_MAP
def run_backtest(strategy_id, params, historical_data_df):
"""
Menjalankan simulasi backtesting untuk strategi tertentu pada data historis.
"""
strategy_class = STRATEGY_MAP.get(strategy_id)
if not strategy_class:
return {"error": "Strategi tidak ditemukan"}
# Inisialisasi state backtesting
trades = []
in_position = False
initial_capital = 10000 # Modal awal virtual $10,000
capital = initial_capital
equity_curve = [initial_capital]
peak_equity = initial_capital
max_drawdown = 0.0
position_type = None
entry_price = 0.0
sl_pips = params.get('sl_pips', 100)
tp_pips = params.get('tp_pips', 200)
# Asumsi pip value sederhana untuk backtesting, bisa disempurnakan nanti
# Untuk pair JPY, point adalah 0.001, untuk yang lain 0.00001
point = 0.001 if 'JPY' in historical_data_df.columns[0].upper() else 0.00001
# Asumsi nilai per pip untuk 0.01 lot
# Ini adalah penyederhanaan besar, tapi cukup untuk backtesting awal
value_per_pip = 0.1 # $0.10 per pip
pip_value = 10 * point
# Mock bot object untuk strategi
class MockBot:
def __init__(self):
self.market_for_mt5 = "BACKTEST"
self.timeframe = "H1"
self.tf_map = {}
# Inisialisasi strategi dengan parameter yang diberikan
strategy_instance = strategy_class(bot_instance=MockBot(), params=params)
# Loop melalui setiap bar data historis
for i in range(1, len(historical_data_df)):
# Buat DataFrame "seolah-olah" ini adalah data real-time hingga bar saat ini
# PERBAIKAN: Gunakan .copy() untuk membuat salinan eksplisit dari slice.
# Ini akan menghilangkan SettingWithCopyWarning di semua strategi.
current_market_data = historical_data_df.iloc[:i].copy()
analysis = strategy_instance.analyze(current_market_data)
signal = analysis.get("signal")
current_price = historical_data_df.iloc[i]['close']
# Cek SL/TP jika sedang dalam posisi
if in_position:
profit = 0
if position_type == 'BUY':
profit_pips = (current_price - entry_price) / point / 10
if current_price <= entry_price - (sl_pips * pip_value):
trades.append({'entry': entry_price, 'exit': current_price, 'profit_pips': profit_pips, 'reason': 'SL'})
capital += profit_pips * value_per_pip
in_position = False
elif current_price >= entry_price + (tp_pips * pip_value):
trades.append({'entry': entry_price, 'exit': current_price, 'profit_pips': profit_pips, 'reason': 'TP'})
capital += profit_pips * value_per_pip
in_position = False
elif position_type == 'SELL':
profit_pips = (entry_price - current_price) / point / 10
if current_price >= entry_price + (sl_pips * pip_value):
trades.append({'entry': entry_price, 'exit': current_price, 'profit_pips': profit_pips, 'reason': 'SL'})
capital += profit_pips * value_per_pip
in_position = False
elif current_price <= entry_price - (tp_pips * pip_value):
trades.append({'entry': entry_price, 'exit': current_price, 'profit_pips': profit_pips, 'reason': 'TP'})
capital += profit_pips * value_per_pip
in_position = False
if not in_position: # Jika posisi baru saja ditutup
equity_curve.append(capital)
peak_equity = max(peak_equity, capital)
drawdown = (peak_equity - capital) / peak_equity
max_drawdown = max(max_drawdown, drawdown)
# Cek sinyal baru
if signal == 'BUY' and not in_position:
in_position = True
position_type = 'BUY'
entry_price = current_price
elif signal == 'SELL' and not in_position:
in_position = True
position_type = 'SELL'
entry_price = current_price
elif (signal == 'SELL' and in_position and position_type == 'BUY') or \
(signal == 'BUY' and in_position and position_type == 'SELL'):
# Sinyal berlawanan, tutup posisi lama
profit_pips = ((current_price - entry_price) if position_type == 'BUY' else (entry_price - current_price)) / point / 10
trades.append({'entry': entry_price, 'exit': current_price, 'profit_pips': profit_pips, 'reason': 'Signal Flip'})
capital += profit_pips * value_per_pip
equity_curve.append(capital)
peak_equity = max(peak_equity, capital)
drawdown = (peak_equity - capital) / peak_equity
max_drawdown = max(max_drawdown, drawdown)
in_position = False
# Hitung hasil akhir
total_profit_pips = sum(trade['profit_pips'] for trade in trades)
wins = len([trade for trade in trades if trade['profit_pips'] > 0])
losses = len(trades) - wins
win_rate = (wins / len(trades) * 100) if trades else 0
return {
"total_trades": len(trades),
"total_profit_pips": total_profit_pips,
"win_rate_percent": win_rate,
"wins": wins,
"losses": losses,
"max_drawdown_percent": max_drawdown * 100,
"equity_curve": equity_curve,
"trades": trades[-20:] # Tampilkan 20 trade terakhir
}