import pytest from engine.portfolio import PortfolioManager @pytest.fixture def pm(): return PortfolioManager(initial_capital=200.0) def test_initial_state(pm): assert pm.cash == 200.0 assert pm.positions == {} assert pm.trades == [] def test_buy(pm): pm.buy("BTCUSDT", price=40000.0, score=85) assert "BTCUSDT" in pm.positions assert pm.cash < 200.0 assert len(pm.trades) == 1 assert pm.trades[0]["side"] == "BUY" def test_buy_size_by_score(pm): pm.buy("SOLUSDT", price=140.0, score=75) assert abs(pm.trades[0]["amount_usd"] - 30.0) < 0.01 def test_buy_respects_max_positions(pm): for i, coin in enumerate(["A", "B", "C", "D", "E"]): pm.buy(f"{coin}USDT", price=10.0, score=80) pm.buy("FUSDT", price=10.0, score=80) assert len(pm.positions) == 5 def test_buy_respects_min_position(pm): pm.cash = 10.0 pm.buy("BTCUSDT", price=40000.0, score=85) assert "BTCUSDT" not in pm.positions def test_sell_full(pm): pm.buy("ETHUSDT", price=3500.0, score=80) invested = pm.positions["ETHUSDT"]["invested_usd"] pm.sell("ETHUSDT", price=3800.0, reason="signal") assert "ETHUSDT" not in pm.positions assert pm.cash > 200.0 - invested def test_stop_loss(pm): pm.buy("DOGEUSDT", price=0.10, score=80) pm.check_exit("DOGEUSDT", current_price=0.091) assert "DOGEUSDT" not in pm.positions def test_take_profit_partial(pm): pm.buy("SOLUSDT", price=100.0, score=80) qty_before = pm.positions["SOLUSDT"]["quantity"] pm.check_exit("SOLUSDT", current_price=116.0) assert pm.positions["SOLUSDT"]["quantity"] < qty_before def test_take_profit_full(pm): pm.buy("SOLUSDT", price=100.0, score=80) pm.check_exit("SOLUSDT", current_price=126.0) assert "SOLUSDT" not in pm.positions def test_pnl_calculation(pm): pm.buy("ETHUSDT", price=3500.0, score=80) pnl = pm.get_portfolio_value({"ETHUSDT": 3800.0}) assert pnl["total_pnl"] > 0 assert pnl["total_value"] > 200.0