galaxis-po/backend/tests/unit/test_trading_portfolio.py
zephyrdark 0aac70886f feat: add TradingPortfolio for signal-based position management
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 15:08:36 +09:00

136 lines
4.6 KiB
Python

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