Files
auto-stock/backend/tests/test_portfolio_cache_bridge.py
shuim fd8a0f1188 fix: 데이트레이딩 보유 종목이 화면에 안 보이던 문제 — 엔진 포지션 직접 표시
Portfolio 테이블은 stock_code가 unique라 같은 종목을 스윙+데이트레이딩으로 동시 보유하면
한 행에 합쳐지고 strategy_type도 하나만 남는다(LG씨엔에스 스윙 8 + DT 4 = 12주 'swing').
- 프론트: /daytrading/positions(엔진 인메모리)를 직접 조회해 '데이트레이딩' 그룹으로 표시하고,
  스윙 행에서는 그 수량을 차감
- 백엔드 동기화: 전량이 DT 포지션일 때만 'daytrading' 태깅 (부분 혼재 시 스윙 청산 대상 유지)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-23 09:33:14 +09:00

67 lines
2.9 KiB
Python

"""portfolio_cache_bridge: 매매 엔진 체결 → 포트폴리오 캐시/Portfolio 즉시 반영."""
import asyncio
from decimal import Decimal
from unittest.mock import AsyncMock, patch
import pytest
from app.api.endpoints.portfolio import portfolio_cache
from app.models.models import Portfolio
from app.services import portfolio_cache_bridge as bridge
@pytest.fixture(autouse=True)
def _reset_cache():
portfolio_cache.holdings = []
portfolio_cache.summary = {"available_cash": 1_000_000, "total_value": 0, "total_investment": 0,
"total_assets": 1_000_000, "total_profit_loss": 0, "total_profit_loss_rate": 0, "total_stocks": 0}
portfolio_cache.recent_sells = {}
yield
@pytest.mark.asyncio
async def test_reflect_buy_then_sell_updates_cache_and_queues_db():
q = AsyncMock()
with patch("app.services.db_worker.enqueue_portfolio_upsert", q):
await bridge.reflect_buy("005930", "삼성전자", 3, 70000.0, strategy_type="daytrading")
h = portfolio_cache.holdings[0]
assert (h.stock_code, h.quantity, h.strategy_type) == ("005930", 3, "daytrading")
assert portfolio_cache.summary["available_cash"] == 1_000_000 - 210_000
assert q.await_args.args[0]["strategy_type"] == "daytrading"
# 부분 매도 → 잔량 반영, 전량 매도 → 목록에서 제거 + 최근매도 기록(동기화 되살림 방지)
with patch("app.services.db_worker.enqueue_portfolio_upsert", q):
await bridge.reflect_sell("005930", 1, 71000.0, remaining_qty=2)
assert portfolio_cache.holdings[0].quantity == 2
await bridge.reflect_sell("005930", 2, 71000.0, remaining_qty=0)
assert portfolio_cache.holdings == []
assert q.await_args.args[0]["quantity"] == 0
assert portfolio_cache.recently_sold_qty("005930") == 2
@pytest.mark.asyncio
async def test_reflect_sell_unknown_holding_does_not_zero_db():
"""캐시에 없고 잔량도 모르면 DB 수량을 0으로 덮어쓰지 않는다."""
q = AsyncMock()
with patch("app.services.db_worker.enqueue_portfolio_upsert", q):
await bridge.reflect_sell("000660", 5, 100000.0)
q.assert_not_awaited()
@pytest.mark.asyncio
async def test_dt_and_swing_same_stock_quantities_are_separable():
"""같은 종목을 스윙+DT로 보유해도 화면상 수량이 분리 가능해야 한다.
Portfolio는 종목당 1행(stock_code unique)이므로 합계만 남는다 →
프론트는 엔진 포지션 수량을 빼서 스윙 수량을 계산한다. 여기서는 그 계산식을 검증.
"""
q = AsyncMock()
with patch("app.services.db_worker.enqueue_portfolio_upsert", q):
await bridge.reflect_buy("064400", "LG씨엔에스", 8, 70148.0, strategy_type="swing")
await bridge.reflect_buy("064400", "LG씨엔에스", 4, 70800.0, strategy_type="daytrading")
row = portfolio_cache.holdings[0]
assert row.quantity == 12
dt_qty = 4
assert row.quantity - dt_qty == 8 # 스윙 잔량