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:
parent
6c479bcf75
commit
d3d6b1ef76
1 changed files with 152 additions and 0 deletions
|
|
@ -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 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.
|
||||
Loading…
Reference in a new issue