"""Tests for the paper-trading service.""" from __future__ import annotations from datetime import date, datetime, timezone import pytest from app.exceptions import ValidationError from app.models.ohlcv import OHLCVRecord from app.models.ticker import Ticker from app.models.user import User from app.services import paper_trade_service as svc from tests.conftest import _test_session_factory # type: ignore @pytest.fixture async def session(): async with _test_session_factory() as s: yield s async def _seed(session, symbol: str, close: float) -> int: user = await session.get(User, 1) if user is None: session.add(User(id=1, username="u", password_hash="x", role="user", has_access=True)) await session.flush() t = Ticker(symbol=symbol) session.add(t) await session.flush() session.add(OHLCVRecord(ticker_id=t.id, date=date.today(), open=close, high=close, low=close, close=close, volume=1)) await session.commit() return t.id async def test_create_and_list_open(session): await _seed(session, "AAA", close=110.0) await svc.create_trade(session, 1, symbol="AAA", direction="long", entry_price=100.0, shares=10, stop_loss=95.0, target=120.0) rows = await svc.list_trades(session, 1) assert len(rows) == 1 row = rows[0] assert row["symbol"] == "AAA" assert row["status"] == "open" assert row["current_price"] == 110.0 # marked to the latest close async def test_close_uses_current_price(session): await _seed(session, "AAA", close=112.0) trade = await svc.create_trade(session, 1, symbol="AAA", direction="long", entry_price=100.0, shares=5, stop_loss=95.0, target=120.0) closed = await svc.close_trade(session, 1, trade.id) assert closed.status == "closed" assert closed.close_price == 112.0 assert closed.closed_at is not None rows = await svc.list_trades(session, 1, status="closed") assert rows[0]["current_price"] == 112.0 # closed → realized exit async def test_close_with_explicit_price(session): await _seed(session, "AAA", close=112.0) trade = await svc.create_trade(session, 1, symbol="AAA", direction="short", entry_price=100.0, shares=5, stop_loss=105.0, target=90.0) closed = await svc.close_trade(session, 1, trade.id, close_price=93.0) assert closed.close_price == 93.0 async def test_invalid_direction_rejected(session): await _seed(session, "AAA", close=100.0) with pytest.raises(ValidationError): await svc.create_trade(session, 1, symbol="AAA", direction="sideways", entry_price=100.0, shares=1, stop_loss=95.0, target=110.0) async def test_double_close_rejected(session): await _seed(session, "AAA", close=100.0) trade = await svc.create_trade(session, 1, symbol="AAA", direction="long", entry_price=100.0, shares=1, stop_loss=95.0, target=110.0) await svc.close_trade(session, 1, trade.id) with pytest.raises(ValidationError): await svc.close_trade(session, 1, trade.id)