110 lines
3.4 KiB
Python
110 lines
3.4 KiB
Python
"""
|
|
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()
|