85 lines
2.8 KiB
Python
Raw Normal View History

"""
Super Quality strategy implementation.
"""
from datetime import date
from decimal import Decimal
from app.services.strategy.base import BaseStrategy
from app.schemas.strategy import StockFactor, StrategyResult, UniverseFilter
class QualityStrategy(BaseStrategy):
"""Super Quality strategy using F-Score and quality factors."""
strategy_name = "quality"
def run(
self,
universe_filter: UniverseFilter,
top_n: int,
base_date: date = None,
min_fscore: int = 7,
) -> StrategyResult:
if base_date is None:
base_date = date.today()
# Get universe
stocks = self.get_universe(universe_filter)
tickers = [s.ticker for s in stocks]
stock_map = {s.ticker: s for s in stocks}
if not tickers:
return StrategyResult(
strategy_name=self.strategy_name,
base_date=base_date,
universe_count=0,
result_count=0,
stocks=[],
)
# Get data
valuations = self.factor_calc.get_valuations(tickers, base_date)
sectors = self.factor_calc.get_sectors(tickers)
fscores = self.factor_calc.calculate_fscore(tickers)
quality_scores = self.factor_calc.calculate_quality_scores(tickers, base_date)
# Filter by F-Score
qualified_tickers = [t for t in tickers if fscores.get(t, 0) >= min_fscore]
# Build results
results = []
for ticker in qualified_tickers:
stock = stock_map[ticker]
val = valuations.get(ticker)
q_score = quality_scores.get(ticker, Decimal("0"))
fscore = fscores.get(ticker, 0)
results.append(StockFactor(
ticker=ticker,
name=stock.name,
market=stock.market,
sector_name=sectors.get(ticker),
market_cap=int(stock.market_cap / 100_000_000) if stock.market_cap else None,
close_price=Decimal(str(stock.close_price)) if stock.close_price else None,
per=Decimal(str(val.per)) if val and val.per else None,
pbr=Decimal(str(val.pbr)) if val and val.pbr else None,
dividend_yield=Decimal(str(val.dividend_yield)) if val and val.dividend_yield else None,
quality_score=q_score,
total_score=q_score,
fscore=fscore,
))
# Sort by quality score
results.sort(key=lambda x: x.total_score or Decimal("0"), reverse=True)
for i, r in enumerate(results[:top_n], 1):
r.rank = i
return StrategyResult(
strategy_name=self.strategy_name,
base_date=base_date,
universe_count=len(stocks),
result_count=min(top_n, len(results)),
stocks=results[:top_n],
)