"""Tests for PositionManager.""" import pytest from unittest.mock import Mock from nautilus_dolphin.nautilus.position_manager import PositionManager from nautilus_trader.model.enums import PositionSide class TestPositionManager: def test_on_position_opened_calculates_tp(self): """Test TP price calculation on position open.""" strategy = Mock() pm = PositionManager(strategy, tp_bps=99, max_hold_bars=120) position = Mock() position.id = "POS-001" position.instrument_id = "BTCUSDT.BINANCE" position.avg_px_open = 50000.0 position.side = PositionSide.SHORT pm.on_position_opened(position) assert "BTCUSDT.BINANCE" in pm.positions metadata = pm.positions["BTCUSDT.BINANCE"] # TP should be entry * (1 - 99/10000) = 50000 * 0.9901 expected_tp = 50000.0 * 0.9901 assert abs(metadata['tp_price'] - expected_tp) < 0.01 def test_on_bar_increments_bars_held(self): """Test bars_held counter increments.""" strategy = Mock() strategy.cache = Mock() pm = PositionManager(strategy) position = Mock() position.id = "POS-001" position.instrument_id = "BTCUSDT.BINANCE" position.avg_px_open = 50000.0 position.side = PositionSide.SHORT position.is_closed = False pm.on_position_opened(position) strategy.cache.position.return_value = position bar = Mock() bar.instrument_id = "BTCUSDT.BINANCE" bar.close = 50000.0 pm.on_bar(bar) assert pm.positions["BTCUSDT.BINANCE"]['bars_held'] == 1 pm.on_bar(bar) assert pm.positions["BTCUSDT.BINANCE"]['bars_held'] == 2 def test_tp_exit_triggered(self): """Test TP exit when price hits target.""" strategy = Mock() strategy.cache = Mock() strategy.order_factory = Mock() pm = PositionManager(strategy, tp_bps=99) position = Mock() position.id = "POS-001" position.instrument_id = "BTCUSDT.BINANCE" position.avg_px_open = 50000.0 position.side = PositionSide.SHORT position.is_closed = False position.quantity = 1.0 pm.on_position_opened(position) strategy.cache.position.return_value = position bar = Mock() bar.instrument_id = "BTCUSDT.BINANCE" bar.close = 49505.0 # Below TP pm.on_bar(bar) # Should have called order_factory.market strategy.order_factory.market.assert_called_once() def test_max_hold_exit_triggered(self): """Test max hold exit after 120 bars.""" strategy = Mock() strategy.cache = Mock() strategy.order_factory = Mock() pm = PositionManager(strategy, max_hold_bars=3) position = Mock() position.id = "POS-001" position.instrument_id = "BTCUSDT.BINANCE" position.avg_px_open = 50000.0 position.side = PositionSide.SHORT position.is_closed = False position.quantity = 1.0 pm.on_position_opened(position) strategy.cache.position.return_value = position bar = Mock() bar.instrument_id = "BTCUSDT.BINANCE" bar.close = 50100.0 # Above TP # Trigger max hold for _ in range(3): pm.on_bar(bar) # Should have called order_factory.market strategy.order_factory.market.assert_called_once()