Files
PythagorasGoal/tests/portfolio/test_win_net_of_fees.py
T
Adriano Dal Pastro ab4f706057 fix(live): win netto-fee + filtro TP edge-minimo sulle fade
Due correzioni emerse da close live con win=True ma pnl<0.

1) Metrica win lorda -> netta. _close_position contava is_win=trade_return>0
   (lordo), gonfiando l'accuracy: un take-profit colpito con mossa < fee RT
   risultava "win" pur perdendo. 51 close live: 39 win (76,5%) -> 13 falsi win
   -> accuracy netta reale 52,9%. Fix: is_win = net > 0. Capitale/PnL erano
   già corretti (netti). Contatori persistiti riconciliati a parte (MR01/DIP01
   BTC 7->1).

2) Filtro edge-minimo min_tp_frac. I 13 falsi win erano tutti MR01/DIP01 BTC in
   regime piatto: TP (la media) entro il costo round-trip -> perdenti garantiti.
   Aggiunto param min_tp_frac (default 0.0=off) a tutte e 4 le fade (MR01 banda,
   MR02 midpoint, MR07 ATR, DIP01): salta i segnali col TP entro la soglia.
   Non si "allarga" il TP (rischierebbe di perdere di piu'): si evita la trade.
   Cablato live a 0.0015 (1,5x fee) in _defs.py.

Validazione backtest BTC+ETH 1h: neutro su tutte le fade (0-1 trade rimossi,
pnl invariato o +leggero su DIP01). I micro-scalp sotto-fee non esistono nello
storico -> artefatto del regime attuale. Filtro puro-upside.

Test: test_win_net_of_fees.py, test_min_tp_frac.py (monotonia + gap > soglia +
default-off invariato). Suite: 50 passed.

NB deploy: il sorgente e' COPY nell'immagine, non montato -> serve
`docker compose up -d --build`, non un semplice restart (vale anche per il fix
SH01 horizon-exit, andato live solo con questo rebuild). Volume ./data persiste,
14 worker in RESUME puliti.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 11:07:30 +00:00

54 lines
1.9 KiB
Python

"""Fix metrica `win`: una trade è win solo se il PnL è positivo NETTO delle fee, non sul
lordo. Prima del fix `is_win = trade_return > 0` (lordo): un take-profit colpito con un
movimento più piccolo del round-trip fee veniva contato come win pur perdendo denaro,
gonfiando l'accuracy (es. MR01/DIP01 BTC in regime piatto: TP entro le fee)."""
import pandas as pd
from src.live.strategy_worker import StrategyWorker
from src.live.strategy_loader import load_strategy
def _worker(tmp, fee_rt=0.001):
w = StrategyWorker(strategy=load_strategy("MR01_bollinger_fade"), asset="BTC", tf="1h",
capital=1000.0, data_dir=tmp)
w._notify = lambda *a, **k: None
w.fee_rt = fee_rt
w.in_position = True
w.direction = 1
w.entry_price = 100.0
return w
def _last_close(w):
import json
rows = [json.loads(l) for l in w.trades_path.read_text().strip().splitlines()]
return [r for r in rows if r.get("event") == "CLOSE"][-1]
def test_tiny_favorable_move_is_loss_after_fees(tmp_path):
"""Mossa lorda +0,05% < fee RT 0,10%: prezzo salito, ma netto negativo -> NON è win."""
w = _worker(tmp_path, fee_rt=0.001)
w._close_position(100.05, "take_profit") # +0.05% lordo, sotto le fee
c = _last_close(w)
assert c["net_return"] < 0
assert c["win"] is False # prima del fix era True
assert w.total_wins == 0
def test_move_beyond_fees_is_win(tmp_path):
"""Mossa lorda +0,30% > fee 0,10%: netto positivo -> win."""
w = _worker(tmp_path, fee_rt=0.001)
w._close_position(100.30, "take_profit")
c = _last_close(w)
assert c["net_return"] > 0
assert c["win"] is True
assert w.total_wins == 1
def test_loss_is_not_win(tmp_path):
"""Prezzo sceso su long: perdita netta -> non win."""
w = _worker(tmp_path, fee_rt=0.001)
w._close_position(99.0, "stop_loss")
c = _last_close(w)
assert c["win"] is False
assert w.total_wins == 0