Phase 8: M12 Historical-V2 DatasetGate + Run-Audit + Legacy-Smoke (DatasetGate, BLOCKED-Run-Audit, Run-Identität, EURUSD-V2-Blocktest, Legacy-Kompatibilität, Production Gate) - Geändert von Rain Ocampo, 22.08.2026

This commit is contained in:
Rain Ocampo 2026-08-22 22:18:26 +00:00
parent 6c479bcf75
commit d3d6b1ef76

View file

@ -0,0 +1,152 @@
# 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 AJ ✅ (test_shared_repository)
- Phase 5 20/20 ✅
- Phase 6 14/14 ✅
- Phase 7 AJ ✅
## 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.