5.5 KiB
| type | _organized |
|---|---|
| Note | true |
Phase 10d — Commission / Fees (cost_model_v1_simple)
Geändert von: Rain Ocampo (Hermes) Datum: 23.08.2026 Grund: Deterministisches, reproduzierbares und auditierbares Commission-/Fee-Modell für Historical-V2-Backtests (M12), strikt nach Safe-Change-Management.
Zusammenfassung
Phase 10d führt ein deterministisches Cost-Modell cost_model_v1_simple in den Historical-V2-Backtest ein. Es unterstützt zwei kombinierbare Gebührenarten pro Fill/Seite:
- FIXED PER SIDE (
commission_fixed_per_side) — pauschal je Entry- und Exit-Fill. - NOTIONAL RATE PER SIDE (
commission_rate) — prozentual auf den tatsächlichen Fill-Notional (abs(fill_price * quantity) * rate).
CostModel.NONE lässt Legacy-Fee-Felder (fee_fixed/fee_pct) exakt unverändert. CostModel.SIMPLE_V1 ersetzt im V2-Pfad die betreffende Fee-Berechnung und wird nicht zusätzlich zur Legacy-Fee berechnet (keine Doppelzählung). Spread (10b) und Slippage (10c) bleiben getrennte Ebenen und werden nicht als Commission erneut berechnet.
Fee-Reihenfolge (verbindlich)
MARKET PRICE
→ BID/ASK SIDE (LONG Entry=ASK / Exit=BID; SHORT Entry=BID / Exit=ASK)
→ SLIPPAGE (adversarial, DETERMINISTIC_FIXED)
→ FILL PRICE
→ COMMISSION/FEE (auf Fill, pro Seite)
→ NET PNL
Die Rate-Commission verwendet den tatsächlichen Fill NACH Slippage — nicht den Pre-Slippage-Basispreis.
Fee-Formel
fee_per_side = fixed_per_side + abs(fill_price * quantity) * rate
- LONG/SHORT-symmetrisch — Fees sind immer Kosten, keine Vorzeichenlogik.
- Keine FX-/Währungs-Konvertierung — Fees in derselben PnL-/Quote-Währung.
- Entry- und Exit-Fee exakt einmal pro Trade; kein Exit → keine Exit-Fee.
- Target, Stop und Force-Close verwenden jeweils den tatsächlichen Exit-Fill.
Gross / Net
gross_pnl − (entry_fee + exit_fee) = net_pnl
Trade-Audit trägt: gross_pnl, entry_fee, exit_fee, total_fees, net_pnl.
NONE vs SIMPLE(0)
CostModel.NONE und cost_model_v1_simple(fixed=0, rate=0) erzeugen gleiche Fillpreise, gleiches gross_pnl, gleiches net_pnl, aber unterschiedlichen ExecutionContext und V2-run_hash — gewollt.
Fail-Closed
Ungültige Werte blockieren: fixed < 0, rate < 0, NaN, Inf, nicht-numerisch, unbekanntes cost_model. Kein stilles Clamp, kein Fallback auf NONE.
run_hash
Für historical_v2 fließen in den reproduzierbaren Run-Kontext ein: cost_model, cost_model_version, commission_fixed_per_side, commission_rate. Legacy-Hash (fee_fixed/fee_pct-Pfad) bleibt exakt unverändert.
Proof: deterministische Beispiele
| Test | Erwartung | Ergebnis |
|---|---|---|
| A: FIXED LONG (Fill 100→110, qty 1, fixed 1) | gross +10, fees 2, net +8 | grün |
| B: FIXED SHORT (Fill 110→100, qty 1, fixed 1) | gross +10, fees 2, net +8 | grün |
| C: RATE LONG (Fill 100→110, qty 2, 1%) | entry 2.00, exit 2.20, gross 20, fee 4.20, net 15.80 | grün |
| D: RATE SHORT (Fill 110→100, qty 2, 1%) | entry 2.20, exit 2.00, gross 20, fee 4.20, net 15.80 | grün |
| E: FIXED + RATE | exakt additiv | grün |
Testabdeckung
test_phase10d_cost_model.py: 38 Tests — NONE, SIMPLE zero, fixed/rate LONG+SHORT, fixed+rate, quantity scaling, fill-price basis, slippage-before-fee, kein double spread/slippage, target/stop/force-close fee, gross/net, audit, run_hash-deterministic/-changes, fail-closed (negativ/NaN/Inf/unbekannt), fixture blocked/allowed, Legacy unchanged.
Regression (Phase 5–10d + M12/M13, 2× grün)
- pytest M12-Gruppe: 129 passed × 2
- Phase 5 (Eligibility): 20/20 ×2 · Phase 6 (DatasetContext): 14/14 ×2
- Phase 7 (M13 run_hash A–J): grün ×2 (Legacy-Hash unverändert)
- M13 compat: 5/5 ×2 (inkl.
optimizer_no_exec_params= M13 optimiert KEINE Cost-Parameter) - M13 shared_repository: A–J grün ×2
Deploy
- READ-ONLY Importkette geprüft; Backup + sha256 VORHER verifiziert.
- Minimaler Dateisatz (5 Dateien):
execution_context.py,pricing.py,engine.py,service.py,api/schemas.py. docker cp(echter Pfad) →chown 1001:1001(als root) →py_compileOK → Import-Smoke OK → Restart → Health 200.- Production Gates:
HISTORICAL_DATA_SOURCE=UNSET,ALLOW_FIXTURE_DATA=UNSET.
Legacy-Production-Smoke (2×, byte-identisch)
- run_hash
bc6e25533d22ee102b6921eeeccd8f7b7dff4ec74a3316e381007ec56b0e1b37 - data_hash
d76a75496453d9c05d5690237f4621534f954f9f3e87b67eee4ae9327c5c6bb9 - net_pnl 196.586062, total_trades 1 — 2× identisch → Legacy unverändert.
Geänderte Dateien (Phase 10d)
shared/historical/execution_context.py— Cost-Felder + Fail-Closed +build_execution_context-Parameter.m12_app/core/pricing.py—commission_for_fill(FIXED + NOTIONAL, pro Seite).m12_app/core/engine.py—_position_fee, Entry/Exit-Fee, Trade-Audit-Felder.m12_app/core/service.py— run_hash-Felder,_dataset_gateexplizite Cost-Parameter.m12_app/api/schemas.py— Request-Felder.
Technische Schulden / Hinweise
- Deploy-Vorfall (dokumentiert):
docker cp /dev/stdinerzeugt defekte Symlinks (/proc/self/fd/0). Sauberer Weg: scp → Staging-Pfad →docker cp <echter-pfad>→docker exec -u root chown. Der Vorfall ist vollständig behoben, kein Daten-/PnL-Befund, Produktion valid. - Keine FX-Konvertierung; Overnight/Swap/Finanzierung/Borrow sind NICHT Teil von 10d.
Nächster Schritt
STOPP nach Phase 10d. Keine automatische Fortsetzung (Finanzierung/Overnight/Swap/Borrow/Phase 10e/Jahresbackfill/Live-Broker-Costs). Auf neue Freigabe warten.