""" Unit tests for TradingPortfolio. """ from decimal import Decimal from datetime import date from app.services.backtest.trading_portfolio import TradingPortfolio def test_initial_state(): tp = TradingPortfolio(Decimal("10000000")) assert tp.cash == Decimal("10000000") assert tp.investable_capital == Decimal("7000000") assert len(tp.positions) == 0 def test_enter_position(): tp = TradingPortfolio(Decimal("10000000")) txn = tp.enter_position( ticker="005930", price=Decimal("70000"), date=date(2024, 1, 2), commission_rate=Decimal("0.00015"), slippage_rate=Decimal("0.001"), ) assert txn is not None assert txn.action == "buy" assert "005930" in tp.positions pos = tp.positions["005930"] assert pos.entry_price == Decimal("70000") assert pos.stop_loss == Decimal("67900") # -3% assert pos.target1 == Decimal("73500") # +5% assert pos.target2 == Decimal("77000") # +10% def test_max_positions(): tp = TradingPortfolio(Decimal("10000000"), max_positions=2) tp.enter_position("A", Decimal("1000"), date(2024, 1, 2), Decimal("0"), Decimal("0")) tp.enter_position("B", Decimal("1000"), date(2024, 1, 2), Decimal("0"), Decimal("0")) txn = tp.enter_position("C", Decimal("1000"), date(2024, 1, 2), Decimal("0"), Decimal("0")) assert txn is None assert len(tp.positions) == 2 def test_stop_loss_exit(): tp = TradingPortfolio(Decimal("10000000")) tp.enter_position("005930", Decimal("70000"), date(2024, 1, 2), Decimal("0"), Decimal("0")) exits = tp.check_exits( date=date(2024, 1, 3), prices={"005930": Decimal("67000")}, commission_rate=Decimal("0"), slippage_rate=Decimal("0"), ) assert len(exits) == 1 assert exits[0].action == "sell" assert "005930" not in tp.positions def test_partial_take_profit(): tp = TradingPortfolio(Decimal("10000000")) tp.enter_position("005930", Decimal("70000"), date(2024, 1, 2), Decimal("0"), Decimal("0")) pos = tp.positions["005930"] initial_shares = pos.shares exits = tp.check_exits( date=date(2024, 1, 10), prices={"005930": Decimal("73500")}, commission_rate=Decimal("0"), slippage_rate=Decimal("0"), ) assert len(exits) == 1 assert exits[0].action == "partial_sell" assert exits[0].shares == initial_shares // 2 assert "005930" in tp.positions assert tp.positions["005930"].stop_loss == Decimal("70000") def test_full_take_profit(): tp = TradingPortfolio(Decimal("10000000")) tp.enter_position("005930", Decimal("70000"), date(2024, 1, 2), Decimal("0"), Decimal("0")) tp.check_exits( date=date(2024, 1, 10), prices={"005930": Decimal("73500")}, commission_rate=Decimal("0"), slippage_rate=Decimal("0"), ) exits = tp.check_exits( date=date(2024, 1, 15), prices={"005930": Decimal("77000")}, commission_rate=Decimal("0"), slippage_rate=Decimal("0"), ) assert len(exits) == 1 assert exits[0].action == "sell" assert "005930" not in tp.positions def test_trailing_stop_after_partial(): tp = TradingPortfolio(Decimal("10000000")) tp.enter_position("005930", Decimal("70000"), date(2024, 1, 2), Decimal("0"), Decimal("0")) tp.check_exits( date=date(2024, 1, 10), prices={"005930": Decimal("73500")}, commission_rate=Decimal("0"), slippage_rate=Decimal("0"), ) # After partial sell, stop should be at entry (70000) assert tp.positions["005930"].stop_loss == Decimal("70000") # Price rises to +8%, stop stays at entry tp.check_exits( date=date(2024, 1, 12), prices={"005930": Decimal("75600")}, commission_rate=Decimal("0"), slippage_rate=Decimal("0"), ) assert tp.positions["005930"].stop_loss == Decimal("70000") def test_cash_reserve(): tp = TradingPortfolio(Decimal("10000000"), cash_reserve_ratio=Decimal("0.3")) assert tp.investable_capital == Decimal("7000000") def test_get_portfolio_value(): tp = TradingPortfolio(Decimal("10000000")) tp.enter_position("005930", Decimal("70000"), date(2024, 1, 2), Decimal("0"), Decimal("0")) value = tp.get_value({"005930": Decimal("75000")}) assert value > Decimal("10000000") def test_duplicate_entry_rejected(): tp = TradingPortfolio(Decimal("10000000")) tp.enter_position("005930", Decimal("70000"), date(2024, 1, 2), Decimal("0"), Decimal("0")) txn = tp.enter_position("005930", Decimal("70000"), date(2024, 1, 3), Decimal("0"), Decimal("0")) assert txn is None