zephyrdark 3f8ef7e108 feat: add multi-factor, quality, and value-momentum strategies
- BaseStrategy abstract class
- MultiFactorStrategy with weighted factors
- QualityStrategy with F-Score filtering
- ValueMomentumStrategy combining value and momentum

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 08:59:05 +09:00

85 lines
2.8 KiB
Python

"""
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],
)