--- id: object/ac1df780-019b-bb11-cb91-4edaa49e1843 type: arch role: history representation: canonical state: historical knowledge_schema: 1 --- # Phase 8 — M12 Historical-V2 DatasetGate + Run-Audit Autor: Rain Ocampo (Hermes) | Datum: 2026-08-22 | Status: IMPLEMENTIERT + GETESTET ## Ziel M12 akzeptiert Historical-V2-Datasets künftig NUR für Backtests, wenn der DatasetContext fachlich backtest-eligible ist. Jeder V2-Run macht seine komplette Datenwahrheit auditierbar. Legacy-Verhalten bleibt unverändert. ## 1. Baseline - M12 12/12 grün (vor Änderung, unverändert nach) - M12 Backup: `/opt/data/backup_phase8_20260822_214633` (service.py, client.py, storage.py, core/) - DATA SOURCE = UNSET (→ legacy) bestätigt - ALLOW_FIXTURE_DATA = UNSET (→ false/default) bestätigt - /health + /health/ready = 200 OK (im Container via urllib) ## 2. DatasetGate (nur historical_v2, fail-closed) `BacktestService._dataset_gate()` greift NUR bei `marketdata.source == "historical_v2"`. Legacy (source=legacy) ist vollständig unangetastet — kein Gate, kein neuer Fehler. Gate-Regeln (Punkt 2/3): - A) backtest_eligible=true → Run darf weiter - B) backtest_eligible=false → fail-closed blockiert - C) UNKNOWN_CRITICAL_METADATA → NICHT starten - D) Fixture + ALLOW_FIXTURE_DATA=false → FIXTURE_DATA_BLOCKED - E) fehlender DatasetContext → fail-closed (DATASET_CONTEXT_MISSING) - F) fehlende Dataset-Version → fail-closed (DATASET_VERSION_NOT_FOUND) - G) kein stiller Fallback auf Legacy ## 3. Block-Codes | error_code | Bedingung | |---|---| | DATASET_NOT_ELIGIBLE | backtest_eligible=false, reason != UNKNOWN_CRITICAL_METADATA | | UNKNOWN_CRITICAL_METADATA | backtest_eligible=false + reason=UNKNOWN_CRITICAL_METADATA | | FIXTURE_DATA_BLOCKED | is_fixture=true + ALLOW_FIXTURE_DATA=false | | DATASET_CONTEXT_MISSING | build_dataset_context wirft / Context None | | DATASET_VERSION_NOT_FOUND | dataset_version_id & hash beide None | Ein geblockter Run wird als **auditierbarer BLOCKED-Run persistiert** (status=BLOCKED, metrics.error_code, metrics.block_reason, metrics.policy_version, metrics.timestamp, metrics.audit). KEINE Strategieausführung. ## 4. Run-Audit (Punkt 4) Vollständiger Audit-Datensatz pro V2-Run (in `metrics.audit` + Antwort `audit`): dataset_context_version, data_source, dataset_id, dataset_version_id, dataset_version_hash, raw_source_hash, instrument, timeframe, start/end, provider, feed_type, price_basis, data_as_of, data_hash, normalization_version, quality_model_version, stale_model_version, session_model_version, calendar_version, eligibility_model_version, aggregation_version, eligibility_policy_version, is_fixture, quality_summary, effective_eligibility_summary, backtest_eligible, block_reason, strategy, strategy_version, parameters, run_hash. Keine DB-Migration nötig — sauber im bestehenden `backtest_run.metrics` (JSONB). ## 5. Run-Identität / Reproduzierbarkeit (Punkt 5) M12 hatte bereits deterministischen `run_hash` (strategy/version/params/symbol/ timeframe/start/end/data_hash). Für historical_v2 wird ZUSÄTZLICH `dataset_version_hash` + `eligibility_policy_version` in den Hash aufgenommen (nur wenn gesetzt). - gleiche Daten + Strategie + Parameter + Policy → gleiche Identität - andere Dataset-Version → andere Identität - andere Policy-Version → andere Identität - Legacy (beide None) → exakt alter Hash, unverändert ## 6. Echter EURUSD-V2-Blocktest (POSITIV, Punkt 6) Gegen historical-DB (Port 5433): DatasetContext liefert aktuell `backtest_eligible=False`, `block_reason=NO_DATASET_METADATA`, `dataset_version_id=None` (keine Dataset-Version für EURUSD/H4 2024-01). Gate blockt mit `DATASET_VERSION_NOT_FOUND` (strenger als UNKNOWN). Keine Strategieausführung, Run als BLOCKED auditierbar. -> PASS (fail-closed, keine Echtorders) Anmerkung: Der im Brief erwartete Code `UNKNOWN_CRITICAL_METADATA` trifft hier nicht zu, weil keine Dataset-Version existiert. Der UNKNOWN-Code ist separat über Mock-Test abgedeckt. Beides gültige fail-closed Blöcke. ## 7. Positiver ELIGIBLE-Test (Punkt 7) Mock/Test-DatasetContext vollständig & backtest_eligible=true, mit ALLOW_FIXTURE_DATA=true → Gate lässt durch, M12 läuft bis zur bestehenden Backtestlogik, Audit vollständig. (Test 3, 7 grün.) ## 8. QualitySummary im Run (Punkt 8) total/eligible/conditional/excluded/unknown/suspect/partial/stale_bars, gap_count, coverage_pct — über `quality_summary` im Audit. None bleibt None (nicht 0/100% erfunden), bewiesen durch Test. ## 9. CONDITIONAL Data Policy (Punkt 9) Keine finale Handelslogik. Audit kennzeichnet CONDITIONAL sichtbar (`effective_eligibility_summary.conditional`). Kein stilles Zählen als FULL. ## 10. Feed Type (Punkt 10) Historical V2 Dukascopy: feed_type=REFERENCE — im Audit sichtbar. Kein Eindruck REFERENCE == IG Broker Feed, keine Kosten-Simulation. ## 11. Legacy-Kompatibilität - Legacy (source=legacy) → kein Gate, kein Audit, kein neuer Fehler - M12 12/12 unverändert grün - Legacy run_hash deterministisch unverändert (Phase-7 J, Phase-6 12) - kein Hash-/Cache-Break - Legacy bleibt Default ## 12. Tests (Punkt 12) Neue Suite `m12_app/tests/test_phase8_gate.py` (13 Tests) — alle grün: 1 Legacy unverändert | 2 V2 Context vollständig | 3 eligible erlaubt 4 not_eligible blockiert | 5 UNKNOWN blockiert | 6 Fixture default blockiert 7 Fixture explizit erlaubt | 8 fehlender Context fail-closed 9 fehlende Version fail-closed | 10 andere dataset_hash → andere ID 11 andere policy_version → andere ID | 12 gleiche Inputs → gleiche ID 13 QualitySummary vollständig | 14 CONDITIONAL sichtbar | 15 REFERENCE sichtbar Regression: - M12 12/12 ✅ - M13 A–J ✅ (test_shared_repository) - Phase 5 20/20 ✅ - Phase 6 14/14 ✅ - Phase 7 A–J ✅ ## 13. Produktionsdeploy NUR M12. HISTORICAL_DATA_SOURCE bleibt legacy/unset, ALLOW_FIXTURE_DATA bleibt false/unset. Keine Netzwerkänderung. (Deploy-Pfad: scp → docker cp → chown) ## 14. Dokumentation Diese Datei. Push zu Forgejo/Tolaria in Phase 8 formalisiert (nur diese Datei, keine Secrets); Obsidian NUR lokal, KEIN Push. ## 15. Legacy-Smoke-Ergebnis (POST /backtest, BT_PULL 1h demo, 2026-08-01→19) Ausgeführt im Produktions-Container Modul-12-Backtesting (urllib, Port 55012): - **Run A**: status=COMPLETED, run_id=ed62f1b3628b4fa29c3e880d83002ed8 - **Run B**: status=COMPLETED, run_id=73a8cf75034a439cba60e974bd07304b - **run_hash**: `bc6e2553…` A == B identisch ✅ - **data_hash**: `d76a7549…` A == B identisch ✅ - **error_code**: None / None ✅ (kein Gate-Code) - **block_reason**: None / None ✅ - **audit vorhanden (Legacy)**: False (korrekt minimal, kein Audit-Feld nötig) - KEIN DATASET_NOT_ELIGIBLE / DATASET_VERSION_NOT_FOUND / FIXTURE_DATA_BLOCKED - **Produktions-DB (trading.backtest_run)**: status=COMPLETED, run_hash bc6e2553… - **Health/Ready nach Smoke**: /health 200, /health/ready 200 - **Production Gate nach Smoke**: HISTORICAL_DATA_SOURCE=UNSET, ALLOW_FIXTURE_DATA=UNSET **Legacy-Hash-Nachweis (vorher=nachher):** - Pre-Phase-8-Hash (Backup) payload = {strategy,version,params,symbol,timeframe,start,end,data_hash} - Phase 8 fügt dataset_version_hash/eligibility_policy_version NUR hinzu wenn `is not None` → Legacy ruft mit None auf → **byte-identischer payload → identischer Hash.** - Empirisch 2 unabhängige Runs nach Deploy → gleicher run_hash bc6e2553… - ⇒ Legacy run_hash deterministisch UNVERÄNDERT gegenüber vor Phase 8. ✅ **Safety:** keine M09-Order, keine M07/M08/M15-Control-Auswirkung, keine Netzwerkänderung, keine IG-Calls. Nur M12-Container-Neustart (Prozess, kein Docker-Netz/Config). ## Bekannte offene Punkte - EURUSD-Context liefert NO_DATASET_METADATA (keine Dataset-Version hinterlegt). - CONDITIONAL-Handelslogik (Punkt 9) NICHT implementiert (nur Kennzeichnung) — bewusst. - Noch keine Broker-Kosten-/Feed-Angleichung — bewusst. - `verify_phase8_block.py` nutzt FakeStorage für den Gate-Pfad; echtes DB-Persistieren der BLOCKED-Runs wird im Container (Deploy) verifiziert.