diff --git a/notes/trading/system-docs/phase9_readiness_audit.md b/notes/trading/system-docs/phase9_readiness_audit.md new file mode 100644 index 0000000..7200858 --- /dev/null +++ b/notes/trading/system-docs/phase9_readiness_audit.md @@ -0,0 +1,341 @@ +--- +type: Note +tags: [trading, mt5, architecture, historical-v2, m12, m13, phase9, readiness-audit, phase2] +created: 2026-08-22 +--- + +# Phase 9 — M12/M13 Phase-2-Readiness-Audit + Implementierungsmatrix A–N + +> Autor: Rain Ocampo | Datum: 22.08.2026 | Status: ✅ ANALYSE + DESIGN + VERIFIKATION (KEINE Implementierung) +> **Geändert von: Rain Ocampo, Datum: 22.08.2026, Grund: Phase-9-Audit (read-only, Code-Evidenz)** + +## Executive Summary + +Phase 2 (realistischer Backtest: Bid/Ask-Fill, Spread, Slippage, Kosten, Intrabar, Gap) ist NICHT implementiert. Das heutige M12 nutzt ein **reines Single-Price-Bid-Modell** (nur Legacy-`ohlcv`-`close`, oder V2-`bid_close` als `close`). Es gibt keine Bid/Ask-Fill-Logik, kein separates Spread-Modell außer einem pauschalen Preispunkte-Abschlag auf Entry+Exit, keine Slippage außer derselben Punkte-Verschlechterung, und keinen Kosten-Breakdown. Der wichtigste Verzerrungs-Punkt: **Der Intrabar-Exit ist symmetrie-verzerrt** — Stop gewinnt immer bei Stop+Target-in-einer-Bar (konservativ, aber pessimistisch für Target-Trades). + +Die Erweiterung muss einen **ExecutionContext** einführen, der `execution_model`, `spread_model`, `slippage_model`, `cost_model`, `intrabar_policy`, `price_basis`, `feed_type` transportiert und versioniert — inklusive Run-Hash-Integration, aber **strikt getrennt** von Dataset-Identität und Strategie-Parameter. + +--- + +## A — PRICE MODEL / SINGLE-PRICE AUDIT + +### Candle-Datenmodell (M12) +- **Legacy** (`repository.py:_load_candles_legacy`, Zeile 305-327): `ohlcv`-Tabelle, Keys `timestamp, open, high, low, close, volume`. **Nur Einzelpreis** pro Bar (Mid/Einzelpreis). Kein bid/ask. +- **V2** (`repository.py:_load_candles_v2`, Zeile 332-371): liest aus `historical_bar` und mappt **`bid_open→open, bid_high→high, bid_low→low, bid_close→close`**. Das `historical_bar`-Schema HAT bid_open..ask_close (verifiziert: 37.480/37.480 bid+ask, mid=0), aber `load_candles` **transportiert sie NICHT** — wirft die ask-Seite weg und liefert nur bid-OHLC. +- Die Engine bekommt also immer OHLC (nur bid/Einzel) — **niemals ask**. + +### Entry / Exit / Stop / Target (engine.py) +| Feld | Quelle | Zeile | +|---|---|---| +| Entry (Signal) | `setup.entry` = **letzter Close der Signal-Candle** (Strategie: `entry = last_close`) | strategy 134/175 | +| Entry-Fill (Open Pos) | `next_candle["open"]` + Spread + Slippage (verschlechtert) | `_open_position` 192-214 | +| Stop | `setup.stop_loss` = Swing-Low/High ± buffer | 135, 176 | +| Target | `setup.target` = Entry ± 2R | 139, 180 | +| Exit (normales Ende) | `candle["close"]` (OHNE Slippage/Spread auf Stop/Target) | `_close_position` 283-336 | +| Market-Order-Fill | pauschaler Preispunkte-Abschlag LONG+ / SHORT- | 198-200 | +| Gap | Open-Fill zum Gap-Open | `_evaluate_exit` 268-280 | +| Run-Ende | Force-Close zum letzten `close` | 141-153 | + +### Aktuelle Annahmen (Single-Price) +| Annahme | Konsequenz | +|---|---| +| `open`/`close` sind handelbarer Preis (Einzel) | Kein Bid/Ask-Spread | +| LONG Entry = OPEN + spread + slippage | Wird besser (künstlicher Overhead) | +| LONG Exit = CLOSE (bei normalem Exit) | Kein Exit-Spread/Slippage — nur Entry hat Kosten | + +**RISIKO**: V2-Daten sind real `bid_ask`, aber `load_candles` reduziert auf bid — die ask-Seite wird VOR der Engine verworfen. Realisierung als Bid/Ask-Fill ist daher **nicht möglich ohne Datenmodell-Änderung**. + +**SOLL**: Engine muss `price_basis` + `bid_*`/`ask_*`/`mid_*` transportieren; Entry/Exit-Getrennt je nach Richtung; Stop/Target auf entsprechende Seite anwenden. + +--- + +## B — BID/ASK READINESS + +### Was liefert Historical V2 tatsächlich? +- `historical_bar` hat **`bid_open/high/low/close` UND `ask_open/high/low/close`** (verifiziert: 37.480/37.480/37.480, mid=0) — bid_ask vorhanden, mid NICHT. +- `load_bars_with_quality` (Zeile 163-247) transportiert bid+ask+mid+quality-Keys (vollständig). +- ABER: `load_candles` (der Pfad, den die Engine nutzt) liefert **nur bid-OHLC** — ask wird verworfen. + +### Kann M12 es heute transportieren? +**Nein, nicht über den Engine-Pfad.** Die Engine bekommt `open/high/low/close` als Einzelwerte. `build_dataset_context` bringt `price_basis="bid_ask"` (aus Bar-Metadaten), aber diese Info erreicht die Engine-Fills **nicht** (nur Run-Audit). + +### Zielmodell +``` +LONG: Entry grundsätzlich Ask (Käufer zahlt ask) + Exit grundsätzlich Bid (Verkäufer erhält bid) +SHORT: Entry grundsätzlich Bid (Verkäufer verkauft bid) + Exit grundsätzlich Ask (Käufer erhält ask) +``` +**Sonderfälle (zu dokumentieren, NICHT implementieren):** +- **Stop LONG**: Löse aus, wenn bid_low ≤ stop → fill = stop (oder gap-open bid, wenn gap unter) +- **Stop SHORT**: bid_high ≥ stop → fill = stop (bid/ask?) +- **Target LONG**: ask_high ≥ target → fill = target +- **Market**: Entry LONG = ask_open; Entry SHORT = bid_open +- **Limit**: (Phase 2 nicht eingeführt — kein Limit-Fill) +- **Gap**: bid/ask-gap-opening überschlägt Stop/Target +- **Bar Open**: Entry zum nächsten Bar-Open auf der korrekten Seite +- **End-of-data**: force-close zum letzten bid/ask-close + +--- + +## C — SPREAD AUDIT + +Heute (**engine.py** `_open_position` Zeile 198-201, `_close_position` Zeile 297-303): +- **Spread = ein fester Punktwert** (`params["spread"]`, Default 0.0, M13-Default 0.0). +- **Einheit: Preispunkte** (absolute Preisdifferenz, nicht %). +- Anwendung: **sowohl Entry als auch Exit**, je Richtung versetzt — d.h. ein **doppelter Spread pro Trade** (Entry+Exit) statt einmal. +- **Preisbasis**: unklar — es wird einfach von `open`/`close` abgezogen, ohne bid/ask-Bezug. Da Daten heute bid-single sind, ist es ein "Abschlag" auf den einzigen Preis. + +**Doppelzählungsrisiko**: Wenn Bid/Ask-Fill eingeführt wird, ist der aktuelle pauschale Spread-Abzug bereits implizit im echten bid/ask-Spread enthalten. Zusätzlich ein künstlicher `spread`-Abzug ⇒ **Doppelzählung**. + +**Zielregel**: +- `price_basis=bid_ask` (echte bid/ask) → Spread NICHT künstlich aufschlagen; nutze den intrinsischen bid/ask. +- `price_basis=mid`/single → synthetisches Spread-Modell möglich (aufzuschlagen). +- Beide Modi **strikt unterscheidbar** durch `spread_model` (z.B. `BID_ASK_INTRINSIC` vs `SYNTHETIC_FIXED` vs `SYNTHETIC_POINTS`). + +--- + +## D — SLIPPAGE AUDIT + +Heute (engine.py): +- **Wann**: nur Entry + normaler Exit (nicht Stop/Target, siehe unten). +- **Einheit**: Preispunkte (`params["slippage"]`, Default 0.0). +- **Long/Short**: symmetrisch — Long schlägt auf (Entry+Exit), Short zieht ab. Adversarial in beiden Fällen. +- **Deterministisch**: JA — ein fester Punkte-Wert. Kein Zufall, kein Seed. +- **Bestandteil run_hash**: JA (steht in `params_snapshot` → `_compute_run_hash` inkludiert `params`). Slippage-Änderung ändert run_hash. +- **Bestandteil Audit**: indirekt über `parameters` (in `metrics.audit` nicht explizit). + +**Zielmodell**: `slippage_model` (`DETERMINISTIC_FIXED` | `SYNTHETIC_POINTS` | `BROKER_REPLAY`...). Kein Zufall im deterministischen Kern; falls stochastisch, Seed fix + in Hash. Jeder Trade trennbar slippage_cost. + +--- + +## E — COST MODEL + +| Kosten | Status | Quelle | Heutige Modellierung | +|---|---|---|---| +| **Commission** | TEILWEISE | CONFIG (`fee_fixed`+`fee_pct`) | `_fee()` auf Entry+Exit | +| **Spread** | TEILWEISE | CONFIG (`spread`, Punkte) | pauschaler Preispunkte-Abschlag auf Entry+Exit | +| **Slippage** | TEILWEISE | CONFIG (`slippage`, Punkte) | pauschaler Preispunkte-Abschlag auf Entry+Exit | +| **Financing/Overnight/Swap** | NICHT | — | nicht modelliert | +| **FX conversion** | NICHT | — | nicht modelliert | +| **Guaranteed Stop Premium** | NICHT | — | nicht modelliert | +| **sonstige Gebühren** | NICHT | — | nicht modelliert | + +**Kosten-Quellen heute**: ausschließlich **CONFIG** (keine Broker-, keine Referenz-Kosten). Keine IG-Gebühren erfunden ✓. + +**Doppelzählungsrisiko (Kosten)**: `fee` UND `spread` UND `slippage` werden alle separat abgezogen — kein Broker/Referenz-Double-count, aber `spread` als pauschaler Abzug + künftiges bid/ask würde doppelt zählen (siehe C). + +--- + +## F — INTRABAR AMBIGUITY (engine.py `_evaluate_exit`, Zeile 251-281) + +**Aktuelles Verhalten — Code-Evidenz:** +- **LONG**: Stop wird VOR Target geprüft. Wenn `open<=stop`→Gap-Stop. Dann `if low<=stop`→Stop. Dann `if high>=target`→Target. + ⇒ **Wenn Stop UND Target in derselben Bar** → **Stop gewinnt IMMER** (Worst-Case konservativ). +- **SHORT**: spiegelbildlich (Stop zuerst). + +**Verzerrung**: M1-OHLC kann reale Intrabar-Reihenfolge nicht bestimmen (bekannt aus POC). Aktuelle Wahl ist deterministisch-pessimistisch (Stop-first), aber **kann Target-Gewinner in realistischen Sequenzen unterschätzen** (Stop-first bei bar mit beiden → Trades als Verluste klassifiziert, die real oft Target-Trades wären). + +**Zielmodi (Design, NICHT implementiert)**: +- `PESSIMISTIC` (= Stop-first, heutiges Verhalten) +- `OPTIMISTIC` (= Target-first) +- `STOP_FIRST` / `TARGET_FIRST` (explizite Varianten) +- `TICK_RESOLUTION` (benötigt Tick-Daten — nur V2 nicht lieferbar) +- `UNKNOWN/BLOCK` (fail-closed: keine Intrabar-Reihenfolge festgelegt → Run blocken) +- Optional `DEFAULT=STOP_FIRST` (behält heutiges Verhalten als Baseline; künftig wählbar) + +--- + +## G — GAP EXECUTION (engine.py, Zeile 268-280) + +- **LONG Stop-Gap**: wenn `open<=stop` → Fill zum `min(open, stop)` = Gap-Open (schlechter). Bewiesen: ein Gap-Down unter Stop füllt nicht perfekt bei stop, sondern Gap-Open. +- **LONG Target**: `high>=target`→ fill=target (kein Gap-Fall bei Target gesondert). wenn `open>=target` → wird als `high>=target` abgefangen und zum target gefüllt (das wäre Optimistisch — Gap UP über Target müsste evtl. Gap-Open, nicht target). +- **SHORT analog**. +- **RISIKO**: `target`-Fill bei Gap wird zum target (nicht gap-open) — unrealistisch optimistisch für Gap-UP-LONG (sollte bid/ask-open nach gap). Umgekehrt ist der Stop-Gap korrekt pessimistisch. +- **Zielverhalten**: Gap-Fill-Regel konsistent: bei Gap-Öffnung jenseits Stop/Target → zum **Gap-Open der realen ausführbaren Seite**, nie zum anvisierten Level. + +--- + +## H — REFERENCE VS BROKER FEED + +- **Heute**: M12 kennt keinen Feed-Unterschied. Engine ignoriert `feed_type`/`price_basis`. Ergebnis wird als generischer Backtest-Output geliefert (einzig Run-Audit enthält `feed_type`/`price_basis`, aber nur bei V2 + Context). +- **Zielregel**: REFERENCE-Daten nie als exakte Broker-Ausführung ausgeben. Run muss `execution_model` + `cost_model` + `spread_model` + `feed_type` + `price_basis` sichtbar kennzeichnen. +- **Execution-Klassen (Phase 2)**: + - `MARKET_SIMULATION` — nur OHLC, synthetische Modelle (Spread/Slippage/Cost als config) + - `BROKER_APPROXIMATION` — echte bid/ask OHLC (REFERENCE), realistische Spread/Fill + - `BROKER_REPLAY` — echte Broker-Feed + Broker-Fill (IG later), fehler bei REFERENCE +- `BROKER_REPLAY` auf REFERENCE-Daten → **fail-closed** (inconsist). + +--- + +## I — M13 OPTIMIZATION IMPACT + +M13 (optimizer.py) ruft M12 mit `spread`/`slippage`/`fee_fixed`/`fee_pct` als **feste Baustein-Argumente** (`_bt_kwargs`, Zeile 326-335, alle Default 0.0). **NICHT Teil des Optimizer-Suchraums** — `param_space` enthält nur Strategie-Parameter. + +**Gefahr**: Wenn `spread`/`slippage`/Fees in `param_space` aufgenommen würden, könnte der Optimizer sie MINIMIEREN (um Score zu maximieren) — realistische Kosten würden "schönoptimiert". Das ist **verboten**. + +**Klassifikation (Phase-2-Design)**: +| Klasse | Beispiele | M13 optimiert? | +|---|---|---| +| STRATEGY_PARAMETER | ema_period, rr_multiplier, stop_buffer | JA | +| EXECUTION_PARAMETER | intrabar_policy, gap_rule | NEIN | +| BROKER_PARAMETER | spread, slippage, commission, financing | NEIN | +| DATA_PARAMETER | data_source, feed, price_basis, timeframe | NEIN | +| POLICY_PARAMETER | eligibility_policy, quality_rule | NEIN | + +M13 darf **standardmäßig nur STRATEGY_PARAMETER** optimieren; alle anderen Klassen bleiben fix aus dem Backtest-Request (nicht optimierbar). + +--- + +## J — RUN HASH / REPRODUCIBILITY + +Phase-2-Parameter, die die Run-Identität beeinflussen müssen (Kandidaten): +- `execution_model_version` +- `spread_model_version` +- `slippage_model_version` +- `cost_model_version` +- `intrabar_policy_version` + +**Entscheidung** (Grundregel: Daten ≠ Ausführungsmodell ≠ Strategieparameter): +- **Dataset-Hash** (data_hash): rein die OHLC/Bar-Daten (bid/ask). Unverändert durch Execution-Modelle. +- **Run-Hash**: Dataset-Hash + Strategie+Param + **Execution/Broker/Cost-Modell-Versionen** (weil anderer Execution-Modus → anderes Ergebnis → anderes run_hash, sonst falscher Cache-Hit). +- **Audit**: alle expliziten Modelle/Versions + breakdown, aber NICHT in den Hash (nur repräsentativ). +- M13-Optimizer-Hash analog: Execution-Modell-Versionen zusätzlich. + +--- + +## K — RESULT METRICS + +Heute (`_compute_metrics`): total_trades, winrate, profit_factor, expectancy_r, avg_r, net_pnl, gross_profit, gross_loss, avg_holding_h, long_count, short_count, max_drawdown, by_regime. **Kein Kosten-Breakdown.** + +**Zielmetriken (Phase-2-Design)**: +- `gross_pnl` (ohne Kosten) +- `net_pnl` (nach allen Kosten) +- `spread_cost` / `slippage_cost` / `commission_cost` / `financing_cost` / `total_cost` +- `ambiguous_bars` (Bars mit Intrabar-Ambiguität) +- `gap_fills` +- `conditional_bars_used` (V2-Conditional) +- `reference_feed_warning` + +--- + +## L — FAIL-CLOSED CONDITIONS (Phase-2) + +Design-Liste, Situationen in denen Phase-2-M12 NICHT rechnen darf: +| Fall | Error-Code | +|---|---| +| Bid/Ask erforderlich aber fehlt | `BID_ASK_REQUIRED_MISSING` | +| unbekannte price_basis | `UNKNOWN_PRICE_BASIS` | +| unbekanntes execution_model | `UNKNOWN_EXECUTION_MODEL` | +| Fixture blockiert | `FIXTURE_DATA_BLOCKED` | +| Dataset nicht eligible | `DATASET_NOT_ELIGIBLE` / `UNKNOWN_CRITICAL_METADATA` | +| DatasetContext fehlt | `DATASET_CONTEXT_MISSING` | +| Kostenmodell verlangt Brokerdaten fehlen | `COST_MODEL_BROKER_DATA_MISSING` | +| BROKER_REPLAY + REFERENCE-Feed | `BROKER_REPLAY_REFERENCE_INCOMPATIBLE` | +| Tick-Modus verlangt Tickdaten, hat nur M1 | `TICK_DATA_REQUIRED_MISSING` | + +--- + +## M — MIGRATION / COMPATIBILITY + +| Modus | Daten | Execution | Spread | Slippage | Costs | Intrabar | Audit | Reproducibility | Zulässig | +|---|---|---|---|---|---|---|---|---|---| +| **LEGACY Single-Price** | ohlcv single | open/close, Stop-first | Config-punkte | Config-punkte | Config-fix/% | Stop-first | Legacy-Hash | run_hash | ✅ | +| **V2 REFERENCE BID/ASK** | historical bid/ask | bid/ask-Fill | intrinsic (bid/ask) | Config/Determ. | Config-fix/% | wählbar | full-Audit | run_hash + execution_version | ✅ (nach Implementierung) | +| **BROKER BID/ASK** | Broker bid/ask | broker-Fill | real broker | real broker | real broker | broker | full | Broker-Feed-Audit | (künftig) | +| **TICK** | tick-data | tick-seq | real | real | real | tick-resolved | full | Tick-Audit | (künftig) | + +Legacy bleibt **unverändert** (durch `price_basis`/`execution_model`-Gates). Kein stiller Fallback. + +--- + +## N — IMPLEMENTIERUNGSREIHENFOLGE (begründet) + +Code-Audit belegt, dass die Engine rein Single-Price ist. Empfohlene Reihenfolge (jede Phase testbar + rückbaubar): + +1. **ExecutionContext** (Datenmodell/Versionierung) — Voraussetzung, trennt alles. LOW +2. **Bid/Ask-Datenmodell** (load_candles/bars trägt bid/ask; Engine liest nach price_basis) — MEDIUM +3. **Bid/Ask Fill** (LONG buy ask/sell bid, SHORT bid/ask) — HIGH (Fill-Preis) +4. **Spread-Modell** (bid_ask intrinsic vs synthetic, keine Doppelzählung) — HIGH +5. **Slippage** (slippage_model, Entry/Exit-Einheit, Determinismus) — MEDIUM +6. **Cost-Breakdown** (Commission/Financing getrennt, nicht nur pauschal) — MEDIUM +7. **Gap-Fills** (konsistentes Gap-Open-Verhalten, Stop+Target) — HIGH +8. **Intrabar-Policy** (PESSIMISTIC/OPTIMISTIC/STOP_FIRST/TARGET_FIRST/TICK/UNKNOWN-BLOCK) — MEDIUM +9. **Result-Cost-Breakdown** (gross/net/cost-Metriken) — LOW +10. **M13-Param-Grenzen** (nur STRATEGY_PARAMETER optimizierbar, Execution-Kosten fix) — MEDIUM +11. **Reproducibility** (execution_model_version in run_hash, Audit-Vollständigkeit) — MEDIUM +12. **Regression/Legacy** (unverändert; Gate-Zusammenheit) — LOW +13. **Acceptance-Tests** (Plan unten) + +Die Reihenfolge weicht von der (User)-Suggestions ab: **ExecutionContext-Versionierung zuerst** ist zwingend, sonst werden Modelle ohne Versionierung eingebaut und Reproducibility bricht nachträglich. Bid/Ask-Datenmodell vor Fill zwingend. + +--- + +## RISIKO-MATRIX + +| Änderung | Modul | Datei/Fn | Risiko | Regression | Test | Rollback | +|---|---|---|---|---|---|---| +| ExecutionContext | M12 | service.py/engine.py | LOW | — | Hash-Vergleich | revert service+engine | +| Bid/Ask-Datenmodell | M12 | marketdata/client.py, repo | **MEDIUM** | Daten-Format | load-candles-Pfad | revert client/repo | +| Bid/Ask-Fill | M12 | engine.py | **HIGH** | Fill-Preis | LONG/SHORT Fill-Test | revert engine | +| Spread-Modell | M12 | engine.py | **HIGH** | Spread, PnL | No-Doppelzählung | revert | +| Slippage | M12 | engine.py | MEDIUM | slippage-Einheit | Entry/Exit-Slipp | revert | +| Commission/Costs | M12 | engine.py | MEDIUM | Cost-Breakdown | Fee-Test | revert | +| Gap-Fills | M12 | engine.py | **HIGH** | Stop/Target, PnL | Gap-Tests | revert | +| Intrabar-Policy | M12 | engine.py | MEDIUM | Stop/Target | Intrabar-Tests | revert | +| Result-Cost-Breakdown | M12 | engine.py | **HIGH** | PnL | Metrics | revert | +| M13-Param-Grenzen | M13 | optimizer.py | MEDIUM | Overfit | Optimizer-Test | revert | +| Reproducibility | M12/M13 | service.py/optimizer.py | MEDIUM | Run-Hash | Hash-Repro | revert | + +--- + +## ACCEPTANCE PLAN (vor Implementierung — Katalog) + +**Fill/Spread:** +- LONG Entry Ask, LONG Exit Bid (BID-ASK) +- SHORT Entry Bid, SHORT Exit Ask +- echte bid/ask-Spread NICHT doppelt (Bid/Ask-Modus) +- synthetischer Spread korrekt (Single-Price-Modus) + +**Slippage/Cost:** +- Slippage Long/Short adversarial +- Commission korrekt (fix+%) + +**Intrabar/Gap:** +- Stop-only, Target-only, Stop+Target gleiche Bar +- Gap über Stop, Gap über Target +- PESSIMISTIC/OPTIMISTIC unterscheidbar + +**Feed/Klassifizierung:** +- REFERENCE korrekt markiert +- BROKER_REPLAY + REFERENCE → fail-closed + +**Reproduzierbarkeit:** +- gleicher Input → identisches Ergebnis +- andere Execution-Version → anderer run_hash + +**Regression:** +- Legacy unverändert + +--- + +## OFFENE ENTSCHEIDUNGEN (für Christian) + +1. **Spread-Anwendung**: heutiger doppelter Entry+Exit-Spread → auf **einmaliger Roundtrip** oder je Seite ändern? Empfehlung: je Seite (realistisch). +2. **Intrabar-Default**: PESSIMISTIC (heutig, konservativ) als Standard bis Tick? Oder OPTIMISTIC? +3. **Slippage-Form**: deterministisch (Punkte) oder stochastisch (Seed)? Empfehlung: deterministisch default, stochastisch nur mit Seed+Hash. +4. **Cost-Modell-Version**: initial simple (fix+%, Spread/Punkte) → spätere Finanzierung/Swap/GS-Premium nachgeschaltet? +5. **Bid/Ask-Datenmodell**: sollen V2-bid/ask OHLC-Daten direkt in Engine (execution_model=APPROXIMATION) oder erst als REFERENCE-Feed (MARKET_SIMULATION) weitergegeben werden? Empfehlung: bid/ask zuerst nur in `load_bars_with_quality`, Engine-Pfad weiterhin single, bis Fill implementiert. +6. **M13**: Execution-Costs fix → ja (nicht optimizierbar) bestätigt. +7. **feed_type-Klassifizierung**: MARKT_SIMULATION vs BROKER_APPROXIMATION Bezeichnungen beibehalten? + +--- + +## EXAKTER NÄCHSTER IMPLEMENTIERUNGSSCHRITT + +Sobald Christian die offenen Entscheidungen freigibt, ist der **1. Schritt von Phase 10**: +1. **ExecutionContext-Dataclass** (execution_model, spread_model, slippage_model, cost_model, intrabar_policy, price_basis, feed_type + je *_version) — **ausser** DatasetContext, reine Ausführungs-Konfiguration. +2. Engine lädt als Einzelz: `execution_model` in `BacktestRequest`. +3. Tests: deterministisch, Hash-Integration, Legacy unverändert. + +**NOCH NICHT implementiert** (verboten bis Freigabe): Bid/Ask-Fill, Spread-Änderung, Slippage-Änderung, Kostenmodell-Änderung, Intrabar-Ausführungslogik, Jahresbackfill, IG-Calls, Orders, Docker-Netzwerkänderung.