Skip to content

Commit d142374

Browse files
committed
perf(metrics): O(N^2) -> O(N) snapshot lookup in rolling Sharpe
get_rolling_sharpe_ratio used next((s for s in snapshots if s.created_at == date), None) once per output row, scanning the full snapshot list every time. With ~7600 daily rows this was roughly 47% of recalculate_backtests() runtime in profiling. Replaced with a one-shot {created_at: snapshot} dict lookup. End-to-end recalculate_backtests on the 6-strategy x 11-runs example batch: 20.8s -> 10.9s (~1.9x). recalculate_backtests itself is unchanged and works on bundle- and legacy-loaded backtests identically since it operates on Backtest objects.
1 parent d723f3e commit d142374

1 file changed

Lines changed: 6 additions & 2 deletions

File tree

investing_algorithm_framework/services/metrics/sharpe_ratio.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,15 +121,19 @@ def get_rolling_sharpe_ratio(
121121
# Ensure chronological order
122122
snapshots = sorted(snapshots, key=lambda s: s.created_at)
123123

124+
# O(1) lookup of snapshot by created_at — avoids the O(N^2) scan
125+
# that previously dominated recalculate_backtests on large
126+
# backtests.
127+
snapshot_by_dt = {s.created_at: s for s in snapshots}
128+
124129
result = []
125130
for date, sharpe in rolling_sharpe_s.items():
126131

127132
if pd.isna(sharpe):
128133
result.append((sharpe, date))
129134
continue
130135

131-
# Find the corresponding snapshot
132-
snapshot = next((s for s in snapshots if s.created_at == date), None)
136+
snapshot = snapshot_by_dt.get(date)
133137

134138
if snapshot:
135139
result.append((sharpe, snapshot.created_at))

0 commit comments

Comments
 (0)