124 lines
3.7 KiB
Python

"""All Value Strategy (PER, PBR, PCR, PSR, DY)."""
from typing import List, Dict
from decimal import Decimal
from datetime import datetime
from sqlalchemy.orm import Session
import pandas as pd
from app.strategies.base import BaseStrategy
from app.utils.data_helpers import (
get_ticker_list,
get_value_indicators,
calculate_value_rank,
get_prices_on_date
)
class AllValueStrategy(BaseStrategy):
"""
종합 가치 투자 전략.
- PER, PBR, PCR, PSR, DY 5가지 가치 지표 통합
- 낮은 밸류에이션 종목 선정
"""
def __init__(self, config: Dict = None):
"""
초기화.
Args:
config: 전략 설정
- count: 선정 종목 수 (기본 20)
"""
super().__init__(config)
self.count = config.get('count', 20)
def select_stocks(self, rebal_date: datetime, db_session: Session) -> List[str]:
"""
종목 선정.
Args:
rebal_date: 리밸런싱 날짜
db_session: 데이터베이스 세션
Returns:
선정된 종목 코드 리스트
"""
try:
# 1. 종목 리스트 조회
ticker_list = get_ticker_list(db_session)
if ticker_list.empty:
return []
tickers = ticker_list['종목코드'].tolist()
# 2. 5가지 밸류 지표 조회 (PER, PBR, DY, PSR, PCR)
value_list = get_value_indicators(
db_session,
tickers,
base_date=rebal_date,
include_psr_pcr=True
)
if value_list.empty:
return []
# 3. 가로로 긴 형태로 변경 (pivot)
value_pivot = value_list.pivot(index='종목코드', columns='지표', values='')
# 4. 티커 테이블과 가치 지표 테이블 병합
data_bind = ticker_list[['종목코드', '종목명']].merge(
value_pivot,
how='left',
on='종목코드'
)
# 5. 5개 지표 중 적어도 3개 이상 있는 종목만 필터링
required_cols = ['PER', 'PBR', 'PCR', 'PSR', 'DY']
available_cols = [col for col in required_cols if col in data_bind.columns]
if len(available_cols) < 3:
return []
# 최소 3개 이상의 지표가 있는 종목만
data_bind['valid_count'] = data_bind[available_cols].notna().sum(axis=1)
data_bind = data_bind[data_bind['valid_count'] >= 3]
if data_bind.empty:
return []
# 6. 순위 계산 (DY는 높을수록 좋으므로 calculate_value_rank에서 처리)
value_sum = calculate_value_rank(
data_bind.set_index('종목코드'),
available_cols
)
# 7. 상위 N개 선정
data_bind['rank'] = value_sum
data_bind = data_bind.dropna(subset=['rank'])
selected = data_bind.nsmallest(self.count, 'rank')
return selected['종목코드'].tolist()
except Exception as e:
print(f"All Value 전략 종목 선정 오류: {e}")
return []
def get_prices(
self,
tickers: List[str],
date: datetime,
db_session: Session
) -> Dict[str, Decimal]:
"""
종목 가격 조회.
Args:
tickers: 종목 코드 리스트
date: 조회 날짜
db_session: 데이터베이스 세션
Returns:
{ticker: price} 딕셔너리
"""
return get_prices_on_date(db_session, tickers, date)