Initial v1 autonomous alpaca llm trading bot with dark dashboard
This commit is contained in:
109
services.py
Normal file
109
services.py
Normal file
@@ -0,0 +1,109 @@
|
||||
import json
|
||||
import random
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from config import settings
|
||||
|
||||
|
||||
def searx_news(symbol: str, limit: int = 8):
|
||||
q = f"{symbol} stock news earnings guidance analyst"
|
||||
params = {"q": q, "format": "json", "language": "en"}
|
||||
try:
|
||||
r = requests.get(settings.searx_url, params=params, timeout=20)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
out = []
|
||||
for it in data.get("results", [])[:limit]:
|
||||
out.append({"title": it.get("title", ""), "url": it.get("url", ""), "content": it.get("content", "")[:400]})
|
||||
return out
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
def ollama_decide(symbol: str, context_items: list):
|
||||
prompt = {
|
||||
"task": "You are a strict trading policy engine. Return JSON only.",
|
||||
"constraints": {
|
||||
"actions": ["buy", "sell", "hold"],
|
||||
"max_order_usd": settings.max_order_usd,
|
||||
"style": "conservative intraday swing",
|
||||
},
|
||||
"symbol": symbol,
|
||||
"news": context_items,
|
||||
"output_schema": {
|
||||
"action": "buy|sell|hold",
|
||||
"confidence": "0-1",
|
||||
"reason": "short rationale",
|
||||
"order_usd": f"<= {settings.max_order_usd}",
|
||||
},
|
||||
}
|
||||
payload = {
|
||||
"model": settings.ollama_model,
|
||||
"prompt": json.dumps(prompt),
|
||||
"stream": False,
|
||||
"format": "json",
|
||||
}
|
||||
try:
|
||||
r = requests.post(f"{settings.ollama_url}/api/generate", json=payload, timeout=40)
|
||||
r.raise_for_status()
|
||||
resp = r.json().get("response", "{}")
|
||||
d = json.loads(resp)
|
||||
action = d.get("action", "hold").lower()
|
||||
if action not in {"buy", "sell", "hold"}:
|
||||
action = "hold"
|
||||
confidence = float(d.get("confidence", 0.5))
|
||||
order_usd = min(float(d.get("order_usd", settings.max_order_usd)), settings.max_order_usd)
|
||||
reason = d.get("reason", "fallback")
|
||||
return {"action": action, "confidence": confidence, "order_usd": order_usd, "reason": reason}
|
||||
except Exception:
|
||||
# resilient fallback to hold or tiny buy
|
||||
return {
|
||||
"action": random.choice(["hold", "hold", "buy"]),
|
||||
"confidence": 0.3,
|
||||
"order_usd": min(1.0, settings.max_order_usd),
|
||||
"reason": "fallback-mode",
|
||||
}
|
||||
|
||||
|
||||
def alpaca_headers():
|
||||
return {
|
||||
"APCA-API-KEY-ID": settings.alpaca_key,
|
||||
"APCA-API-SECRET-KEY": settings.alpaca_secret,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
|
||||
def alpaca_last_price(symbol: str):
|
||||
url = f"https://data.alpaca.markets/v2/stocks/{symbol}/trades/latest"
|
||||
try:
|
||||
r = requests.get(url, headers=alpaca_headers(), timeout=20)
|
||||
r.raise_for_status()
|
||||
return float(r.json()["trade"]["p"])
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def place_order(symbol: str, action: str, order_usd: float):
|
||||
if action not in {"buy", "sell"}:
|
||||
return None
|
||||
payload = {
|
||||
"symbol": symbol,
|
||||
"side": action,
|
||||
"type": "market",
|
||||
"time_in_force": "day",
|
||||
"notional": round(order_usd, 2),
|
||||
}
|
||||
try:
|
||||
r = requests.post(f"{settings.alpaca_base}/v2/orders", headers=alpaca_headers(), json=payload, timeout=20)
|
||||
return {"ok": r.ok, "status": r.status_code, "json": r.json() if r.text else {}}
|
||||
except Exception as e:
|
||||
return {"ok": False, "status": 0, "json": {"error": str(e)}}
|
||||
|
||||
|
||||
def account_snapshot():
|
||||
try:
|
||||
r = requests.get(f"{settings.alpaca_base}/v2/account", headers=alpaca_headers(), timeout=20)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
except Exception:
|
||||
return {}
|
||||
Reference in New Issue
Block a user