feat: 데이트레이딩 포지션 화면 표시 + 모든 매도 즉시 화면 반영
- portfolio_cache_bridge 신설: 매매 엔진 체결 시 Portfolio(db_worker 큐) + 인메모리 캐시 즉시 갱신. DT 매수는 strategy_type='daytrading'으로 태깅되어 보유 종목 패널의 '데이트레이딩' 그룹에 표시되고 스윙 청산 로직(strategy_type='swing' 대상)에서 제외됨. (기존엔 KIS 동기화로 'swing'으로 유입돼 DT 포지션이 안 보이고 스윙 엔진이 같이 관리하는 문제) - DT 매도·스윙 자동 매도도 캐시에서 즉시 제거/차감 + 최근매도 기록(동기화 되살림 방지). - 동기화 시 DT 엔진 보유 종목은 'daytrading'으로 태깅. - 프론트: 수동 매도 성공 시 낙관적 갱신으로 서버 재조회 전 즉시 목록에서 제거. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
48
backend/tests/test_portfolio_cache_bridge.py
Normal file
48
backend/tests/test_portfolio_cache_bridge.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user