PR5: backtesting - replay the strategy over historical prices #5

Merged
DasAoD merged 2 commits from feature/backtesting into main 2026-08-28 22:14:14 +02:00
Owner

Fünfter PR. krypto-dca backtest spielt die aktuelle Konfiguration gegen echte Vergangenheitskurse durch.

Inhalt

  • backtest.py – run() ist eine reine Funktion über {symbol: [(ts, close)]}:
    • vergleicht drei Strategien über denselben Zeitraum und dasselbe Geld: wöchentliches DCA ([strategy]), DCA + Dip ([dip]-Regel auf die historischen Closes), Buy & Hold (Einmalkauf am Start, nach Gewichten)
    • je Strategie: eingezahlt, Endwert, PnL abs/%, CAGR, max. Drawdown, Anzahl regulärer + Dip-Käufe
    • max_total_spend wird respektiert; Fee = paper.fee_rate
    • Backtest läuft nur über das gemeinsame Zeitfenster aller Assets; BacktestError wenn es keine Überlappung gibt
  • backtest Subcommand – --from/--to oder --days N, --timeframe 1d|1w (Default 1d), --refresh, --json
    • Fortschritts- und Horizont-Hinweise gehen nach stderr, der Report (Tabelle oder JSON) nach stdout
  • OHLC-Cache – neue Tabelle ohlcv_cache (symbol, timeframe, ts, close), Helfer upsert_ohlcv / get_ohlcv / ohlcv_span; init_db legt sie idempotent an, bestehende DBs bekommen sie beim nächsten Lauf
    • kraken_client.fetch_ohlcv_closes(); zweiter Lauf im selben Fenster nutzt den Cache (kleine Slack-Toleranz für Kerzen-Ausrichtung), --refresh erzwingt Neuabruf
  • Read-only: schreibt nie in trades / balance_snapshots / price_history

Grenze (dokumentiert)

Krakens OHLC-Endpoint gibt max. ~720 Kerzen zurück und kann nicht weiter zurück paginieren → praktisch ~2 Jahre bei 1d, weiter zurück nur mit --timeframe 1w. Der Befehl warnt, wenn das Fenster früher beginnt. Eine zweite Datenquelle für mehrjährige Tages-Backtests wäre ein späterer PR.

Verifiziert

  • pytest → 67 passed (12 neu): flacher/steigender Markt, Spend-Cap, Dip-Variante kauft den Dip und liegt danach vorn, max. 1 Dip/ISO-Woche, Overlap-Guard, gemeinsames Fenster, ohlcv_cache-Round-Trip, Report-Form; backtest-CLI Tabelle + --json + Exit-Code
  • End-to-End gegen echte Kraken-Kurse:
    • --days 400 (1d): 116 Wochenkäufe, in fallendem Markt DCA −5 % vs. Buy&Hold −34 %
    • --from 2019-01-01 --timeframe 1w: 250 Wochenkäufe bis zum max_total_spend-Cap, im Bullrun Buy&Hold klar vorn
    • zweiter Lauf ohne --fetch-Zeile (Cache greift), --refresh holt neu

Beispiel

Backtest 2025-11-02 .. 2026-08-28  (299 days, 1d candles)
  BTC/EUR 95,814.30 -> 66,836.20, ETH/EUR 3,386.59 -> 2,098.77

                   Weekly DCA   DCA + dip  Buy & Hold
invested               580.00      900.00      580.00
end value (EUR)        ...
PnL %                  ...
CAGR                   ...
max drawdown           ...
regular buys              ...
dip buys                   0          ..           0

Co-authored-by: Claude claude@anthropic.com

