"""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