Bug Cleanup

This commit is contained in:
moen0
2026-05-11 19:14:32 +02:00
parent 130da68f4e
commit 473fe3bd88
12 changed files with 348 additions and 150 deletions
+81 -41
View File
@@ -10,7 +10,7 @@ from fastapi import FastAPI, HTTPException, Query
from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from indicators.sessions import set_timezone
BACKEND_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if BACKEND_DIR not in sys.path:
sys.path.insert(0, BACKEND_DIR)
@@ -141,13 +141,29 @@ def _get_candles_for_timeframe(dataset_id, timeframe):
def _build_strategy(
session, lookback, ob_age, atr_mult, use_fvg, use_ob,
proximity_pct, sweep, sweep_lookback,
min_gap_size, impulse_multiplier, require_unmitigated_fvg,
require_bos_confluence, min_ob_size, require_fvg_ob_confluence,
asian_sweep_only, day_filter,
use_break_even=False, be_trigger_rr=1.0,
use_partial_tp=False, partial_tp_rr=1.0, partial_tp_percent=50.0,
session="new_york",
lookback=5,
ob_age=50,
atr_mult=1.5,
use_fvg=True,
use_ob=True,
proximity_pct=0.3,
sweep=True,
sweep_lookback=10,
min_gap_size=0.0,
impulse_multiplier=0.0,
require_unmitigated_fvg=True,
require_bos_confluence=False,
min_ob_size=0.0,
require_fvg_ob_confluence=False,
asian_sweep_only=False,
day_filter=None,
use_break_even=False,
be_trigger_rr=1.0,
use_partial_tp=False,
partial_tp_rr=1.0,
partial_tp_percent=50.0,
timezone="est",
):
return ICTStrategy(
session=session,
@@ -172,6 +188,7 @@ def _build_strategy(
use_partial_tp=use_partial_tp,
partial_tp_rr=partial_tp_rr,
partial_tp_percent=partial_tp_percent,
timezone=timezone,
)
@@ -189,12 +206,28 @@ def _trade_payload(trade):
}
def _stats_payload(trades, rr):
def _stats_payload(trades, rr, starting_balance=10000.0):
total_pnl = sum(t.pnl for t in trades)
winners = [t for t in trades if t.pnl > 0]
losers = [t for t in trades if t.pnl <= 0]
partial_tp_trades = [t for t in trades if getattr(t, "partial_tp_taken", False)]
partial_tp_realized_total = sum(float(getattr(t, "partial_tp_realized_pnl", 0.0) or 0.0) for t in partial_tp_trades)
pnls = [t.pnl for t in trades]
returns = [(p / starting_balance) for p in pnls] if starting_balance > 0 else []
mean_return = (sum(returns) / len(returns)) if returns else 0.0
variance = (sum((r - mean_return) ** 2 for r in returns) / len(returns)) if returns else 0.0
std_dev = variance ** 0.5
sharpe_ratio = ((mean_return / std_dev) * (len(returns) ** 0.5)) if std_dev > 0 else 0.0
equity_points = _build_equity_points(trades, starting_balance=starting_balance)
peak = equity_points[0] if equity_points else starting_balance
max_drawdown_pct = 0.0
for value in equity_points:
if value > peak:
peak = value
drawdown_pct = ((peak - value) / peak) * 100 if peak > 0 else 0.0
if drawdown_pct > max_drawdown_pct:
max_drawdown_pct = drawdown_pct
return {
"total_trades": len(trades),
"winners": len(winners),
@@ -208,6 +241,8 @@ def _stats_payload(trades, rr):
"partial_tp_rate": (len(partial_tp_trades) / len(trades) * 100) if trades else 0,
"partial_tp_realized_total": partial_tp_realized_total,
"partial_tp_realized_avg": (partial_tp_realized_total / len(partial_tp_trades)) if partial_tp_trades else 0,
"sharpe_ratio": round(sharpe_ratio, 6),
"max_drawdown_pct": round(max_drawdown_pct, 6),
}
@@ -310,7 +345,7 @@ def _risk_metrics(trades, starting_balance=10000.0):
sortino = (mean_pnl / downside_dev) * (trade_count ** 0.5) if downside_dev > 0 else 0.0
equity_points = _build_equity_points(trades, starting_balance=starting_balance)
peak = equity_points[0]
peak = equity_points[0] if equity_points else starting_balance
max_drawdown_pct = 0.0
for value in equity_points:
if value > peak:
@@ -319,8 +354,9 @@ def _risk_metrics(trades, starting_balance=10000.0):
if drawdown_pct > max_drawdown_pct:
max_drawdown_pct = drawdown_pct
calmar = (net_pnl / max_drawdown_pct) if max_drawdown_pct > 0 else 0.0
recovery = (net_pnl / max_drawdown_pct) if max_drawdown_pct > 0 else 0.0
calmar = ((net_pnl / starting_balance) * 100 / max_drawdown_pct) if max_drawdown_pct > 0 else 0.0
drawdown_amount = starting_balance * (max_drawdown_pct / 100) if max_drawdown_pct > 0 else 0.0
recovery = (net_pnl / drawdown_amount) if drawdown_amount > 0 else 0.0
if trade_count < 80:
trade_score = max(0.0, trade_count / 80)
@@ -524,24 +560,22 @@ def get_backtest(
max_consecutive_losses: int = 0,
):
dataset_id = _resolve_dataset(dataset)
if "MT5" in dataset.upper():
set_timezone("mt5")
else:
set_timezone("est")
timezone = "mt5" if "MT5" in dataset.upper() else "est"
candles = _get_candles_for_timeframe(dataset_id, timeframe)
strategy = _build_strategy(
session=session, lookback=lookback, ob_age=ob_age, atr_mult=atr_mult,
use_fvg=use_fvg, use_ob=use_ob, proximity_pct=proximity_pct,
sweep=sweep, sweep_lookback=sweep_lookback,
min_gap_size=min_gap_size, impulse_multiplier=impulse_multiplier,
require_unmitigated_fvg=require_unmitigated_fvg,
require_bos_confluence=require_bos_confluence,
min_ob_size=min_ob_size, require_fvg_ob_confluence=require_fvg_ob_confluence,
asian_sweep_only=asian_sweep_only, day_filter=day_filter,
use_break_even=use_break_even, be_trigger_rr=be_trigger_rr,
use_partial_tp=use_partial_tp, partial_tp_rr=partial_tp_rr, partial_tp_percent=partial_tp_percent,
)
session=session, lookback=lookback, ob_age=ob_age, atr_mult=atr_mult,
use_fvg=use_fvg, use_ob=use_ob, proximity_pct=proximity_pct,
sweep=sweep, sweep_lookback=sweep_lookback,
min_gap_size=min_gap_size, impulse_multiplier=impulse_multiplier,
require_unmitigated_fvg=require_unmitigated_fvg,
require_bos_confluence=require_bos_confluence,
min_ob_size=min_ob_size, require_fvg_ob_confluence=require_fvg_ob_confluence,
asian_sweep_only=asian_sweep_only, day_filter=day_filter,
use_break_even=use_break_even, be_trigger_rr=be_trigger_rr,
use_partial_tp=use_partial_tp, partial_tp_rr=partial_tp_rr, partial_tp_percent=partial_tp_percent,
timezone=timezone,
)
trades = run_backtest(
candles, strategy, 10000, risk_reward=rr,
max_daily_loss=max_daily_loss,
@@ -551,7 +585,7 @@ def get_backtest(
return {
"trades": [_trade_payload(t) for t in trades],
"candle_times": [c.time_open.isoformat() for c in candles],
"stats": _stats_payload(trades, rr),
"stats": _stats_payload(trades, rr, starting_balance=10000.0),
}
@@ -585,6 +619,7 @@ def backtest_monte_carlo(req: MonteCarloRequest):
@app.post("/api/optimize")
def get_optimize(req: OptimizeRequest):
dataset_id = _resolve_dataset(req.dataset)
timezone = "mt5" if "MT5" in dataset_id.upper() else "est"
candles = _get_candles_for_timeframe(dataset_id, req.timeframe)
session_list = req.sessions
@@ -709,6 +744,7 @@ def get_optimize(req: OptimizeRequest):
use_partial_tp=params["use_partial_tp"],
partial_tp_rr=params["partial_tp_rr"],
partial_tp_percent=params["partial_tp_percent"],
timezone=timezone,
)
trades = run_backtest(candles, strategy, 10000, risk_reward=params["rr"])
@@ -815,6 +851,7 @@ def get_optimize_monte_carlo(
ruin_drawdown_pct: float = Query(default=20.0, ge=0.0, le=100.0),
):
dataset_id = _resolve_dataset(dataset)
timezone = "mt5" if "MT5" in dataset_id.upper() else "est"
candles = _get_candles_for_timeframe(dataset_id, timeframe)
strategy = _build_strategy(
@@ -840,6 +877,7 @@ def get_optimize_monte_carlo(
partial_tp_rr=partial_tp_rr,
partial_tp_percent=partial_tp_percent,
day_filter=None,
timezone=timezone,
)
trades = run_backtest(candles, strategy, 10000, risk_reward=rr)
trade_r_multiples = [getattr(t, "r_multiple", 0.0) for t in trades]
@@ -948,20 +986,22 @@ def stream_backtest(
max_consecutive_losses: int = 0,
):
dataset_id = _resolve_dataset(dataset)
timezone = "mt5" if "MT5" in dataset_id.upper() else "est"
candles = _get_candles_for_timeframe(dataset_id, timeframe)
strategy = _build_strategy(
session=session, lookback=lookback, ob_age=ob_age, atr_mult=atr_mult,
use_fvg=use_fvg, use_ob=use_ob, proximity_pct=proximity_pct,
sweep=sweep, sweep_lookback=sweep_lookback,
min_gap_size=min_gap_size, impulse_multiplier=impulse_multiplier,
require_unmitigated_fvg=require_unmitigated_fvg,
require_bos_confluence=require_bos_confluence,
min_ob_size=min_ob_size, require_fvg_ob_confluence=require_fvg_ob_confluence,
asian_sweep_only=asian_sweep_only, day_filter=day_filter,
use_break_even=use_break_even, be_trigger_rr=be_trigger_rr,
use_partial_tp=use_partial_tp, partial_tp_rr=partial_tp_rr, partial_tp_percent=partial_tp_percent,
)
session=session, lookback=lookback, ob_age=ob_age, atr_mult=atr_mult,
use_fvg=use_fvg, use_ob=use_ob, proximity_pct=proximity_pct,
sweep=sweep, sweep_lookback=sweep_lookback,
min_gap_size=min_gap_size, impulse_multiplier=impulse_multiplier,
require_unmitigated_fvg=require_unmitigated_fvg,
require_bos_confluence=require_bos_confluence,
min_ob_size=min_ob_size, require_fvg_ob_confluence=require_fvg_ob_confluence,
asian_sweep_only=asian_sweep_only, day_filter=day_filter,
use_break_even=use_break_even, be_trigger_rr=be_trigger_rr,
use_partial_tp=use_partial_tp, partial_tp_rr=partial_tp_rr, partial_tp_percent=partial_tp_percent,
timezone=timezone,
)
def _sse(data):
return f"data: {json.dumps(data)}\n\n"
@@ -993,7 +1033,7 @@ def stream_backtest(
yield _sse({
"type": "trade",
"trade": _trade_payload(trade),
"stats": _stats_payload(streamed_trades, rr),
"stats": _stats_payload(streamed_trades, rr, starting_balance=10000.0),
"processed_candles": event["processed_candles"],
"total_candles": event["total_candles"],
})
@@ -1002,7 +1042,7 @@ def stream_backtest(
yield _sse({
"type": "done",
"trades": [_trade_payload(t) for t in streamed_trades],
"stats": _stats_payload(streamed_trades, rr),
"stats": _stats_payload(streamed_trades, rr, starting_balance=10000.0),
"duration_ms": round(duration_ms, 1),
"candle_times": [c.time_open.isoformat() for c in candles],
})