Refactor and update forex and stock modal handling in JavaScript

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.
This commit is contained in:
Reynov Christian
2025-08-08 10:39:03 +08:00
parent 53ae5e8861
commit 7b74743d58
62 changed files with 499387 additions and 1002 deletions
+65 -55
View File
@@ -5,83 +5,92 @@ 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.
VERSI OPTIMIZED: Indikator dihitung sekali di awal.
"""
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
# --- LANGKAH 1: Hitung semua indikator SEKALI di awal ---
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']
# Panggil metode baru 'analyze_df' untuk pra-perhitungan indikator
df_with_indicators = strategy_instance.analyze_df(historical_data_df.copy())
if df_with_indicators.empty:
return {"error": "Gagal menghasilkan data indikator. Periksa panjang data input."}
strategy_name = strategy_instance.name
# --- LANGKAH 2: Inisialisasi state backtesting ---
trades = []
in_position = False
initial_capital = 10000
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)
symbol_name = historical_data_df.columns[0].upper()
pip_size = 0.0001 # Default untuk Forex standar
if 'JPY' in symbol_name:
pip_size = 0.01
elif 'XAU' in symbol_name or 'XAG' in symbol_name: # Emas atau Perak
pip_size = 0.01
# Tentukan nilai per pip berdasarkan simbol (untuk lot 0.01)
if 'XAU' in symbol_name or 'XAG' in symbol_name: # Emas atau Perak
# Untuk 0.01 lot (1 oz), pergerakan harga $0.01 = profit/loss $0.01
value_per_pip = 0.01
else:
# Untuk Forex (misal EURUSD), 0.01 lot, pergerakan 1 pip = profit/loss $0.1
# Ini adalah asumsi umum, untuk JPY pairs nilainya bisa sedikit berbeda
value_per_pip = 0.1
# --- LANGKAH 3: Loop melalui data yang sudah ada indikatornya ---
for i in range(1, len(df_with_indicators)):
current_bar = df_with_indicators.iloc[i]
signal = current_bar.get("signal", "HOLD")
current_price = current_bar['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
profit_pips = (current_price - entry_price) / pip_size
if current_price <= entry_price - (sl_pips * pip_size):
trades.append({'entry': entry_price, 'exit': current_price, 'profit_pips': -sl_pips, 'reason': 'SL'})
capital -= sl_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
elif current_price >= entry_price + (tp_pips * pip_size):
trades.append({'entry': entry_price, 'exit': current_price, 'profit_pips': tp_pips, 'reason': 'TP'})
capital += tp_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
profit_pips = (entry_price - current_price) / pip_size
if current_price >= entry_price + (sl_pips * pip_size):
trades.append({'entry': entry_price, 'exit': current_price, 'profit_pips': -sl_pips, 'reason': 'SL'})
capital -= sl_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
elif current_price <= entry_price - (tp_pips * pip_size):
trades.append({'entry': entry_price, 'exit': current_price, 'profit_pips': tp_pips, 'reason': 'TP'})
capital += tp_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
drawdown = (peak_equity - capital) / peak_equity if peak_equity > 0 else 0
max_drawdown = max(max_drawdown, drawdown)
# Cek sinyal baru
@@ -96,12 +105,12 @@ def run_backtest(strategy_id, params, historical_data_df):
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'})
profit_pips = ((current_price - entry_price) if position_type == 'BUY' else (entry_price - current_price)) / pip_size # Calculate pips
trades.append({'entry': entry_price, 'exit': current_price, 'profit_pips': profit_pips, 'reason': 'Signal Flip'}) # Log trade
capital += profit_pips * value_per_pip
equity_curve.append(capital)
peak_equity = max(peak_equity, capital)
drawdown = (peak_equity - capital) / peak_equity
drawdown = (peak_equity - capital) / peak_equity if peak_equity > 0 else 0
max_drawdown = max(max_drawdown, drawdown)
in_position = False
@@ -112,6 +121,7 @@ def run_backtest(strategy_id, params, historical_data_df):
win_rate = (wins / len(trades) * 100) if trades else 0
return {
"strategy_name": strategy_name,
"total_trades": len(trades),
"total_profit_pips": total_profit_pips,
"win_rate_percent": win_rate,
@@ -119,5 +129,5 @@ def run_backtest(strategy_id, params, historical_data_df):
"losses": losses,
"max_drawdown_percent": max_drawdown * 100,
"equity_curve": equity_curve,
"trades": trades[-20:] # Tampilkan 20 trade terakhir
"trades": trades[-20:]
}