Metadata-Version: 2.4
Name: kronos-finance
Version: 0.1.3
Summary: Pythonic wrapper around the Kronos foundation model for OHLCV forecasting across any market
Author-email: lordxmen2k <lordxmen2k@users.noreply.github.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/lordxmen2k/kronos-finance
Project-URL: Repository, https://github.com/lordxmen2k/kronos-finance
Project-URL: Issues, https://github.com/lordxmen2k/kronos-finance/issues
Project-URL: Changelog, https://github.com/lordxmen2k/kronos-finance/blob/main/CHANGELOG.md
Project-URL: Citation, https://github.com/lordxmen2k/kronos-finance#citation
Keywords: kronos,finance,ohlcv,forecasting,foundation-model,time-series,quant
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: torch>=2.0.0
Requires-Dist: numpy>=1.26
Requires-Dist: pandas>=2.2
Requires-Dist: einops>=0.8.1
Requires-Dist: huggingface_hub>=0.33.1
Requires-Dist: safetensors>=0.6.2
Requires-Dist: tqdm>=4.67.1
Requires-Dist: matplotlib>=3.9.3
Provides-Extra: cn
Requires-Dist: akshare>=1.18.0; extra == "cn"
Provides-Extra: global
Requires-Dist: yfinance>=0.2.60; extra == "global"
Provides-Extra: crypto
Requires-Dist: ccxt>=4.0.0; extra == "crypto"
Provides-Extra: qlib
Requires-Dist: pyqlib>=0.9.7; extra == "qlib"
Provides-Extra: ui
Requires-Dist: flask>=2.3; extra == "ui"
Requires-Dist: flask-cors>=4.0; extra == "ui"
Requires-Dist: plotly>=5.17; extra == "ui"
Provides-Extra: analysis
Requires-Dist: pandas-ta>=0.3.14b; extra == "analysis"
Requires-Dist: quantstats>=0.0.62; extra == "analysis"
Provides-Extra: finetune
Requires-Dist: pyqlib>=0.9.7; extra == "finetune"
Requires-Dist: pyyaml>=6.0; extra == "finetune"
Requires-Dist: comet-ml>=3.0; extra == "finetune"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Requires-Dist: mypy>=1.11; extra == "dev"
Requires-Dist: pre-commit>=4.0; extra == "dev"
Requires-Dist: ipython>=8.0; extra == "dev"
Provides-Extra: test
Requires-Dist: pytest-playwright>=0.5; extra == "test"
Requires-Dist: playwright>=1.47; extra == "test"
Provides-Extra: docs
Requires-Dist: mkdocs>=1.5; extra == "docs"
Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
Provides-Extra: all
Requires-Dist: akshare>=1.18.0; extra == "all"
Requires-Dist: yfinance>=0.2.60; extra == "all"
Requires-Dist: ccxt>=4.0.0; extra == "all"
Requires-Dist: pyqlib>=0.9.7; extra == "all"
Requires-Dist: flask>=2.3; extra == "all"
Requires-Dist: flask-cors>=4.0; extra == "all"
Requires-Dist: plotly>=5.17; extra == "all"
Requires-Dist: pandas-ta>=0.3.14b; extra == "all"
Requires-Dist: quantstats>=0.0.62; extra == "all"
Requires-Dist: pyyaml>=6.0; extra == "all"
Requires-Dist: comet-ml>=3.0; extra == "all"
Dynamic: license-file

# kronos-finance

