trading-system-docs/notes/trading/system-docs/phase10d_commission_fees.md

115 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
type: Note
_organized: 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 510d + 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 AJ): 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: AJ 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_compile` OK → 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_gate` explizite Cost-Parameter.
- `m12_app/api/schemas.py` — Request-Felder.
## Technische Schulden / Hinweise
- **Deploy-Vorfall (dokumentiert):** `docker cp /dev/stdin` erzeugt 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.