Phase 10d: Commission/Fees cost_model_v1_simple - Deterministische Fees, Regression+Deploy+Smoke 2x gruen - Rain Ocampo, 23.08.2026
This commit is contained in:
parent
1cb7022fc9
commit
69aecf2bb3
1 changed files with 115 additions and 0 deletions
115
notes/trading/system-docs/phase10d_commission_fees.md
Normal file
115
notes/trading/system-docs/phase10d_commission_fees.md
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
---
|
||||||
|
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.
|
||||||
Loading…
Reference in a new issue