[![](https://img.shields.io/pypi/v/kronos-finance.svg)](https://pypi.org/project/kronos-finance/)
[![](https://img.shields.io/pypi/pyversions/kronos-finance.svg)](https://pypi.org/project/kronos-finance/)
[![](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)
[![](https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12-blue.svg)](#)

> Pythonic wrapper around the Kronos foundation model for OHLCV forecasting across any market.

## Built on Kronos

Kronos is the first open-source foundation model for financial candlesticks (K-lines),
by the [NeoQuasar team](https://github.com/shiyu-coder/Kronos), accepted at AAAI 2026,
MIT-licensed.

This package (`kronos-finance`) is a **wrapper** that turns the upstream research codebase
into a pip-installable library with a CLI, a dashboard, multi-source data loaders, and
comprehensive tests. All model code comes from the original project — see the
[Citation](#citation) section.

- Original repo: <https://github.com/shiyu-coder/Kronos>
- Paper: <https://arxiv.org/abs/2508.02739>
- Model zoo on HuggingFace: <https://huggingface.co/NeoQuasar>

## Use a virtual environment (recommended)

A virtual environment keeps your system Python clean and avoids the
`error: externally-managed-environment` (PEP 668) error on Ubuntu 23.04+,
macOS Homebrew Python, and Fedora 39+.

```bash
python -m venv .venv
source .venv/bin/activate          # macOS / Linux
# .venv\Scripts\activate           # Windows PowerShell
```

Every install command below assumes you've activated a venv first.

## Installation

```bash
pip install kronos-finance                       # core (CUDA-enabled PyTorch, ~800MB)
pip install kronos-finance[cn]                   # + AKShare for Chinese A-shares
pip install kronos-finance[global]               # + yfinance for global equities
pip install kronos-finance[crypto]               # + CCXT for crypto exchanges
pip install kronos-finance[qlib]                 # + Qlib for CN finetune data
pip install kronos-finance[ui]                   # + Flask + Plotly for the dashboard
pip install kronos-finance[analysis]             # + pandas-ta + quantstats
pip install kronos-finance[all]                  # everything above
```

The default install includes CUDA-enabled PyTorch and works on CPU or GPU.
For CPU-only or a specific CUDA version, see [Hardware](#hardware-cpu-vs-cuda)
below before installing.

Verify the install:

```bash
kronos --version
```

## Quickstart (60 seconds)

```python
from kronos_finance import load_kronos
from kronos_finance.data import load_ohlcv
import pandas as pd

df = load_ohlcv("AAPL", period="2y", interval="1d")
wrapper = load_kronos(model_id="small")
y_ts = pd.date_range(df["timestamps"].iloc[-1], periods=31, freq="1D")[1:]
pred = wrapper.predict(
    df=df[["open", "high", "low", "close", "volume", "amount"]].tail(400),
    x_timestamp=df["timestamps"].tail(400), y_timestamp=y_ts, pred_len=30,
)
print(pred.head())
```

## Features

- One-line predict on any market — US equities, CN A-shares, crypto, HK, JP, EU.
- Multi-source loaders — AKShare, yfinance, CCXT, Qlib, local CSV.
- Ticker auto-detection — type `"600519"` and AKShare is picked; type `"BTC/USDT"` and
  CCXT is picked.
- Bundled ticker catalog + user-extendable `~/.kronos/tickers.json`.
- CLI: `kronos predict`, `kronos backtest`, `kronos ui`, `kronos tickers`.
- Flask dashboard with candlestick chart, autocomplete, model selector, indicator overlay.
- Optional indicators (RSI, MACD, BBands) and HTML tearsheets (quantstats).
- 12 runnable examples + comprehensive docs + a 35-term glossary.

## How to predict any ticker in the world

| Market | Ticker format | Source | Install extra |
|---|---|---|---|
| US equity | `AAPL`, `MSFT`, `NVDA` | yfinance | `[global]` |
| US ETF | `SPY`, `QQQ`, `IWM` | yfinance | `[global]` |
| US index | `^GSPC`, `^DJI`, `^IXIC` | yfinance | `[global]` |
| CN A-share | `600519`, `000001`, `002594` | AKShare | `[cn]` |
| CN index | `000300`, `000905` | AKShare | `[cn]` |
| Crypto pair | `BTC/USDT`, `ETH/USDT` | CCXT (Binance default) | `[crypto]` |
| HK stock | `0700.HK`, `9988.HK` | yfinance | `[global]` |
| JP stock | `7203.T`, `6758.T` | yfinance | `[global]` |
| EU stock | `ASML.AS`, `SAP.DE` | yfinance | `[global]` |
| Local CSV | path to `.csv` | `csv_path=` | (core) |

The `source="auto"` default routes the ticker to the right loader based on its shape.

## CLI reference

```bash
kronos predict TICKER [--source auto] [--model small] [--interval 1d]
                     [--pred-len 30] [--lookback 400] [--export forecast.csv]
                     [--device cpu]
kronos batch TICKERS_FILE [--output ./out] [--model small] [--pred-len 30]
kronos backtest TICKER [--period 1y] [--interval 1d] [--source auto]
                      [--export tearsheet.html]
kronos ui [--host 127.0.0.1] [--port 5000] [--debug]
kronos tickers list
kronos tickers search QUERY [--market cn|us|crypto|...]
kronos tickers add SYMBOL NAME SOURCE [--exchange binance]
kronos --version
kronos --help
```

Examples:

```bash
kronos predict 600519 --source akshare
kronos predict BTC/USDT --source ccxt --exchange binance
```

Exit codes: `0` ok, `1` generic error, `2` usage error, `130` SIGINT (clean Ctrl+C).

## Python API

```python
from kronos_finance import load_kronos          # model
from kronos_finance.data import load_ohlcv       # data fetchers
from kronos_finance.tickers import catalog, search, add_user_ticker
from kronos_finance.analysis import (
    enrich_with_indicators, forecast_to_returns, make_tearsheet,
)
from kronos_finance import (                      # errors
    KronosFinanceError, TickerNotFoundError,
    DataSourceError, ModelLoadError, PredictionError, CatalogError,
)
```

Full reference: [`docs/API.md`](docs/API.md).

## Dashboard

Launch with `kronos ui` (default `http://127.0.0.1:5000`):

- Ticker input with autocomplete from the bundled + user catalog.
- Candlestick chart with historical in green/red and forecast in blue/purple.
- Model selector (mini / small / base with parameter counts).
- Source + interval pickers.
- Indicator overlay (RSI / MACD / BBands).
- Recent predictions history with CSV export.
- Dark / light theme toggle.

### Dashboard quickstart

1. `kronos ui` from your shell.
2. Open `http://localhost:5000` in any modern browser.
3. Type a ticker (e.g. `AAPL`, `600519`, `BTC/USDT`).
4. Click **Predict**.
5. The forecast paints on the chart; the metrics card shows the
   predicted close and expected return; the row appears in the
   Recent predictions table.
6. Click **CSV** on any row to download that prediction.

The dashboard is also a JSON API. You can drive it from any HTTP
client — see `examples/22_dashboard_api_reference.py` for curl,
Python `requests`, and JavaScript `fetch` examples for every endpoint.

Full tour (anatomy of the page, every UI element, every error message,
production deployment, extending the dashboard): [`docs/DASHBOARD.md`](docs/DASHBOARD.md).

## Examples

**Twenty-two** runnable examples covering beginner through advanced
workflows. Each one is a complete, runnable file with a docstring
explaining what it demonstrates, when to use it, what to expect, and
common pitfalls.

| #  | Scenario | Extra needed |
|---|---|---|
| 01 | Quickstart: predict AAPL 30 days | `[global]` |
| 02 | Multi-ticker US watchlist | `[global]` |
| 03 | CN A-share with column-rename walkthrough | `[cn]` |
| 04 | Crypto: BTC/USDT 5m from Binance | `[crypto]` |
| 05 | Batch sweep across many tickers | `[global]` |
| 06 | Indicators + quantstats HTML tearsheet | `[global,analysis]` |
| 07 | CLI: `kronos predict` from a shell script | `[global]` |
| 08 | CLI: `kronos backtest` from a shell script | `[global]` |
| 09 | Launch the Flask dashboard | `[ui]` |
| 10 | Save/load predictions: CSV, JSON, Parquet | `[global,analysis]` |
| 11 | Qlib-format CSV for the upstream finetune | `[qlib]` |
| 12 | Load a local Kronos checkpoint | (none) |
| 13 | Multi-timeframe: 1d + 1h + 5m on one ticker | `[global]` |
| 14 | Quantile bands via sample paths | `[global]` |
| 15 | Recursive (autoregressive) 1-year forecast | `[global]` |
| 16 | Adapt any CSV (English / Chinese / custom columns) | (none) |
| 17 | Pre-flight environment health check (CI-friendly) | (none) |
| 18 | Failure mode runbook (6 cases + recovery) | (none) |
| 19 | Daily cron-job style forecast with lockfile + logs | `[global]` |
| 20 | Forecast entirely from a local CSV (no network) | (none) |
| 21 | Portfolio construction: 3 weighting schemes | `[global]` |
| 22 | Dashboard JSON API reference (curl/Python/JS) | (none) |

Quick-pick by goal:

- *"Show me how to forecast one ticker"* → `examples/01_quickstart_predict.py`
- *"Run a daily forecast across my watchlist"* → `examples/19_cronjob_daily_forecast.py`
- *"Evaluate Kronos vs buy-and-hold"* → `examples/06_indicators_and_tearsheet.py`
- *"I have my own CSV / proprietary data"* → `examples/16_csv_with_arbitrary_columns.py` or `examples/20_local_csv_user_data.py`
- *"Troubleshoot my installation"* → `examples/17_healthcheck_environment.py` then `examples/18_failure_modes_and_recovery.py`
- *"Build a portfolio with Kronos signals"* → `examples/21_portfolio_kronos_weighting.py`

Each file starts with a docstring explaining what the example teaches.
Browse the index in [`examples/README.md`](examples/README.md).

**Want to wire Kronos into a trading strategy?** See
[Trading Strategy use cases](#trading-strategy-use-cases) for
backtesting frameworks, broker APIs (IBKR / Alpaca / ccxt), creative
uses (volatility sizing, options premium, pairs), live automation
patterns, and the disclaimers you need to read first.

## Trading Strategy use cases

> **⚠️ Disclaimer — read this before you trade real money.**
>
> This software is for **research, education, and software development**. Kronos
> is a statistical model — its outputs are *predictions*, not advice. The
> authors, contributors, and Kronos upstream maintainers are **not licensed
> financial advisors, brokers, or dealers**. Nothing in this package or
> repository constitutes a recommendation to buy, sell, or hold any
> security, derivative, cryptocurrency, or other instrument.
>
> **No warranty of profit.** Any trading strategy you build using these
> forecasts can — and will, eventually — lose money. Past model accuracy
> is not a guarantee of future returns. Backtesting is not the same as
> live trading because (a) you saw the historical data the model was
> trained on, (b) you didn't pay slippage, spreads, commissions, fees,
> funding, borrow, taxes, or market impact, (c) you assumed infinite
> liquidity and immediate fills. Real markets punish all three.
>
> **Regulatory.** Depending on where you live, running automated trading
> software against a brokerage may require a license, registration, or
> disclosure. You are solely responsible for understanding the law in
> your jurisdiction before you connect any of the code below to a real
> account. Examples below reference third-party libraries for *technical*
> capability — they are not endorsements.
>
> **Risk controls first.** Before any live automation: paper-trade it
> for ≥30 trading days, log every decision, set position-size limits,
> hard-stop daily-loss circuit breakers, and never risk more than you
> can afford to lose outright. If a strategy can't survive paper trading,
> it won't survive real trading.

So: read on for *what's possible*, build with care, and own the risks.

### From forecast to actionable strategy

A Kronos prediction is one input — not a complete strategy. A complete
strategy is the loop:

```
[Forecast]  ->  [Signal]  ->  [Sizing]  ->  [Execution]  ->  [Review]
     ^                                                      |
     +------------------------------------------------------+
```

This section walks through each ring of that loop with the libraries,
APIs, and patterns people actually use.

### 1. Signal generation — turning forecasts into trades

A Kronos forecast is a 30-bar (or whatever horizon) predicted close
trajectory. To turn that into a *trade signal*, you have many choices.
Each has different risk profiles.

| Signal logic | What it does | When to use it |
|---|---|---|
| **Direction** | Long if `pred_close > last_close`, short otherwise | First-pass prototype. Loses to spread + slippage. |
| **Threshold** | Long only if `expected_return > +X%` (e.g. +3%) | Skips weak forecasts; better hit rate. |
| **Magnitude-weighted** | Position size scales with `expected_return` (with cap) | "Conviction" sizing. Common in quant funds. |
| **Volatility-adjusted** | Compare predicted return to predicted path vol (Sharpe-like) | Avoids being long into a forecasted-messy period. |
| **Regime-conditional** | Long only when `RSI<70` AND `expected_return>+2%` | Reduces drawdown vs raw direction. |
| **Crossover** | Long when forecast close crosses predicted SMA from below | Trend-following flavor. |
| **Path consistency** | Long if the forecast is monotonic up (no flip-flops) | Filters "noise" predictions. |

A small pattern, copyable:

```python
import pandas as pd
from kronos_finance import load_kronos
from kronos_finance.data import load_ohlcv

wrapper = load_kronos(model_id="small", device="cpu")
df = load_ohlcv("AAPL", period="1y", interval="1d")
last_close = df["close"].iloc[-1]
y_ts = pd.date_range(df["timestamps"].iloc[-1], periods=31, freq="1D")[1:]
pred = wrapper.predict(
    df=df[["open", "high", "low", "close", "volume", "amount"]].tail(400),
    x_timestamp=df["timestamps"].tail(400),
    y_timestamp=pd.Series(y_ts, name="timestamps"),
    pred_len=30,
)
close_30d = pred["close"].iloc[-1]
expected_return = (close_30d - last_close) / last_close

# Magnitude-weighted long-only with a confidence threshold.
THRESHOLD = 0.03       # require >3% predicted return to engage
SIZE_CAP = 0.10        # never more than 10% of equity in this name
if expected_return > THRESHOLD:
    target_weight = min(expected_return, SIZE_CAP) * 1.0  # tune the multiplier
    side, weight = "BUY", float(target_weight)
elif expected_return < -THRESHOLD:
    side, weight = "SELL", float(min(-expected_return, SIZE_CAP))
else:
    side, weight = "HOLD", 0.0
print(f"{side} {weight:+.2%}")
```

> Don't trust the above numbers. They're a starting point. Backtest,
> paper-trade, and tune — don't trust a single backtest either; it
> overfits by construction.

### 2. Backtesting frameworks

You can wire the `wrapper.predict()` output into any standard Python
backtesting framework. The list below is ordered roughly from simplest
to most production-grade.

**Quick-and-dirty (you write the loop):** see `examples/05_batch_predict.py`
and `examples/06_indicators_and_tearsheet.py`. ~50 lines of Python, total
control, zero dependencies.

**`backtrader`** — event-driven, indicator-rich, mature.
```python
import backtrader as bt
from kronos_finance import load_kronos
from kronos_finance.data import load_ohlcv

class KronosSignal(bt.Strategy):
    params = dict(horizon=30, retrain_every=20, threshold=0.03)
    def next(self):
        if len(self.data) % self.p.retrain_every != 0:
            return
        df = self.data.p.dataname  # pre-fetched DataFrame
        # ... call wrapper.predict on df.iloc[:len(self.data)] ...
        # ... emit self.buy() / self.sell() based on expected_return ...

cerebro = bt.Cerebro()
cerebro.addstrategy(KronosSignal)
cerebro.adddata(load_ohlcv("AAPL", period="5y", interval="1d"))
cerebro.broker.setcash(100_000)
cerebro.run()
```

**`vectorbt`** — vectorized, fast, lots of plots. Best for parameter
sweeps because it's 100x faster than event-driven frameworks.

**`zipline-reloaded`** — the original Quantopian engine. Best if you're
porting an old Quantopian algo.

**`lean` / QuantConnect Lean (C# / Python)** — institutional-grade,
multi-asset, comes with a cloud research environment. Free for paper
trading.

**`freqtrade`** — dedicated to crypto. Has its own strategy DSL and a
backtesting CLI. Plug Kronos as a custom "predictor" source.

**`nautilus_trader`** — Rust core, Python API. Professional-grade
event-driven backtest + live. Designed for HFT-grade realism.

**`backtesting.py`** — minimal, well-documented, ~5-line strategies.
Perfect for "I just want to know if the idea is nonsense" sanity
checks.

> **The most common backtest bug is look-ahead bias.** Kronos is fit on
> history up to time `T`. To predict `T+1`, it must see *only* data up
> to and including `T`. Re-feeding the actual `T+1` close (even by
> accident via an off-by-one) gives you an oracle that doesn't exist
> live. The recursive-prediction pattern in
> `examples/15_recursive_predict.py` shows the safe way to chain
> multi-step predictions without leaking.

### 3. Live trading APIs (brokerage integrations)

Once a backtest is convincing, you can wire the same signal-generation
code to a live broker. *You* are responsible for testing in paper mode
first, complying with broker terms of service, and any regulatory
requirements in your jurisdiction.

| Broker / API | Asset classes | API style | Notes |
|---|---|---|---|
| **Interactive Brokers (`ib_insync`)** | Stocks, options, futures, FX, bonds worldwide | Python wrapper over TWS API | Mature. Supports paper trading. Read [IBKR docs](https://ibkrcampus.com/ibkr-api-page/). |
| **Alpaca (`alpaca-py`)** | US stocks + crypto | REST + WebSocket | Commission-free, paper-trading key is free in minutes. Great starting point. |
| **TD Ameritrade / Schwab** | US stocks + options | REST | Being deprecated — Schwab is the successor. OAuth flow. |
| **Tradier** | US stocks + options | REST | Developer-friendly. Free sandbox. |
| **Binance / Coinbase / Kraken** (via `ccxt`) | Crypto | REST + WebSocket | `ccxt` gives a uniform interface to 100+ exchanges. |
| **OANDA (v20 REST)** | FX, CFDs, metals | REST | Practice account available. |
| **Polygon.io** | US stocks, options, forex, crypto | REST + WebSocket | Real-time + historical ticks. Free tier limited. |
| **Tradegate / DEGIRO / IBKR EU** | EU stocks | varies | For European markets. |

A minimal Alpaca example to show the wiring (paper trading only — do
NOT use live keys until you've tested):

```python
# pip install alpaca-py
from alpaca_trade_api.rest import REST, TimeFrame
from kronos_finance import load_kronos
from kronos_finance.data import load_ohlcv

API_KEY = "PAPER_KEY_HERE"          # <-- from alpaca.markets paper account
API_SECRET = "PAPER_SECRET_HERE"    # <-- never commit real keys
BASE_URL = "https://paper-api.alpaca.markets"

api = REST(API_KEY, API_SECRET, BASE_URL)

# 1. Get Kronos forecast for a ticker
wrapper = load_kronos(model_id="small", device="cpu")
df = load_ohlcv("AAPL", period="1y", interval="1d")
pred = wrapper.predict(...)
last_close = float(df["close"].iloc[-1])
expected = (float(pred["close"].iloc[-1]) - last_close) / last_close

# 2. Decide side + size from the forecast
if expected > 0.03:
    side, qty = "buy", 10
elif expected < -0.03:
    side, qty = "sell", 10
else:
    side, qty = None, 0

# 3. Submit a paper order
if side:
    api.submit_order(
        symbol="AAPL", qty=qty, side=side,
        type="market", time_in_force="day",
    )
```

> **Do not run this code with live keys without weeks of paper trading.**
> The model can (and will) make confident wrong predictions. Position
> sizing, stop-losses, daily-loss circuit breakers, and broker kill
> switches must be in place first.

### 4. Creative non-obvious uses

Beyond "buy if up, sell if down," Kronos predictions have uses that
don't fit the simple long/short template. Each scenario below is
walked through end-to-end: what it does, why it works, what to
install, and a copyable command sequence or Python snippet that
runs it.

#### 4.1 Volatility-aware position sizing

**What it does:** Inverts the usual "size up when the forecast is
big" intuition. Wide predicted distributions = uncertain forecasts =
shrink the position. Tight distributions = confident forecasts =
let the position breathe.

**Why it works:** Kelly-criterion-style sizing says position size
should be proportional to *edge / variance*. Most retail traders
do the opposite (large bets on exciting forecasts). Kronos's
predicted distribution width is a free, well-calibrated variance
estimate.

**Install:**
```bash
pip install kronos-finance[global]
```

**Run:**
```bash
python examples/14_quantile_bands.py        # generates P10/P50/P90 bands
python -c "
import pandas as pd
bands = pd.read_csv('AAPL_quantile_bands.csv')  # produced by example 14
p10, p90 = bands['P10'].iloc[-1], bands['P90'].iloc[-1]
last_close = bands['P50'].iloc[0]                # the P50 at horizon start
band_width = (p90 - p10) / last_close * 100       # as a % of last close
print(f'30d P90-P10 band width: {band_width:.1f}% of last close')
# <10% = tight, confident -> allow up to 10% of equity
# 10-25% = medium       -> allow 5% of equity
# >25% = wide, fuzzy    -> 0% of equity (sit out)
"
```

The full pipeline is in `examples/14_quantile_bands.py`. To wire it
into sizing, swap the simple `expected_return` lookup in `examples/21_portfolio_kronos_weighting.py`
for the `(expected_return / band_width)` Sharpe-like ratio.

#### 4.2 Options premium selling

**What it does:** Compares Kronos's predicted 30-day range to the
options market's implied-volatility-implied range. If Kronos is
narrower than the market, the option premium is overpriced — sell it.
If Kronos is wider, the market is underpricing risk — buy it (or skip).

**Why it works:** Options are priced on volatility, not direction.
Kronos's predicted distribution width is a *realized-vol-style*
estimate; comparing it to *implied* vol from options chains reveals
where the market is wrong.

**Install:**
```bash
pip install kronos-finance[global]
pip install yfinance                  # already in [global] but explicit
pip install mibian                    # Black-Scholes implied-vol calculator
pip install wallstreet                # alternative IBKR-lite options chain
```

**Run:**
```python
import yfinance as yf
import pandas as pd
from kronos_finance import load_kronos
from kronos_finance.data import load_ohlcv

# 1. Get Kronos's predicted 30-day range
wrapper = load_kronos(model_id="small", device="cpu")
df = load_ohlcv("AAPL", period="1y", interval="1d")
y_ts = pd.date_range(df["timestamps"].iloc[-1], periods=31, freq="1D")[1:]
pred = wrapper.predict(
    df=df[["open", "high", "low", "close", "volume", "amount"]].tail(400),
    x_timestamp=df["timestamps"].tail(400),
    y_timestamp=pd.Series(y_ts, name="timestamps"),
    pred_len=30,
)
kronos_low  = float(pred["low"].min())
kronos_high = float(pred["high"].max())
kronos_range_pct = (kronos_high - kronos_low) / float(df["close"].iloc[-1]) * 100
print(f"Kronos predicted 30d range: {kronos_low:.2f} - {kronos_high:.2f} "
      f"({kronos_range_pct:.1f}% wide)")

# 2. Get the options market's implied vol (ATM 30-day call)
tkr = yf.Ticker("AAPL")
chain = tkr.option_chain()                      # nearest expiry chain
# Find the strike nearest to last close
last_close = float(df["close"].iloc[-1])
calls = chain.calls
nearest_idx = (calls["strike"] - last_close).abs().idxmin()
iv_pct = float(calls.loc[nearest_idx, "impliedVolatility"]) * 100
print(f"Options market implied vol:  {iv_pct:.1f}% "
      f"({calls.loc[nearest_idx, 'strike']} strike)")

# 3. Compare
if kronos_range_pct < iv_pct * 0.7:
    print(">> KRONOS NARROWER THAN MARKET — premium is overpriced.")
    print(">> STRATEGY: sell a covered call (or cash-secured put).")
elif kronos_range_pct > iv_pct * 1.3:
    print(">> KRONOS WIDER THAN MARKET — market is underpricing risk.")
    print(">> STRATEGY: buy protective put (or stay flat / avoid selling premium).")
else:
    print(">> Roughly aligned — no edge, sit out.")
```

**Risk note:** Selling options has *unlimited* downside on uncovered
naked calls. Only sell covered calls (against shares you own) or
cash-secured puts (against cash equal to strike × 100). Never naked.

#### 4.3 Pairs / stat-arb trading

**What it does:** Forecast two correlated instruments, watch the
spread between them, and trade when the spread's *predicted* move
diverges from its *realized* move.

**Why it works:** Two correlated names usually mean-revert. When
the spread drifts, it's a temporary dislocation. Kronos forecasts
the *direction* of the reversion; you trade the magnitude.

**Install:**
```bash
pip install kronos-finance[global]
pip install statsmodels                  # for ADF test (mean-reversion proof)
pip install scikit-learn                 # for rolling correlation
```

**Run:**
```python
import numpy as np
import pandas as pd
from statsmodels.tsa.stattools import adfuller
from kronos_finance import load_kronos
from kronos_finance.data import load_ohlcv

PAIR_A, PAIR_B = "KO", "PEP"             # or "BTC/USDT", "ETH/USDT"
HORIZON = 30

# 1. Confirm the pair actually mean-reverts (skip if not)
a = load_ohlcv(PAIR_A, period="2y", interval="1d")
b = load_ohlcv(PAIR_B, period="2y", interval="1d")
spread = a["close"].values - b["close"].values
adf_p = adfuller(spread, maxlag=5)[1]
print(f"ADF p-value on spread: {adf_p:.3f} "
      f"(<0.05 = mean-reverting, good candidate)")

if adf_p >= 0.05:
    print("Spread is NOT mean-reverting. Choose another pair.")
else:
    # 2. Forecast each leg separately
    wrapper = load_kronos(model_id="small", device="cpu")
    def forecast_one(ticker):
        d = load_ohlcv(ticker, period="1y", interval="1d")
        last = d["timestamps"].iloc[-1]
        y_ts = pd.date_range(last, periods=HORIZON + 1, freq="1D")[1:]
        return wrapper.predict(
            df=d[["open", "high", "low", "close", "volume", "amount"]].tail(400),
            x_timestamp=d["timestamps"].tail(400),
            y_timestamp=pd.Series(y_ts, name="timestamps"),
            pred_len=HORIZON,
        )
    pred_a, pred_b = forecast_one(PAIR_A), forecast_one(PAIR_B)

    # 3. Predicted spread move
    pred_spread_t0 = float(pred_a["close"].iloc[0] - pred_b["close"].iloc[0])
    pred_spread_tH = float(pred_a["close"].iloc[-1] - pred_b["close"].iloc[-1])
    pred_delta = pred_spread_tH - pred_spread_t0
    print(f"\nPredicted spread change over {HORIZON}d: {pred_delta:+.2f}")

    # 4. Decide side
    last_spread = float(a["close"].iloc[-1] - b["close"].iloc[-1])
    z_score = (last_spread - spread.mean()) / spread.std()
    print(f"Current z-score: {z_score:+.2f}")

    # Simple rule: if z>1 and Kronos thinks spread falls, long B/short A
    #              if z<-1 and Kronos thinks spread rises, long A/short B
    if z_score > 1.0 and pred_delta < 0:
        action = f"LONG {PAIR_B} / SHORT {PAIR_A}"
    elif z_score < -1.0 and pred_delta > 0:
        action = f"LONG {PAIR_A} / SHORT {PAIR_B}"
    else:
        action = "HOLD (no mean-reversion signal)"
    print(f">> ACTION: {action}")
```

**Risk note:** Pairs fail when the correlation breaks (one company
merges, scandal, delisting). Always have an exit rule: if the rolling
60-day correlation drops below 0.5, unwind the pair.

#### 4.4 Event-driven reaction

**What it does:** Establishes a "no-event" forecast, lets the event
happen (earnings, Fed, CPI), then runs a second forecast using the
*post-event* price action. The gap between the two is the "surprise"
you can trade.

**Why it works:** Markets move on the *gap* between expectation and
reality. Kronos's pre-event forecast IS the expectation model;
the post-event model captures the new reality. The difference is
the trade.

**Install:**
```bash
pip install kronos-finance[global]
pip install pandas_market_calendars       # NYSE / earnings calendar
```

**Run (manual event workflow):**
```python
import json
from datetime import datetime
from kronos_finance import load_kronos
from kronos_finance.data import load_ohlcv

# 1. BEFORE the event: capture the baseline forecast
EVENT_DATE = "2026-10-30"               # edit to your event
TICKER = "AAPL"

df = load_ohlcv(TICKER, period="1y", interval="1d")
last_ts_before = df["timestamps"].iloc[-1]
y_ts = pd.date_range(last_ts_before, periods=31, freq="1D")[1:]

wrapper = load_kronos(model_id="small", device="cpu")
baseline = wrapper.predict(
    df=df[["open", "high", "low", "close", "volume", "amount"]].tail(400),
    x_timestamp=df["timestamps"].tail(400),
    y_timestamp=pd.Series(y_ts, name="timestamps"),
    pred_len=30,
)
baseline_record = {
    "ticker": TICKER,
    "event": "earnings",                # edit
    "captured_at": datetime.utcnow().isoformat(),
    "predicted_30d_close": float(baseline["close"].iloc[-1]),
}
with open(f"baseline_{TICKER}_{EVENT_DATE}.json", "w") as f:
    json.dump(baseline_record, f, indent=2)
print(f"Saved baseline to baseline_{TICKER}_{EVENT_DATE}.json")
print(f"  Pre-event predicted 30d close: ${baseline_record['predicted_30d_close']:.2f}")

# 2. WAIT FOR THE EVENT. Then run the same script with the same args.
#    Kronos will see new price action and produce a NEW forecast.
# 3. Compare the two:
#    new_record = {...}
#    surprise_pct = (new_record["predicted_30d_close"]
#                     - baseline_record["predicted_30d_close"]) / last * 100
#    if surprise_pct > +5%: market is more bullish than expected -> LONG
#    if surprise_pct < -5%: market is more bearish -> SHORT or reduce
```

For an automated approach, use `pandas_market_calendars` to know when
US equities are closed for events, then run this script daily.

#### 4.5 Crypto funding-rate arbitrage

**What it does:** Perpetual futures charge "funding" every 8 hours —
longs pay shorts when funding is positive (the contract is priced
above spot). Kronos's predicted direction can confirm or contradict
the funding-implied direction. When they disagree, you trade.

**Why it works:** Funding rates embed market sentiment (longs are
willing to pay shorts for leverage). Kronos gives an independent
direction signal. Combining the two lets you fade extreme funding
when Kronos disagrees, with the funding payment compensating you
while you wait.

**Install:**
```bash
pip install kronos-finance[crypto]      # installs ccxt
```

**Run:**
```python
import ccxt
import pandas as pd
from kronos_finance import load_kronos
from kronos_finance.data import load_ohlcv

SYMBOL = "BTC/USDT"
exchange = ccxt.binance({"enableRateLimit": True})

# 1. Get current funding rate (positive = longs pay shorts)
funding = exchange.fetch_funding_rate(SYMBOL)
rate_pct = float(funding["fundingRate"]) * 100
print(f"Current funding rate: {rate_pct:+.4f}% per 8h "
      f"({rate_pct * 3 * 365:+.1f}% annualized)")

# 2. Get Kronos's predicted direction
wrapper = load_kronos(model_id="small", device="cpu")
df = load_ohlcv(SYMBOL, source="ccxt", exchange="binance",
                interval="5m", limit=500)
y_ts = pd.date_range(df["timestamps"].iloc[-1], periods=121, freq="5min")[1:]
pred = wrapper.predict(
    df=df[["open", "high", "low", "close", "volume", "amount"]].tail(400),
    x_timestamp=df["timestamps"].tail(400),
    y_timestamp=pd.Series(y_ts, name="timestamps"),
    pred_len=120, T=1.0, top_p=0.9,
)
last_close = float(df["close"].iloc[-1])
expected = (float(pred["close"].iloc[-1]) - last_close) / last_close * 100
print(f"Kronos expected 10h move: {expected:+.2f}%")

# 3. Decision matrix
if rate_pct > 0.01 and expected < -0.5:
    print(">> Funding is POSITIVE (longs over-leveraged), Kronos is BEARISH.")
    print(">> ACTION: SHORT the perp, LONG spot to delta-hedge.")
    print("            Collect funding every 8h until Kronos turns bullish.")
elif rate_pct < -0.01 and expected > 0.5:
    print(">> Funding is NEGATIVE (shorts over-leveraged), Kronos is BULLISH.")
    print(">> ACTION: LONG the perp, SHORT spot to delta-hedge.")
    print("            Collect funding until Kronos turns bearish.")
else:
    print(">> No edge: funding and Kronos agree. Skip.")
```

**Risk note:** Funding-rate arb involves both legs moving. If the
perp dumps and the spot doesn't, your hedge ratio drifts. Re-hedge
every hour or use a market-neutral rebalancer. Also: exchange fees
on the spot leg eat into the funding collection.

#### 4.6 Slack / Discord / Telegram signal bot

**What it does:** Posts daily forecast summaries to a private chat
channel. Read-only — no trading. Useful as a "second opinion" or as
a notification system before you trade manually.

**Why it works:** Decisions made in isolation are worse than
decisions made with a daily nudge. Even a 30-line bot changes
behavior because it forces the model output into a human-readable
summary at a consistent time each day.

**Install:**
```bash
pip install kronos-finance[global]
pip install slack-sdk                    # for Slack
pip install discord.py                   # for Discord
pip install python-telegram-bot          # for Telegram
```

**Slack example (read the [Slack docs](https://api.slack.com/messaging/webhooks)
on webhooks first):**
```python
# 1. Create an Incoming Webhook in your Slack workspace.
#    Settings -> Apps -> Incoming Webhooks -> Add to Slack.
#    Copy the webhook URL.
import os
from slack_sdk.webhook import WebhookClient
from kronos_finance import load_kronos
from kronos_finance.data import load_ohlcv

slack_url = os.environ["SLACK_WEBHOOK_URL"]   # never commit this
client = WebhookClient(slack_url)

WATCHLIST = ["AAPL", "MSFT", "NVDA"]
wrapper = load_kronos(model_id="small", device="cpu")

def fmt(ticker):
    df = load_ohlcv(ticker, period="1y", interval="1d")
    y_ts = pd.date_range(df["timestamps"].iloc[-1], periods=31, freq="1D")[1:]
    pred = wrapper.predict(
        df=df[["open", "high", "low", "close", "volume", "amount"]].tail(400),
        x_timestamp=df["timestamps"].tail(400),
        y_timestamp=pd.Series(y_ts, name="timestamps"),
        pred_len=30,
    )
    last = float(df["close"].iloc[-1])
    p30 = float(pred["close"].iloc[-1])
    ret = (p30 - last) / last * 100
    arrow = "🟢" if ret > 0 else "🔴"
    return f"{arrow} *{ticker}*: ${last:.2f} → ${p30:.2f} ({ret:+.2f}% in 30d)"

message = "*Kronos daily forecast*\n" + "\n".join(fmt(t) for t in WATCHLIST)
client.send_text(text=message)
```

The cron entry (Linux/Mac):
```cron
0 8 * * 1-5  cd /home/user/kronos-finance && \
            /usr/bin/python3 daily_signal_post.py
```

For Windows Task Scheduler: same pattern, point at `python.exe` and
`daily_signal_post.py`. For GitHub Actions schedule: see
`examples/19_cronjob_daily_forecast.py` for the lockfile + JSONL-log
pattern that translates directly.

**Bot safety:** never put trade execution logic in the same bot as
the Slack poster. Run them in separate processes with separate
credentials. If the Slack token leaks, the worst case is "spammer
posts bad forecasts to your private channel."

#### 4.7 Risk dashboard for held positions

**What it does:** Runs Kronos against every position you currently
hold and alerts if the predicted 30-day drawdown exceeds your
pre-set loss tolerance. No trading — pure early warning.

**Why it works:** Most losses come from holding a position through
a regime change. Kronos sees the regime shift in price + volume
*before* the news does. The signal isn't "sell" — it's "re-evaluate
this position."

**Install:**
```bash
pip install kronos-finance[global]
```

**Run:**
```python
# positions.csv format: ticker,shares,cost_basis
# AAPL,100,150.00
# NVDA,50,400.00
import pandas as pd
from kronos_finance import load_kronos
from kronos_finance.data import load_ohlcv

positions = pd.read_csv("positions.csv")
LOSS_TOLERANCE_PCT = 15                    # alert if predicted drawdown > this
wrapper = load_kronos(model_id="small", device="cpu")

alerts = []
for _, row in positions.iterrows():
    ticker, shares, cost = row["ticker"], int(row["shares"]), float(row["cost_basis"])
    df = load_ohlcv(ticker, period="1y", interval="1d")
    y_ts = pd.date_range(df["timestamps"].iloc[-1], periods=31, freq="1D")[1:]
    pred = wrapper.predict(
        df=df[["open", "high", "low", "close", "volume", "amount"]].tail(400),
        x_timestamp=df["timestamps"].tail(400),
        y_timestamp=pd.Series(y_ts, name="timestamps"),
        pred_len=30,
    )
    worst_predicted = float(pred["low"].min())
    worst_drawdown_pct = (worst_predicted - cost) / cost * 100
    current_pnl = (float(df["close"].iloc[-1]) - cost) / cost * 100

    if worst_drawdown_pct < -LOSS_TOLERANCE_PCT:
        alerts.append({
            "ticker": ticker,
            "shares": shares,
            "current_pnl_pct": round(current_pnl, 2),
            "predicted_30d_worst_drawdown_pct": round(worst_drawdown_pct, 2),
            "action": "REVIEW POSITION",
        })
    else:
        print(f"  {ticker:6s}: current P&L {current_pnl:+.2f}%, "
              f"predicted worst 30d drawdown {worst_drawdown_pct:+.2f}% — OK")

if alerts:
    print("\n!!! POSITIONS NEED REVIEW !!!")
    for a in alerts:
        print(f"  {a['ticker']}: {a['predicted_30d_worst_drawdown_pct']:+.2f}% "
              f"predicted drawdown vs {a['current_pnl_pct']:+.2f}% current P&L")
else:
    print("\nAll positions within predicted risk tolerance.")
```

Pipe the `alerts` list to your Slack webhook from 4.6 above for a
real-time risk dashboard.

#### 4.8 Macro overlay / market-timing filter

**What it does:** Runs Kronos once a day on broad indexes (SPY,
QQQ, BTC, gold ETF). When the *aggregate* expected return across
all of them is negative, raise cash. When positive, be fully invested.

**Why it works:** Markets cluster — when broad indexes sell off,
most stocks sell off with them. A single-market signal can shield
a long-only portfolio from the worst drawdowns. It's not fancy,
it's not high-Sharpe, but it's historically robust and easy to
implement.

**Install:**
```bash
pip install kronos-finance[global]
```

**Run:**
```python
from kronos_finance import load_kronos
from kronos_finance.data import load_ohlcv

UNIVERSE = ["SPY", "QQQ", "IWM", "GLD"]    # broad-market ETFs
THRESHOLD_PCT = 1.0                       # raise cash if aggregate < +1%
wrapper = load_kronos(model_id="small", device="cpu")

expected_returns = []
for ticker in UNIVERSE:
    df = load_ohlcv(ticker, period="1y", interval="1d")
    y_ts = pd.date_range(df["timestamps"].iloc[-1], periods=31, freq="1D")[1:]
    pred = wrapper.predict(
        df=df[["open", "high", "low", "close", "volume", "amount"]].tail(400),
        x_timestamp=df["timestamps"].tail(400),
        y_timestamp=pd.Series(y_ts, name="timestamps"),
        pred_len=30,
    )
    last = float(df["close"].iloc[-1])
    p30 = float(pred["close"].iloc[-1])
    expected_returns.append((p30 - last) / last * 100)

aggregate = sum(expected_returns) / len(expected_returns)
print(f"Aggregate 30d expected return across {UNIVERSE}: {aggregate:+.2f}%")

if aggregate > THRESHOLD_PCT:
    print(">> MACRO SIGNAL: bullish. Be long.")
elif aggregate < -THRESHOLD_PCT:
    print(">> MACRO SIGNAL: bearish. Raise cash or hedge.")
else:
    print(">> MACRO SIGNAL: neutral. No action.")
```

Run this daily and act on the signal. Add a "no-trade zone"
(aggregate in (-1%, +1%)) to avoid over-trading.

#### 4.9 News corroboration with FinBERT

**What it does:** Pulls today's news for a ticker, runs sentiment
analysis with FinBERT, and combines it with Kronos's expected
return. When both agree, conviction is high. When they disagree,
the model output is suspect.

**Why it works:** Models fail differently. Kronos fails on regime
changes it hasn't seen; FinBERT fails on sarcasm or unusual
phrasing. Two independent signals are more reliable than one,
*especially* when the signal *disagrees*.

**Install:**
```bash
pip install kronos-finance[global]
pip install transformers torch
pip install newsapi-python                # news API client
# export NEWSAPI_KEY=...
```

**Run:**
```python
import os
import pandas as pd
from transformers import pipeline
from newsapi import NewsApiClient
from kronos_finance import load_kronos
from kronos_finance.data import load_ohlcv

newsapi = NewsApiClient(api_key=os.environ["NEWSAPI_KEY"])
finbert = pipeline("sentiment-analysis",
                   model="ProsusAI/finbert",
                   tokenizer="ProsusAI/finbert")

TICKER = "AAPL"
QUERY = TICKER if TICKER != "BTC/USDT" else "bitcoin"

# 1. News sentiment
articles = newsapi.get_everything(q=QUERY, language="en",
                                  page_size=20, sort_by="relevancy")
scores = []
for a in articles["articles"]:
    text = (a["title"] or "") + ". " + (a["description"] or "")
    if text.strip():
        r = finbert(text[:512])[0]                  # FinBERT caps at 512 tokens
        # Map to signed score: +1 positive, -1 negative, 0 neutral
        sign = 1.0 if r["label"] == "positive" else (-1.0 if r["label"] == "negative" else 0.0)
        scores.append(sign * r["score"])
news_signal = sum(scores) / len(scores) if scores else 0.0
print(f"FinBERT news signal ({len(scores)} articles): {news_signal:+.3f}")

# 2. Kronos signal
wrapper = load_kronos(model_id="small", device="cpu")
df = load_ohlcv(TICKER, period="1y", interval="1d")
y_ts = pd.date_range(df["timestamps"].iloc[-1], periods=31, freq="1D")[1:]
pred = wrapper.predict(
    df=df[["open", "high", "low", "close", "volume", "amount"]].tail(400),
    x_timestamp=df["timestamps"].tail(400),
    y_timestamp=pd.Series(y_ts, name="timestamps"),
    pred_len=30,
)
last = float(df["close"].iloc[-1])
p30 = float(pred["close"].iloc[-1])
kronos_signal = (p30 - last) / last * 100
print(f"Kronos 30d signal: {kronos_signal:+.2f}%")

# 3. Combine
def bucket(x, bull=2.0, bear=-2.0):
    if x > bull:  return "BULLISH"
    if x < bear:  return "BEARISH"
    return "NEUTRAL"

k = bucket(kronos_signal)
n = bucket(news_signal * 100)              # scale FinBERT to comparable magnitude
print(f"\nKronos: {k}  |  News: {n}")

if k == n and k != "NEUTRAL":
    print(">>> BOTH AGREE — HIGH CONVICTION trade.")
elif k != "NEUTRAL" and n != "NEUTRAL" and k != n:
    print(">>> CONFLICT — investigate before trading (regime change likely).")
else:
    print(">>> No actionable signal.")
```

**Risk note:** News APIs have rate limits. `newsapi` free tier
caps at 100 requests/day; for production use, cache the day's
articles and only re-query on ticker change.

#### 4.10 Multi-signal ensemble

**What it does:** Combines Kronos with *every* other signal in
this section into a single weighted score per ticker per day. The
weighting is the art — start with equal weights and let
walk-forward validation tune them.

**Why it works:** Ensemble methods beat single models in nearly
every domain, finance included. Kronos is one of many inputs;
the ensemble averages out the per-model failure modes.

**Install:**
```bash
pip install kronos-finance[global,analysis]
```

**Run:**
```python
# Wire all the signals from sections 4.1 - 4.9 into a single daily score.
# The pattern is identical each time:
#   1. Run the signal logic on the same ticker / same day.
#   2. Map the output to a signed number in [-1, +1].
#   3. Multiply by a weight; sum across signals.
#   4. Trade when the weighted sum crosses a threshold.

import pandas as pd
from kronos_finance import load_kronos
from kronos_finance.data import load_ohlcv

WEIGHTS = {
    "kronos_direction":    0.30,    # 4.1 / standard signal
    "kronos_vol_sizing":   0.15,    # 4.1 / Sharpe-like ratio
    "options_iv_edge":     0.15,    # 4.2
    "pairs_signal":        0.10,    # 4.3
    "macro_overlay":       0.15,    # 4.8
    "news_sentiment":      0.15,    # 4.9
}
TICKER = "AAPL"

# Compute each component (pseudo-code — wire the sections above):
components = {
    "kronos_direction":    0.40,    # +0.40 = +4% expected return normalized
    "kronos_vol_sizing":   0.20,
    "options_iv_edge":     -0.30,   # market is pricing more vol than Kronos
    "pairs_signal":        0.50,
    "macro_overlay":       0.60,
    "news_sentiment":      0.20,
}
ensemble_score = sum(WEIGHTS[k] * v for k, v in components.items())
print(f"Ensemble score for {TICKER}: {ensemble_score:+.3f}")

if ensemble_score > 0.30:
    print(">>> Strong ensemble signal: consider long.")
elif ensemble_score < -0.30:
    print(">>> Strong ensemble signal: consider short / reduce.")
else:
    print(">>> Weak or mixed: hold current position.")
```

Use `examples/06_indicators_and_tearsheet.py`'s quantstats HTML
output to compare the ensemble's performance against any single
signal over the same window. The ensemble almost always wins on
risk-adjusted return, but check anyway.

### 5. Live automation patterns

A daily forecast that fires automatically (cron / Task Scheduler) is
the most common production pattern. See
`examples/19_cronjob_daily_forecast.py` for the basics. To go further:

- **Multi-ticker fan-out with retry.** Wrap each forecast in
  try/except + exponential backoff. One bad ticker (rate-limited,
  delisted) should not stop the rest.
- **Slack/email on anomaly.** If a forecast's expected return moves
  >3% from yesterday's prediction, send an alert. The model's
  *change* is often more informative than its *level*.
- **Health checks before orders.** Run the health-check example
  (`examples/17_healthcheck_environment.py`) as a precondition. If
  CUDA is broken or HF cache is corrupt, halt the auto-trader.
- **Audit log every order.** Persist *every* order decision (input
  DataFrame, prediction, signal logic, sizing, timestamp, account
  state) to a JSONL file. When (not if) you need to debug a bad day,
  the audit log is the only way.
- **Daily-loss circuit breaker.** Track cumulative P&L for the day;
  if it's worse than `-X%` of starting equity, halt the bot for the
  rest of the day. This is more important than the strategy itself.
- **Idempotency keys.** Network retries can submit duplicate orders.
  Use idempotency keys (Alpaca, IBKR both support them) keyed on
  `(ticker, date, side, qty)`.
- **Read-only mode first.** For the first 30 days, run the bot with
  execution disabled — log what it *would* have done. Compare against
  your paper broker's actual fill prices. Only enable execution when
  the read-only logs match the broker.

### 6. Tools that pair well with Kronos

You don't need to write everything from scratch. These composable
pieces each fill a gap.

- **`vectorbt.pro`** — paid, but the only Python backtester that runs
  a 10-year tick-level crypto strategy in seconds. Worth it for
  serious sweeps.
- **`quantstats`** — already a dep of `[analysis]`. Pull-lev, factor
  analysis, HTML tearsheets. Use it to compare strategy variants.
- **`riskfolio-lib`** — portfolio optimization (HRP, mean-variance,
  Black-Litterman). Combine Kronos expected returns with
  riskfolio-lib's covariance model for a complete portfolio
  construction stack.
- **`empyrical`** (deprecated) / **`pyfolio-reloaded`** — risk and
  performance metrics.
- **`pandas-ta`** — already a dep. ~130 indicators. Don't reinvent
  RSI / MACD / Bollinger / ATR / OBV.
- **`yfinance`** — already a dep. Free OHLCV for US/CN/HK tickers
  with no API key.
- **`akshare`** — already a dep. CN A-share data.
- **`ccxt`** — already a dep. 100+ crypto exchanges.
- **`alpaca-trade-api` / `ib_insync` / `binance`** — broker APIs as
  listed above.
- **`great-expectations`** — schema validation on input data. Catches
  "today's data has NaN, the model silently predicted NaN, and the
  bot bought 10,000 shares of garbage." Cheap insurance.
- **`pydantic`** — already a dep. Validate signal outputs before
  they hit the broker.
- **`apscheduler` / `croniter`** — robust scheduling if you don't want
  raw cron.

### 7. What "good" looks like

After 6 months of running a Kronos-based strategy on a paper account,
expect:

- **Win rate**: 50-60% on daily-bar direction. Higher than random but
  lower than naive backtests suggest.
- **Sharpe ratio**: 0.5-1.5 after costs, if you've tuned the signal
  logic well. Realistic, not glamorous.
- **Max drawdown**: 10-25% on the underlying, regardless of strategy.
  The model doesn't prevent drawdowns; it filters which *direction*
  you take them.
- **Turnover**: 1-5 trades/day across a 10-ticker watchlist if you
  retrain daily. Costs matter: even $0.005/share adds up at 1k
  shares/day.
- **Drift**: the model's accuracy will *slowly degrade* over months
  as market regime shifts. Plan to re-evaluate the signal logic
  quarterly.

If your backtest claims >2.0 Sharpe, >70% win rate, and <5% drawdown
on daily bars, it has a bug. Go find it.

### 8. Where to learn more

- `examples/05_batch_predict.py` — multi-ticker sweep
- `examples/06_indicators_and_tearsheet.py` — indicators + quantstats
- `examples/14_quantile_bands.py` — distribution-aware sizing
- `examples/19_cronjob_daily_forecast.py` — daily automation
- `examples/21_portfolio_kronos_weighting.py` — three weighting schemes
- Backtesting frameworks: see the table above; start with
  `backtesting.py` for sanity checks, then `vectorbt` for sweeps,
  then `backtrader` for realism.
- Live execution: start with **Alpaca paper trading** (free API key,
  5-minute setup) before touching any real-money broker.

And again: this is research software. Don't risk what you can't afford
to lose. The first version of your strategy should run in paper mode
for at least 30 trading days before you ever let it touch a live
account.

## Troubleshooting

The most common pitfalls — full list in [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md):

- **`error: externally-managed-environment`** — you're trying to `pip install` into
  system Python. Use a venv (see the section at the top of this README).
- **Model download stalls / 401 from HuggingFace** — set `HF_TOKEN` or run
  `huggingface-cli login`.
- **CUDA version mismatch** — install PyTorch from the matching CUDA index URL.
- **yfinance rate limit** — switch to `auto` source or add `period=` to limit.
- **AKShare returns empty** — the ticker may have delisted. Try yfinance with
  `600519.SS`.
- **Playwright browser missing** — `playwright install --with-deps chromium`.
- **`max_context` exceeded** — reduce `lookback` or use `Kronos-mini` (2048 context).
- **Tz-aware timestamp warning** — convert to UTC and drop the tz before passing in.
- **PermissionError on Windows** — run your terminal as Administrator or use a venv.

## Hardware (CPU vs CUDA)

Kronos runs on **both CPU and CUDA GPUs**. The default `pip install kronos-finance`
installs the standard PyPI `torch` wheel, which is **CUDA-enabled by default**
and works on either. There's no separate "GPU version" of `kronos-finance`.

You pick the device at runtime via `--device` (CLI) or `device=` (Python):

```bash
kronos predict AAPL --device cpu       # default; works everywhere
kronos predict AAPL --device cuda     # first GPU
kronos predict AAPL --device cuda:0   # specific GPU
```

```python
wrapper = load_kronos(model_id="small", device="cuda")
```

### Which device should I use?

| Model | Params | CPU latency (30-step forecast) | CUDA latency | Recommendation |
|---|---|---|---|---|
| `Kronos-mini`  | 4.1M   | ~2s   | ~0.2s | Either works |
| `Kronos-small` | 24.7M  | ~5s   | ~0.3s | Either works |
| `Kronos-base`  | 102.3M | ~15s  | ~1s   | CUDA recommended |

### Memory

- **CPU**: ~1 GB RAM for `small`, +500 MB for `base`.
- **CUDA**: ~1 GB VRAM for `small`, ~2 GB VRAM for `base`. An RTX 3060 (12 GB) is plenty.

### Pinning your CUDA version

If you have an NVIDIA GPU, install PyTorch from the matching CUDA index URL
**before** installing kronos-finance to control which CUDA toolkit version is
bundled:

```bash
# CUDA 12.1 — match your NVIDIA driver
pip install torch --index-url https://download.pytorch.org/whl/cu121
pip install kronos-finance[global]

# CUDA 11.8
pip install torch --index-url https://download.pytorch.org/whl/cu118
pip install kronos-finance[global]

# CPU-only (smaller download, ~200 MB instead of ~800 MB)
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install kronos-finance[global]
```

### Apple Silicon (M1/M2/M3)

`pip install kronos-finance` on Apple Silicon uses PyTorch's MPS backend
automatically. Pass `--device mps` in the CLI or `device="mps"` in the API.

### Common CUDA errors

- **`CUDA error: no kernel image is available`** — your PyTorch CUDA version
  doesn't match your NVIDIA driver. Reinstall PyTorch from the matching index
  URL above.
- **`CUDA out of memory`** — your GPU is too small. Use `Kronos-mini` or
  reduce `lookback`. On CPU there's no such limit (just slower).
- **`CUDA unavailable but requested`** — your install doesn't have CUDA
  support, or no GPU is visible. `nvidia-smi` to check.

## How it works

```
Ticker (e.g. "AAPL")
    │
    ▼  load_ohlcv() auto-detects source -> yfinance
pd.DataFrame [timestamps, open, high, low, close, volume, amount]
    │
    ▼  load_kronos() downloads Kronos-small from HuggingFace
KronosWrapper (model + tokenizer + predictor)
    │
    ▼  wrapper.predict() runs autoregressive Transformer
pd.DataFrame [predicted OHLCV]
    │
    ▼  analysis: indicators + backtest -> HTML tearsheet
```

## Performance & limits

| Setting | Default | Cap | Note |
|---|---|---|---|
| `pred_len` | 30 | 1000 (CLI), no cap in API | Larger = slower inference |
| `lookback` | 400 | 512 (small/base/large), 2048 (mini) | Auto-truncated |
| `sample_count` | 1 | 20 | Quantile bands need >1 |
| Memory (CPU, small) | ~1GB | — | +500MB for base |
| Memory (CUDA, base) | ~2GB VRAM | — | RTX 3060+ recommended |
| Latency (CPU, 30-step, 1 ticker) | ~5s | — | ~1s on CUDA |

## Development

```bash
git clone https://github.com/lordxmen2k/kronos-finance.git
cd kronos-finance
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,test,ui]"
pytest                         # unit + CLI tests
ruff check src/                # lint
```

See [`docs/CONTRIBUTING.md`](docs/CONTRIBUTING.md).

## Citation

If you use this in research, please cite the original Kronos paper:

```bibtex
@inproceedings{kronos2026,
  title  = {Kronos: A Foundation Model for the Language of Financial Markets},
  author = {Shi, Yu and others},
  booktitle = {AAAI},
  year   = {2026},
}
```

And this wrapper:

```bibtex
@software{kronos_finance,
  author = {lordxmen2k},
  title  = {kronos-finance: A Python wrapper for Kronos},
  year   = {2026},
  url    = {https://github.com/lordxmen2k/kronos-finance}
}
```

## License

MIT — see [`LICENSE`](LICENSE) for the full text. The original Kronos project is also
MIT; see `src/kronos_finance/_vendor/LICENSE_KRONOS` for the vendored upstream license.

## Acknowledgements

- The [NeoQuasar team](https://github.com/shiyu-coder/Kronos) for the Kronos
  foundation model.
- [HuggingFace](https://huggingface.co/) for model hosting.
- [AKShare](https://github.com/akfamily/akshare), [yfinance](https://github.com/ranaroussi/yfinance),
  [CCXT](https://github.com/ccxt/ccxt), [Qlib](https://github.com/microsoft/qlib) — the
  data layer.
- [pandas-ta](https://github.com/twopirllc/pandas-ta), [quantstats](https://github.com/ranaroussi/quantstats) — analysis.
- [Plotly](https://plotly.com/), [Flask](https://flask.palletsprojects.com/) — the dashboard.

## Glossary

See [`docs/INSTALL.md#glossary`](docs/INSTALL.md#glossary) for the full 35-term glossary.
A quick index of the most important ones:

- **OHLCV** — Open, High, Low, Close, Volume. The five columns Kronos expects.
- **K-line** — Chinese term for candlestick; same thing.
- **Context length / max_context** — maximum past bars the model can see (512 for
  small/base/large; 2048 for mini).
- **AR / autoregressive** — generates outputs one step at a time.
- **Tokenizer** — converts continuous OHLCV to discrete tokens before the Transformer.
- **Sample count** — number of forecast paths to draw; more = smoother quantile band.
- **Tearsheet** — one-page performance report; quantstats generates HTML.
- **Nucleus sampling (top-p)** — sampling from smallest token set whose cumulative prob ≥ p.
- **Temperature (T)** — sampling temperature; T<1 conservative, T>1 exploratory.
- **Quantile band** — uncertainty interval drawn when `sample_count > 1`.
- **PEP 668** — Python spec marking system Python as externally managed; use venv.
- **Twine** — `twine upload dist/*` publishes to PyPI.
- **Wheel (.whl)** — built distribution format.
