181 lines
5.3 KiB
Python
181 lines
5.3 KiB
Python
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
|