Coverage for src/finbot/backtest/comparison.py: 100%
48 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-04 12:05 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-04 12:05 +0000
1import math
2from collections.abc import Iterable
3from dataclasses import dataclass
4from datetime import datetime
5from uuid import UUID
7from finbot.backtest.experiment import ExperimentRun, WalkForwardExperimentRun
10@dataclass(frozen=True)
11class ExperimentComparisonRow:
12 run_id: UUID
13 strategy: str
14 symbols: tuple[str, ...]
15 start: datetime
16 end: datetime
18 starting_cash: float
19 ending_value: float
20 total_return: float
21 max_drawdown: float
22 total_fees: float
23 total_slippage: float
26def compare_experiments(runs: Iterable[ExperimentRun]) -> list[ExperimentComparisonRow]:
27 return [
28 ExperimentComparisonRow(
29 run_id=run.metadata.run_id,
30 strategy=run.metadata.config.strategy,
31 symbols=run.metadata.config.symbols,
32 start=run.metadata.config.start,
33 end=run.metadata.config.end,
34 starting_cash=run.result.starting_cash,
35 ending_value=run.result.ending_value,
36 total_return=run.result.total_return,
37 max_drawdown=run.result.max_drawdown,
38 total_fees=run.result.total_fees,
39 total_slippage=run.result.total_slippage,
40 )
41 for run in runs
42 ]
45@dataclass(frozen=True)
46class WalkForwardComparisonRow:
47 run_id: UUID
48 strategy: str
49 symbols: tuple[str, ...]
51 test_start: datetime
52 test_end: datetime
53 fold_count: int
55 compounded_return: float
56 average_fold_return: float
57 worst_fold_return: float
58 worst_fold_drawdown: float
60 total_fees: float
61 total_slippage: float
64def compare_walk_forward_experiments(
65 runs: Iterable[WalkForwardExperimentRun],
66) -> list[WalkForwardComparisonRow]:
67 out: list[WalkForwardComparisonRow] = []
69 for run in runs:
70 folds = run.result.folds
71 config = run.metadata.config
73 compounded_return = math.prod(1.0 + fold.test_result.total_return for fold in folds) - 1.0
74 average_fold_return = sum(fold.test_result.total_return for fold in folds) / len(folds)
75 worst_fold_result = min(fold.test_result.total_return for fold in folds)
76 worst_fold_drawdown = max(fold.test_result.max_drawdown for fold in folds)
78 total_fees = sum(fold.test_result.total_fees for fold in folds)
79 total_slippage = sum(fold.test_result.total_slippage for fold in folds)
81 out.append(
82 WalkForwardComparisonRow(
83 run_id=run.metadata.run_id,
84 strategy=config.strategy,
85 symbols=config.symbols,
87 test_start=folds[0].split.test.start,
88 test_end=folds[-1].split.test.end,
89 fold_count=len(folds),
91 compounded_return=compounded_return,
92 average_fold_return=average_fold_return,
93 worst_fold_return=worst_fold_result,
94 worst_fold_drawdown=worst_fold_drawdown,
96 total_fees=total_fees,
97 total_slippage=total_slippage,
98 ),
99 )
101 return out