354 lines
20 KiB
Markdown
354 lines
20 KiB
Markdown
---
|
||
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)
|
||
|
||
> **STAND 22.08.2026: ALLE 7 ENTSCHEIDUNGEN FESTGELEGT** (Christian, 22.08.2026).
|
||
|
||
1. **Spread → pro Fill/Seite modellieren, KEIN pauschaler Roundtrip-Aufschlag.**
|
||
Bei echten Bid/Ask-Daten entsteht der Spread über Entry-/Exit-Seite. Keine Doppelzählung.
|
||
2. **Intrabar-Default = PESSIMISTIC.** Wenn Stop und Target in derselben Bar liegen und keine
|
||
Tick-Reihenfolge vorhanden ist: konservativ ungünstigeren Ausgang verwenden. Später optional TICK_RESOLUTION.
|
||
3. **Slippage = deterministisch.** Kein Zufall in V1. Später optional stochastisch nur mit festem Seed + eigener Modellversion.
|
||
4. **Cost Model = `cost_model_v1_simple`.** Abdeckung: Spread, Slippage, Commission.
|
||
NOCH KEINE Overnight-/Swap-/Financing-Komplexität in V1 (Financing später eigene Modellversion).
|
||
5. **Bid/Ask = durch den vollständigen M12-Engine-/Execution-Pfad propagieren.**
|
||
Nicht nur im Repository/load_bars. Keine Reduktion zurück auf Single Price im V2-Pfad.
|
||
6. **M13: Execution-/Broker-/Cost-Parameter sind FIX und NICHT optimierbar.** M13 optimiert
|
||
standardmäßig nur echte STRATEGY_PARAMETER. Spread/Slippage/Commission/Brokerparameter dürfen nicht schönoptimiert werden.
|
||
7. **Execution Models (verwenden):**
|
||
- `LEGACY_SINGLE_PRICE`
|
||
- `REFERENCE_BID_ASK`
|
||
- `BROKER_APPROXIMATION`
|
||
- später `BROKER_REPLAY`
|
||
- optional später `TICK_REPLAY`
|
||
Dukascopy Historical V2 = `REFERENCE_BID_ASK`. Nicht als IG-Brokerfeed behandeln.
|
||
|
||
---
|
||
|
||
## 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.
|