Files
alpaca-llm-bot-v1/bot.py

103 lines
3.5 KiB
Python

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()