51 lines
1.9 KiB
Python
51 lines
1.9 KiB
Python
from __future__ import annotations
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from typing import Dict, List, Any
|
|
|
|
|
|
@dataclass
|
|
class BacktestResult:
|
|
pnl: float
|
|
trades: int
|
|
timestamp: datetime
|
|
|
|
|
|
def backtest(plan_delta: dict, initial_capital: float = 100000.0) -> BacktestResult:
|
|
# Very simple deterministic replay: PnL proportional to sum of absolute deltas
|
|
total_delta = sum(abs(v) for v in (plan_delta or {}).values())
|
|
pnl = initial_capital * 0.0001 * total_delta # toy PnL model
|
|
return BacktestResult(pnl=pnl, trades=len(plan_delta or {}), timestamp=datetime.utcnow())
|
|
|
|
|
|
class Backtester:
|
|
def __init__(self, initial_cash: float = 0.0, seed: int | None = None):
|
|
self.initial_cash = initial_cash
|
|
self.seed = seed
|
|
|
|
def apply(self, signals: List[Any], plan) -> float:
|
|
# Deterministic, minimal cash-impact calculation based on plan.deltas or plan.delta
|
|
total_cost = 0.0
|
|
deltas: List[Any] = []
|
|
if plan is not None:
|
|
if getattr(plan, "deltas", None):
|
|
deltas = plan.deltas # type: ignore
|
|
elif getattr(plan, "delta", None):
|
|
deltas = plan.delta # type: ignore
|
|
|
|
for item in deltas:
|
|
if isinstance(item, dict):
|
|
s = item.get("size", 0.0)
|
|
p = item.get("price", 0.0)
|
|
total_cost += abs(float(s)) * float(p)
|
|
# If item is a StrategyDelta, accumulate its delta_positions if provided
|
|
elif hasattr(item, "delta_positions") and isinstance(item.delta_positions, dict): # type: ignore
|
|
# Without explicit prices, assume no cost from these in this toy model
|
|
pass
|
|
|
|
return float(self.initial_cash) - total_cost
|
|
|
|
def replay(self, signals: List[Any], plan) -> float:
|
|
# Alias for compatibility with tests that call replay
|
|
return self.apply(signals, plan)
|