diff --git a/scripts/backtest_replay.py b/scripts/backtest_replay.py index f5ce5a9..0a0f617 100644 --- a/scripts/backtest_replay.py +++ b/scripts/backtest_replay.py @@ -30,8 +30,7 @@ def _parse_balances(raw: str) -> Mapping[str, float]: def main() -> int: - parser = argparse.ArgumentParser( - description="Run a deterministic replay backtest.") + parser = argparse.ArgumentParser(description="Run a deterministic replay backtest.") parser.add_argument("--events", type=Path, required=True) parser.add_argument("--starting-balances", type=str, default="USD=1000.0") parser.add_argument("--trade-capital", type=float, default=100.0) @@ -56,18 +55,15 @@ def main() -> int: started_at=events[0].occurred_at if events else datetime.now(UTC), ) report = asyncio.run( - engine.run(events, starting_balances=_parse_balances( - args.starting_balances)) + engine.run(events, starting_balances=_parse_balances(args.starting_balances)) ) print("Backtest report:") print(f"- processed_events: {report.processed_events}") print(f"- opportunities_seen: {report.opportunities_seen}") print(f"- trades_executed: {report.trades_executed}") - print( - f"- win_rate: {report.win_rate if report.win_rate is not None else 'n/a'}") - print( - f"- fill_rate: {report.fill_rate if report.fill_rate is not None else 'n/a'}") + print(f"- win_rate: {report.win_rate if report.win_rate is not None else 'n/a'}") + print(f"- fill_rate: {report.fill_rate if report.fill_rate is not None else 'n/a'}") print(f"- realized_pnl_usd: {report.realized_pnl_usd:.4f}") print(f"- max_drawdown_usd: {report.max_drawdown_usd:.4f}") print(f"- miss_reasons: {dict(report.miss_reasons)}") diff --git a/scripts/security_scan.py b/scripts/security_scan.py index ce90d3c..7d32cf0 100644 --- a/scripts/security_scan.py +++ b/scripts/security_scan.py @@ -63,16 +63,13 @@ def scan_worktree() -> list[str]: for rule_name, pattern in PATTERNS: if pattern.search(content): - findings.append( - f"worktree:{path.relative_to(WORKSPACE)}:{rule_name}") + findings.append(f"worktree:{path.relative_to(WORKSPACE)}:{rule_name}") return findings def scan_git_history() -> list[str]: - cmd = ["git", "-C", str(WORKSPACE), "log", "--all", - "-p", "--pretty=format:%H"] - completed = subprocess.run( - cmd, check=False, capture_output=True, text=True) + cmd = ["git", "-C", str(WORKSPACE), "log", "--all", "-p", "--pretty=format:%H"] + completed = subprocess.run(cmd, check=False, capture_output=True, text=True) if completed.returncode != 0: return ["history_scan_failed"] diff --git a/src/arbitrade/backtesting/replay.py b/src/arbitrade/backtesting/replay.py index e207134..0a14ec7 100644 --- a/src/arbitrade/backtesting/replay.py +++ b/src/arbitrade/backtesting/replay.py @@ -153,8 +153,7 @@ def _parse_book_levels(raw_levels: Any) -> tuple[BookLevel, ...]: or not isinstance(raw_level[1], int | float) ): raise ValueError("Each level must be [price, volume]") - levels.append(BookLevel(price=float( - raw_level[0]), volume=float(raw_level[1]))) + levels.append(BookLevel(price=float(raw_level[0]), volume=float(raw_level[1]))) return tuple(levels) @@ -173,8 +172,7 @@ def load_replay_events(path: Path) -> list[ReplayBookEvent]: if not isinstance(timestamp_raw, str) or not isinstance(symbol_raw, str): raise ValueError("Each event must include timestamp and symbol") - occurred_at = datetime.fromisoformat( - timestamp_raw.replace("Z", "+00:00")).astimezone(UTC) + occurred_at = datetime.fromisoformat(timestamp_raw.replace("Z", "+00:00")).astimezone(UTC) events.append( ReplayBookEvent( occurred_at=occurred_at, @@ -208,8 +206,7 @@ class BacktestReplayEngine: min_order_size_by_pair=config.min_order_size_by_pair, ) self._pre_trade = PreTradeValidator() - self._trade_limits = TradeLimitsGuard( - max_concurrent_trades=config.max_concurrent_trades) + self._trade_limits = TradeLimitsGuard(max_concurrent_trades=config.max_concurrent_trades) self._simulated_rest = _SimulatedRestClient( self._clock, slippage_bps=config.slippage_bps, @@ -244,8 +241,7 @@ class BacktestReplayEngine: trades_executed = 0 realized_pnl = 0.0 - equity = float(starting_balances.get( - self._config.quote_asset.upper(), 0.0)) + equity = float(starting_balances.get(self._config.quote_asset.upper(), 0.0)) peak_equity = equity max_drawdown = 0.0 @@ -288,8 +284,7 @@ class BacktestReplayEngine: result = await self._sequencer.execute(opportunity) self._trade_limits.close_trade(exposure) - execution_latencies.append( - self._simulated_rest.last_trade_latency_ms) + execution_latencies.append(self._simulated_rest.last_trade_latency_ms) fill_samples.append(self._simulated_rest.last_fill_ratio) if not result.success: @@ -312,8 +307,7 @@ class BacktestReplayEngine: wins = sum(1 for pnl in realized_samples if pnl > 0.0) win_rate = (wins / len(realized_samples)) if realized_samples else None - fill_rate = (sum(fill_samples) / len(fill_samples) - ) if fill_samples else None + fill_rate = (sum(fill_samples) / len(fill_samples)) if fill_samples else None return BacktestReport( started_at=events[0].occurred_at if events else self._clock.now, diff --git a/src/arbitrade/runtime/lifecycle.py b/src/arbitrade/runtime/lifecycle.py index 1da5f00..124611d 100644 --- a/src/arbitrade/runtime/lifecycle.py +++ b/src/arbitrade/runtime/lifecycle.py @@ -140,8 +140,7 @@ async def restore_runtime_state(app: FastAPI) -> RuntimeRecoveryReport: snapshot_at = latest.snapshot_at.isoformat() controls.is_running = latest.is_running if latest.kill_switch_active: - controls.kill_switch.activate( - reason=latest.kill_switch_reason or "recovered") + controls.kill_switch.activate(reason=latest.kill_switch_reason or "recovered") else: controls.kill_switch.deactivate() controls.mark_updated() @@ -151,8 +150,7 @@ async def restore_runtime_state(app: FastAPI) -> RuntimeRecoveryReport: if open_trades > 0: controls.is_running = False if not controls.kill_switch.is_active: - controls.kill_switch.activate( - reason="recovery_open_trades_detected") + controls.kill_switch.activate(reason="recovery_open_trades_detected") controls.mark_updated() restart_guard_active = True diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index 4e99090..17df8b0 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -191,8 +191,7 @@ async def test_dashboard_page_and_fragment_and_sse(tmp_path) -> None: assert "trade-open" in overview.text assert overview_stream.status_code == 200 - assert overview_stream.headers["content-type"].startswith( - "text/event-stream") + assert overview_stream.headers["content-type"].startswith("text/event-stream") assert "event: overview" in overview_stream.text assert "trade-open" in overview_stream.text @@ -262,8 +261,7 @@ async def test_dashboard_controls_update_runtime_state_and_config(tmp_path) -> N assert app.state.settings.max_trade_capital_usd == 300.0 assert app.state.settings.max_concurrent_trades == 4 assert app.state.settings.paper_trading_mode is True - assert app.state.dashboard_controls.tradable_pairs == [ - "BTC/USD", "ETH/BTC"] + assert app.state.dashboard_controls.tradable_pairs == ["BTC/USD", "ETH/BTC"] assert app.state.dashboard_controls.strategy_mode == "paper" assert app.state.dashboard_controls.strategy_profit_threshold == 0.0025 assert app.state.dashboard_controls.strategy_max_depth_levels == 7 @@ -275,14 +273,10 @@ async def test_dashboard_controls_update_runtime_state_and_config(tmp_path) -> N assert audit_recent.status_code == 200 entries = audit_recent.json()["entries"] assert len(entries) >= 4 - assert any(entry["event_type"] == - "dashboard.control.stop" for entry in entries) - assert any(entry["event_type"] == - "dashboard.control.start" for entry in entries) - assert any(entry["event_type"] == - "dashboard.control.kill_switch" for entry in entries) - assert any(entry["event_type"] == - "dashboard.control.config" for entry in entries) + assert any(entry["event_type"] == "dashboard.control.stop" for entry in entries) + assert any(entry["event_type"] == "dashboard.control.start" for entry in entries) + assert any(entry["event_type"] == "dashboard.control.kill_switch" for entry in entries) + assert any(entry["event_type"] == "dashboard.control.config" for entry in entries) async def test_dashboard_controls_emit_alerts(tmp_path) -> None: diff --git a/tests/unit/test_backtesting_replay.py b/tests/unit/test_backtesting_replay.py index 62992cd..32f35ad 100644 --- a/tests/unit/test_backtesting_replay.py +++ b/tests/unit/test_backtesting_replay.py @@ -67,13 +67,11 @@ def test_backtest_replay_engine_runs_deterministically() -> None: engine = BacktestReplayEngine( cycles_by_pair=cycles_by_pair, available_pairs=available_pairs, - config=BacktestConfig(trade_capital=100.0, - slippage_bps=5.0, execution_latency_ms=10.0), + config=BacktestConfig(trade_capital=100.0, slippage_bps=5.0, execution_latency_ms=10.0), started_at=started_at, ) - report = asyncio.run(engine.run( - replay_events, starting_balances={"USD": 1000.0})) + report = asyncio.run(engine.run(replay_events, starting_balances={"USD": 1000.0})) assert report.processed_events == 3 assert report.opportunities_seen >= 0