from apscheduler.schedulers.background import BackgroundScheduler from datetime import datetime, timedelta import json from sqlalchemy import func from config import settings from db import SessionLocal, BotDecision, TradeExecution, CuratedInsight from services import searx_news, ollama_decide, place_order, market_open, positions_snapshot, summarize_news_with_ollama scheduler = BackgroundScheduler(timezone=settings.timezone) def _daily_spent(db): since = datetime.utcnow() - timedelta(hours=24) return float(db.query(func.coalesce(func.sum(TradeExecution.notional), 0)).filter(TradeExecution.ts >= since).scalar() or 0) def curate_cycle(): db = SessionLocal() try: for symbol in settings.symbols: news = searx_news(symbol) summary = summarize_news_with_ollama(symbol, news) row = CuratedInsight(symbol=symbol, summary=summary, sources=json.dumps(news)[:60000]) db.add(row) db.commit() finally: db.close() def run_cycle(): db = SessionLocal() try: if not market_open(): return spent = _daily_spent(db) if spent >= settings.max_daily_notional: return pos = positions_snapshot() if len(pos) >= settings.max_open_positions: return for symbol in settings.symbols: if spent >= settings.max_daily_notional: break news = searx_news(symbol) decision = ollama_decide(symbol, news) drow = BotDecision( symbol=symbol, action=decision["action"], confidence=decision["confidence"], reason=decision["reason"], market_context=json.dumps(news)[:60000], order_usd=decision["order_usd"], status="planned", ) db.add(drow) db.commit() db.refresh(drow) should_trade = ( decision["action"] in {"buy", "sell"} and decision["confidence"] >= settings.min_confidence ) if should_trade: notional = min(settings.max_order_usd, decision["order_usd"], settings.max_daily_notional - spent) if notional <= 0: drow.status = "risk_blocked" db.add(drow) db.commit() continue res = place_order(symbol, decision["action"], notional) ok = bool(res and res.get("ok")) drow.status = "executed" if ok else "failed" db.add(drow) db.add(TradeExecution( symbol=symbol, side=decision["action"], qty=float((res or {}).get("json", {}).get("qty", 0) or 0), notional=notional, alpaca_order_id=(res or {}).get("json", {}).get("id", ""), raw=json.dumps(res)[:60000], )) db.commit() if ok: spent += notional else: drow.status = "skipped" db.add(drow) db.commit() finally: db.close() def start_scheduler(): scheduler.add_job(curate_cycle, "interval", minutes=settings.curate_interval_minutes, id="curate_cycle", replace_existing=True) scheduler.add_job(run_cycle, "interval", hours=settings.trade_interval_hours, id="trade_cycle", replace_existing=True) scheduler.start()