from datetime import date from decimal import Decimal from fastapi.testclient import TestClient from sqlalchemy.orm import Session from app.models.screening import ScreeningSignal from app.models.stock import MarketIndex, Price, Sector, Stock def _add_signal( db: Session, *, ticker: str, sector: str, trading_value: int, daily_return: Decimal, is_limit_up: bool = False, ) -> None: db.add( ScreeningSignal( screen_date=date(2026, 5, 22), ticker=ticker, name=f"{ticker} Corp", sector=sector, trading_value=trading_value, daily_return=daily_return, is_limit_up=is_limit_up, status="pending", ) ) def _add_stock_with_sector(db: Session, ticker: str, name: str, sector: str) -> None: db.add(Stock(ticker=ticker, name=name, market="KOSPI", base_date=date(2026, 5, 22))) db.add( Sector( ticker=ticker, sector_code=f"S{ticker[-2:]}", company_name=name, sector_name=sector, base_date=date(2026, 5, 22), ) ) def _add_price_series( db: Session, ticker: str, *, start_close: int, step: int, latest_volume: int, ) -> None: for offset in range(31): current_date = date(2026, 4, 22 + offset) if offset <= 8 else date(2026, 5, offset - 8) close = Decimal(start_close + (step * offset)) volume = latest_volume if offset == 30 else 1_000_000 db.add( Price( ticker=ticker, date=current_date, open=close - Decimal("10"), high=close + Decimal("20"), low=close - Decimal("20"), close=close, volume=volume, trading_value=int(close) * volume, ) ) def _add_kospi_series(db: Session) -> None: for offset in range(31): current_date = date(2026, 4, 22 + offset) if offset <= 8 else date(2026, 5, offset - 8) close = Decimal(2500 + offset) db.add( MarketIndex( code="1001", date=current_date, name="KOSPI", open=close, high=close + Decimal("5"), low=close - Decimal("5"), close=close, volume=1_000_000, trading_value=100_000_000_000, ) ) def test_sector_strongest_returns_top_signal_per_sector( client: TestClient, auth_headers: dict, db: Session, ): _add_signal( db, ticker="000001", sector="반도체", trading_value=250_000_000_000, daily_return=Decimal("0.0500"), ) _add_signal( db, ticker="000002", sector="반도체", trading_value=400_000_000_000, daily_return=Decimal("0.0300"), ) _add_signal( db, ticker="000003", sector="2차전지", trading_value=220_000_000_000, daily_return=Decimal("0.0100"), ) _add_signal( db, ticker="000004", sector="2차전지", trading_value=210_000_000_000, daily_return=Decimal("0.3000"), is_limit_up=True, ) db.commit() response = client.get( "/api/screening/sector-strongest?target_date=2026-05-22", headers=auth_headers, ) assert response.status_code == 200 data = response.json() assert len(data) == 2 by_sector = {item["sector"]: item for item in data} assert by_sector["반도체"]["ticker"] == "000002" assert by_sector["반도체"]["signal_count"] == 2 assert by_sector["2차전지"]["ticker"] == "000004" assert by_sector["2차전지"]["is_limit_up"] is True assert by_sector["2차전지"]["signal_strength"] > by_sector["반도체"]["signal_strength"] def test_recent_sector_candidates_returns_stronger_same_sector_stocks( client: TestClient, auth_headers: dict, db: Session, ): _add_signal( db, ticker="000001", sector="반도체", trading_value=120_000_000_000, daily_return=Decimal("0.0200"), ) _add_stock_with_sector(db, "000001", "기준전자", "반도체") _add_stock_with_sector(db, "000002", "강한전자", "반도체") _add_stock_with_sector(db, "000003", "약한전자", "반도체") _add_stock_with_sector(db, "000004", "다른섹터", "자동차") _add_kospi_series(db) _add_price_series(db, "000001", start_close=10000, step=5, latest_volume=1_000_000) _add_price_series(db, "000002", start_close=10000, step=250, latest_volume=8_000_000) _add_price_series(db, "000003", start_close=10000, step=-5, latest_volume=1_000_000) _add_price_series(db, "000004", start_close=10000, step=400, latest_volume=10_000_000) db.commit() response = client.get( "/api/screening/recent-sector-candidates?as_of=2026-05-22&window_days=30", headers=auth_headers, ) assert response.status_code == 200 data = response.json() assert len(data) == 1 assert data[0]["ticker"] == "000001" assert data[0]["sector"] == "반도체" assert data[0]["stronger_count"] == 1 assert data[0]["candidates"][0]["ticker"] == "000002" assert data[0]["candidates"][0]["is_stronger_than_source"] is True