trading-system-docs/notes/trading/system-docs/historical-v2-phase8-m12-datasetgate.md
Red Queen adbb96d04a feat(tolaria): migrate final auto-safe knowledge batch 3 to schema v1
C3F FINAL AUTO_SAFE BATCH 3: migriert alle 21 verbleibenden AUTO_SAFE
Knowledge Objects auf knowledge_schema v1 (18 standalone logs + 3
standalone-canonical). Body byte-genau, IDs stabil (0 Kollisionen).
Keine HR/DO_NOT_TOUCH berührt. C3 MIGRATION weiter IN_PROGRESS.
2026-08-25 19:06:44 +00:00

7.9 KiB
Raw Permalink Blame History

id type role representation state knowledge_schema
object/ac1df780-019b-bb11-cb91-4edaa49e1843 arch history canonical historical 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 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.