15 knowledge objects (log, note, index, module, history) migrated to C3 knowledge schema v1. Bodies unchanged, metadata preserved.
20 KiB
| id | type | role | representation | state | knowledge_schema | tags | created | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| object/ca9b3551-53f8-408b-8697-9a21fa3a24d8 | arch | history | canonical | historical | 1 |
|
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, Keystimestamp, open, high, low, close, volume. Nur Einzelpreis pro Bar (Mid/Einzelpreis). Kein bid/ask. - V2 (
repository.py:_load_candles_v2, Zeile 332-371): liest aushistorical_barund mapptbid_open→open, bid_high→high, bid_low→low, bid_close→close. Dashistorical_bar-Schema HAT bid_open..ask_close (verifiziert: 37.480/37.480 bid+ask, mid=0), aberload_candlestransportiert 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_barhatbid_open/high/low/closeUNDask_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/closeabgezogen, 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_INTRINSICvsSYNTHETIC_FIXEDvsSYNTHETIC_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_hashinkludiertparams). Slippage-Änderung ändert run_hash. - Bestandteil Audit: indirekt über
parameters(inmetrics.auditnicht 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. Dannif low<=stop→Stop. Dannif 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 zummin(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). wennopen>=target→ wird alshigh>=targetabgefangen 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ältfeed_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_basissichtbar 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/FillBROKER_REPLAY— echte Broker-Feed + Broker-Fill (IG later), fehler bei REFERENCE
BROKER_REPLAYauf 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_versionspread_model_versionslippage_model_versioncost_model_versionintrabar_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_costambiguous_bars(Bars mit Intrabar-Ambiguität)gap_fillsconditional_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):
- ExecutionContext (Datenmodell/Versionierung) — Voraussetzung, trennt alles. LOW
- Bid/Ask-Datenmodell (load_candles/bars trägt bid/ask; Engine liest nach price_basis) — MEDIUM
- Bid/Ask Fill (LONG buy ask/sell bid, SHORT bid/ask) — HIGH (Fill-Preis)
- Spread-Modell (bid_ask intrinsic vs synthetic, keine Doppelzählung) — HIGH
- Slippage (slippage_model, Entry/Exit-Einheit, Determinismus) — MEDIUM
- Cost-Breakdown (Commission/Financing getrennt, nicht nur pauschal) — MEDIUM
- Gap-Fills (konsistentes Gap-Open-Verhalten, Stop+Target) — HIGH
- Intrabar-Policy (PESSIMISTIC/OPTIMISTIC/STOP_FIRST/TARGET_FIRST/TICK/UNKNOWN-BLOCK) — MEDIUM
- Result-Cost-Breakdown (gross/net/cost-Metriken) — LOW
- M13-Param-Grenzen (nur STRATEGY_PARAMETER optimizierbar, Execution-Kosten fix) — MEDIUM
- Reproducibility (execution_model_version in run_hash, Audit-Vollständigkeit) — MEDIUM
- Regression/Legacy (unverändert; Gate-Zusammenheit) — LOW
- 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).
- Spread → pro Fill/Seite modellieren, KEIN pauschaler Roundtrip-Aufschlag. Bei echten Bid/Ask-Daten entsteht der Spread über Entry-/Exit-Seite. Keine Doppelzählung.
- 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.
- Slippage = deterministisch. Kein Zufall in V1. Später optional stochastisch nur mit festem Seed + eigener Modellversion.
- Cost Model =
cost_model_v1_simple. Abdeckung: Spread, Slippage, Commission. NOCH KEINE Overnight-/Swap-/Financing-Komplexität in V1 (Financing später eigene Modellversion). - 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.
- 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.
- Execution Models (verwenden):
LEGACY_SINGLE_PRICEREFERENCE_BID_ASKBROKER_APPROXIMATION- später
BROKER_REPLAY - optional später
TICK_REPLAYDukascopy 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:
- ExecutionContext-Dataclass (execution_model, spread_model, slippage_model, cost_model, intrabar_policy, price_basis, feed_type + je *_version) — ausser DatasetContext, reine Ausführungs-Konfiguration.
- Engine lädt als Einzelz:
execution_modelinBacktestRequest. - 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.