115 lines
5.5 KiB
Markdown
115 lines
5.5 KiB
Markdown
---
|
||
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 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_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.
|