Fünfter PR. `krypto-dca backtest` spielt die **aktuelle** Konfiguration gegen echte Vergangenheitskurse durch. ## Inhalt - **`backtest.py`** &ndash; `run()` ist eine reine Funktion über `{symbol: [(ts, close)]}`: - vergleicht drei Strategien über **denselben Zeitraum und dasselbe Geld**: wöchentliches DCA (`[strategy]`), DCA + Dip (`[dip]`-Regel auf die historischen Closes), Buy & Hold (Einmalkauf am Start, nach Gewichten) - je Strategie: eingezahlt, Endwert, PnL abs/%, CAGR, max. Drawdown, Anzahl regulärer + Dip-Käufe - `max_total_spend` wird respektiert; Fee = `paper.fee_rate` - Backtest läuft nur über das **gemeinsame** Zeitfenster aller Assets; `BacktestError` wenn es keine Überlappung gibt - **`backtest` Subcommand** &ndash; `--from/--to` **oder** `--days N`, `--timeframe 1d|1w` (Default `1d`), `--refresh`, `--json` - Fortschritts- und Horizont-Hinweise gehen nach **stderr**, der Report (Tabelle oder JSON) nach **stdout** - **OHLC-Cache** &ndash; neue Tabelle `ohlcv_cache (symbol, timeframe, ts, close)`, Helfer `upsert_ohlcv` / `get_ohlcv` / `ohlcv_span`; `init_db` legt sie idempotent an, bestehende DBs bekommen sie beim nächsten Lauf - `kraken_client.fetch_ohlcv_closes()`; zweiter Lauf im selben Fenster nutzt den Cache (kleine Slack-Toleranz für Kerzen-Ausrichtung), `--refresh` erzwingt Neuabruf - **Read-only:** schreibt nie in `trades` / `balance_snapshots` / `price_history` ## Grenze (dokumentiert) Krakens OHLC-Endpoint gibt **max. ~720 Kerzen** zurück und kann nicht weiter zurück paginieren &rarr; praktisch ~2 Jahre bei `1d`, weiter zurück nur mit `--timeframe 1w`. Der Befehl warnt, wenn das Fenster früher beginnt. Eine zweite Datenquelle für mehrjährige Tages-Backtests wäre ein späterer PR. ## Verifiziert - `pytest` &rarr; **67 passed** (12 neu): flacher/steigender Markt, Spend-Cap, Dip-Variante kauft den Dip und liegt danach vorn, max. 1 Dip/ISO-Woche, Overlap-Guard, gemeinsames Fenster, `ohlcv_cache`-Round-Trip, Report-Form; `backtest`-CLI Tabelle + `--json` + Exit-Code - End-to-End gegen echte Kraken-Kurse: - `--days 400` (`1d`): 116 Wochenkäufe, in fallendem Markt DCA &minus;5 % vs. Buy&Hold &minus;34 % - `--from 2019-01-01 --timeframe 1w`: 250 Wochenkäufe bis zum `max_total_spend`-Cap, im Bullrun Buy&Hold klar vorn - zweiter Lauf ohne `--fetch`-Zeile (Cache greift), `--refresh` holt neu ## Beispiel ``` Backtest 2025-11-02 .. 2026-08-28 (299 days, 1d candles) BTC/EUR 95,814.30 -> 66,836.20, ETH/EUR 3,386.59 -> 2,098.77 Weekly DCA DCA + dip Buy & Hold invested 580.00 900.00 580.00 end value (EUR) ... PnL % ... CAGR ... max drawdown ... regular buys ... dip buys 0 .. 0 ``` Co-authored-by: Claude <claude@anthropic.com>
- backtest.run(): pure replay of [strategy] + [dip] over historical closes,
  comparing weekly DCA / DCA+dip / buy&hold on the same window and same money;
  reports invested, end value, PnL, CAGR, max drawdown, buy counts
- new `backtest` subcommand: --from/--to or --days, --timeframe 1d|1w,
  --refresh, --json; progress + horizon notes go to stderr, report to stdout
- kraken_client.fetch_ohlcv_closes() + new ohlcv_cache table + db helpers
  (upsert_ohlcv / get_ohlcv / ohlcv_span); cached per (symbol, timeframe),
  reused across runs unless --refresh
- Kraken serves ~720 candles max and cannot page back; the command warns when
  the window starts earlier. Read-only: never touches trades/snapshots.

Co-authored-by: Claude <claude@anthropic.com>
- test_backtest.py: flat/rising markets, spend cap, dip variant buys the dip
  and ends ahead, one dip per ISO week, overlapping-history guard, shared-window
  trim, ohlcv_cache round-trip, text report shape
- test_cli.py: `backtest` table + --json output, missing-window exit code;
  _OHLCExchange fake
- README + SETUP: backtest usage, ~720-candle horizon, ohlcv_cache; PR5 done

Co-authored-by: Claude <claude@anthropic.com>
DasAoD merged commit 69fa420558 into main 2026-08-28 22:14:14 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
DasAoD/krypto-dca-bot!5
No description provided.