""" Daily KJB signal generation job. """ import logging from datetime import date, timedelta import pandas as pd from app.core.database import SessionLocal from app.models.stock import Stock, Price from app.models.signal import Signal, SignalType, SignalStatus from app.services.strategy.kjb import KJBSignalGenerator logger = logging.getLogger(__name__) def run_kjb_signals(): """ Generate KJB trading signals for today. Called by scheduler at 18:15 KST (after price collection). """ logger.info("Starting KJB signal generation") db = SessionLocal() try: today = date.today() signal_gen = KJBSignalGenerator() stocks = ( db.query(Stock) .filter(Stock.market_cap.isnot(None)) .order_by(Stock.market_cap.desc()) .limit(30) .all() ) tickers = [s.ticker for s in stocks] name_map = {s.ticker: s.name for s in stocks} lookback_start = today - timedelta(days=90) kospi_prices = ( db.query(Price) .filter(Price.ticker == "069500") .filter(Price.date >= lookback_start, Price.date <= today) .order_by(Price.date) .all() ) if not kospi_prices: logger.warning("No KOSPI data available for signal generation") return kospi_df = pd.DataFrame([ {"date": p.date, "close": float(p.close)} for p in kospi_prices ]).set_index("date") signals_created = 0 for ticker in tickers: stock_prices = ( db.query(Price) .filter(Price.ticker == ticker) .filter(Price.date >= lookback_start, Price.date <= today) .order_by(Price.date) .all() ) if len(stock_prices) < 21: continue stock_df = pd.DataFrame([{ "date": p.date, "open": float(p.open), "high": float(p.high), "low": float(p.low), "close": float(p.close), "volume": int(p.volume), } for p in stock_prices]).set_index("date") signals = signal_gen.generate_signals(stock_df, kospi_df) if today in signals.index and signals.loc[today, "buy"]: close_price = stock_df.loc[today, "close"] reason_parts = [] if signals.loc[today, "breakout"]: reason_parts.append("breakout") if signals.loc[today, "large_candle"]: reason_parts.append("large_candle") signal = Signal( date=today, ticker=ticker, name=name_map.get(ticker), signal_type=SignalType.BUY, entry_price=close_price, target_price=round(close_price * 1.05, 2), stop_loss_price=round(close_price * 0.97, 2), reason=", ".join(reason_parts), status=SignalStatus.ACTIVE, ) db.add(signal) signals_created += 1 db.commit() logger.info(f"KJB signal generation complete: {signals_created} buy signals") except Exception as e: logger.exception(f"KJB signal generation failed: {e}") finally: db.close()