v1.1 autonomous upgrade: continuous curation, risk engine, scheduler hardening, systemd service

This commit is contained in:
2026-02-25 19:09:05 -08:00
parent cce199d73a
commit 4d17bf8018
14 changed files with 209 additions and 59 deletions

59
bot.py
View File

@@ -1,17 +1,49 @@
from apscheduler.schedulers.background import BackgroundScheduler
from datetime import datetime
from datetime import datetime, timedelta
import json
from sqlalchemy import func
from config import settings
from db import SessionLocal, BotDecision, TradeExecution
from services import searx_news, ollama_decide, place_order
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)
@@ -28,8 +60,20 @@ def run_cycle():
db.commit()
db.refresh(drow)
if decision["action"] in {"buy", "sell"} and decision["confidence"] >= 0.55:
res = place_order(symbol, decision["action"], min(settings.max_order_usd, decision["order_usd"]))
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)
@@ -37,11 +81,13 @@ def run_cycle():
symbol=symbol,
side=decision["action"],
qty=float((res or {}).get("json", {}).get("qty", 0) or 0),
notional=min(settings.max_order_usd, decision["order_usd"]),
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)
@@ -51,5 +97,6 @@ def run_cycle():
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()