lindonstout21/Kronos
Kronos Price Forecasting Service
A FastAPI microservice that forecasts OHLCV price bars using the Kronos foundation model (Kronos-small + Kronos-Tokenizer-base). It fetches market data via yfinance, runs the model, and returns predicted candles as JSON — ready to plug into the Stock Command Center dashboard.
┌────────────┐ POST /predict ┌─────────────────────┐
│ Frontend │ ─────────────────────► │ prediction_service │
│ (dashboard)│ ◄───────────────────── │ (FastAPI/uvicorn) │
└────────────┘ JSON forecast └─────────┬───────────┘
│
┌──────────────────────┼───────────────────────┐
▼ ▼ ▼
data_fetcher.py forecaster.py kronos/ (model)
(yfinance + cache) (load + predict wrapper) (cloned upstream)Layout
Setup
Requires Python 3.9+.
cd ~/stock-forecasting
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtThe model weights (~25M params for Kronos-small) and tokenizer are pulled from the Hugging Face Hub on first run and cached under ~/.cache/huggingface. Inference runs on CUDA or Apple mps automatically if available, otherwise CPU.
Running the service
# from the project root, with the venv active
uvicorn prediction_service:app --host 0.0.0.0 --port 8000
# or simply:
python prediction_service.pyOn startup the service loads the model. Watch for startup: model ready. Until then /predict returns 503.
Configuration (environment variables)
Endpoints
GET /health
{ "status": "ok", "model_loaded": true, "model": "NeoQuasar/Kronos-small",
"device": "cpu", "max_lookback": 512 }POST /predict
Forecast from history you supply. The most recent 512 bars are used (Kronos-small's context limit); older bars are ignored.
Request body:
{
"symbol": "AAPL",
"forecast_periods": 120,
"temperature": 1.0,
"top_p": 0.9,
"sample_count": 1,
"historical_data": [
{"open": 195.1, "high": 196.4, "low": 194.0, "close": 195.8,
"volume": 54213000, "timestamp": "2026-05-01T00:00:00"},
"... oldest first ..."
]
}open/high/low/closeare required per bar;volumedefaults to0,timestampis optional (a daily index is synthesized if omitted, but supplying real timestamps gives the model better temporal features).forecast_periodsdefaults to120, range1..2000.- Sampling defaults:
temperature=1.0,top_p=0.9,sample_count=1.
Response:
{
"symbol": "AAPL",
"forecast_periods": 120,
"lookback_used": 512,
"device": "cpu",
"predictions": [
{"timestamp": "2026-05-02T00:00:00", "open": 196.0, "high": 197.2,
"low": 195.1, "close": 196.8, "volume": 51200000.0, "amount": 1.0e10}
]
}POST /predict/{symbol}
One-shot convenience: the service fetches history itself via yfinance, then forecasts. No need to ship candles from the browser.
curl -X POST http://localhost:8000/predict/BTC-USD \
-H 'Content-Type: application/json' \
-d '{"forecast_periods": 24, "period": "1mo", "interval": "1h"}'period/interval use yfinance conventions (1mo, 2y, max; 1d, 1h, 5m). Returns the same PredictResponse shape as /predict.
Error codes
Frontend integration (Stock Command Center)
The dashboard is a single-file static app, so call the service over fetch. Run the service locally (or deploy it somewhere reachable) and point the frontend at it.
async function getForecast(symbol, periods = 24) {
const res = await fetch(`http://localhost:8000/predict/${symbol}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ forecast_periods: periods, period: "1mo", interval: "1h" }),
});
if (!res.ok) throw new Error(`forecast failed: ${res.status} ${await res.text()}`);
const data = await res.json();
// data.predictions: [{ timestamp, open, high, low, close, volume, amount }, ...]
return data.predictions;
}If you already hold candles in the browser (e.g. from Massive), use /predict instead and pass them as historical_data (oldest first).
CORS: the dashboard runs on a different origin (Netlify) than this service. Add CORSMiddleware before exposing it to the deployed frontend:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://reliable-snickerdoodle-e9cca1.netlify.app", "http://localhost:*"],
allow_methods=["POST", "GET"],
allow_headers=["*"],
)Forecasts are advisory, not financial advice. Kronos is a probabilistic sampler — raise sample_count to average multiple Monte-Carlo paths for a more stable central estimate, at proportional latency cost.
Testing
.venv/bin/python -m pytest test_kronos_integration.py -v -sThe tests fetch real data (BTC-USD hourly, AAPL daily fallback), generate a 24-period forecast, and validate the output shape via both the forecaster module and the FastAPI TestClient. They skip (rather than fail) when offline or when the model can't be downloaded.
Notes & limits
Kronos-smallcontext window is 512 bars — longer histories are truncated to the most recent 512.- First request after startup is slowest (model warm-up). Keep the process warm.
- yfinance results are cached on disk for 15 minutes (
data_fetcher.py,DEFAULT_TTL_SECONDS) under.cache/market_data/.
