Metadata-Version: 2.4
Name: naga-runtime
Version: 1.2.3
Summary: Run trained NAGA trading models (.safetensors) live from MetaTrader 5, from Python, or over a secure JSON HTTP API with a built-in dashboard.
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: numpy>=1.26
Requires-Dist: pandas>=2.1
Requires-Dist: torch>=2.2
Provides-Extra: secure
Requires-Dist: cryptography>=42; extra == "secure"
Provides-Extra: mt5
Requires-Dist: MetaTrader5>=5.0.45; platform_system == "Windows" and extra == "mt5"
Provides-Extra: all
Requires-Dist: cryptography>=42; extra == "all"
Requires-Dist: MetaTrader5>=5.0.45; platform_system == "Windows" and extra == "all"

# naga-runtime

**Run trained NAGA trading models live from MetaTrader 5, from Python, or from any web dashboard over a secure JSON API — with a built-in live dashboard.**

`naga-runtime` is the distribution package for models produced by the NAGA-HGT training system. It takes a model
folder (`model.safetensors` + `config.json`), accepts ordinary market data (OHLC bars, news headlines, economic
calendar events, account state) and returns a complete, structured decision for **XAUUSD, EURUSD and GBPUSD**:
action, confidence, position size, stop-loss / take-profit, maximum holding time, and multi-horizon price forecasts.

It contains no training code and never asks for API keys of any kind. A broker connection is optional: live mode reads
MetaTrader 5 **read-only** (it never places, modifies or closes orders).

```powershell
pip install ".[all]"
naga-runtime live C:\path\to\NAGA_model --open      # MT5 + news + calendar → model → dashboard in your browser
```

> **Risk notice.** Model outputs are statistical estimates, not financial advice. Trading leveraged products carries
> a high risk of loss. Validate every model on a demo account before risking capital.

---

## Contents

1. [Features](#features)
2. [Installation](#installation)
3. [Live mode (MetaTrader 5)](#live-mode-metatrader-5)
4. [Quick start (Python)](#quick-start-python)
5. [Model folder](#model-folder)
6. [Input reference](#input-reference)
7. [Output reference](#output-reference)
8. [Command-line interface](#command-line-interface)
9. [HTTP API for dashboards](#http-api-for-dashboards)
10. [Client libraries](#client-libraries)
11. [Security](#security)
12. [Performance](#performance)
13. [Troubleshooting](#troubleshooting)
14. [Versioning and compatibility](#versioning-and-compatibility)

---

## Features

| | |
|---|---|
| **One-line loading** | `NagaModel.load("NAGA_model")` — CPU or GPU selected automatically. |
| **Flexible input** | pandas DataFrames, dicts of lists, or plain row lists; epoch seconds, milliseconds, ISO strings or `datetime`. |
| **Complete output** | Action + probabilities, confidence, lots, SL/TP (distance and price), max hold, forecasts for 5 / 15 / 60 / 240 minutes, expected favourable/adverse excursion, volatility outlook, market-session status. |
| **Identical to training** | The architecture, features and graph builder are generated from the training code; a parity test proves bit-identical outputs. |
| **No lookahead by design** | Bars, news and events after the decision time are ignored automatically; future economic "actual" values are hidden. |
| **Secure distribution** | SHA-256 integrity manifest, optional AES-256-GCM encryption of the weights (decrypted in memory only). |
| **Live mode in one command** | `naga-runtime live MODEL` reads bars, quotes, account and positions from MetaTrader 5 (read-only), free RSS headlines and the economic calendar, and re-decides on every bar close, high-impact headline, sharp price move or position change. |
| **Built-in dashboard** | Open `http://localhost:8000` — live decisions, probabilities, trade plan, forecasts, upcoming events and headlines; light and dark themes; works on phones. |
| **Dashboard-ready API** | Bearer-token auth, CORS allow-list, rate limiting, size limits, live streaming (Server-Sent Events), no stack traces leaked. |
| **Client libraries** | `NagaClient` for Python and `clients/naga-client.mjs` for browsers / Node.js — `info`, `latest`, `predict`, `stream`. |
| **Low latency** | Bar features are computed once per new bar; quote/account refreshes cost a single forward pass. Model warm-up at start. |
| **Built-in guards** | New entries are blocked during abnormal spreads (> 3× normal) and after a 10 % daily loss, mirroring the live trading engine. |
| **Zero extra services** | Pure Python + PyTorch. No database, no message queue, no web framework required. |
| **Cross-platform** | Windows, Linux and macOS; Python 3.10 – 3.13 (MetaTrader 5 live mode: Windows). |

---

## Installation

```bash
python -m venv .venv
# Windows (PowerShell):  .\.venv\Scripts\Activate.ps1
# Linux / macOS:          source .venv/bin/activate

# 1) PyTorch — pick the build for your machine (https://pytorch.org/get-started/locally/)
pip install torch --index-url https://download.pytorch.org/whl/cu128   # NVIDIA GPU
# pip install torch                                                     # CPU only

# 2) naga-runtime (from this folder, or from a wheel you received)
pip install ".[all]"          # everything: encryption + MetaTrader 5 (Windows)
# or pick:  pip install .   |   pip install ".[secure]"   |   pip install ".[mt5]"
```

Check the computer and the model in one step:

```bash
naga-runtime doctor path/to/NAGA_model
```

`doctor` reports Python, PyTorch, GPU, optional packages, the MetaTrader 5 terminal, the news and calendar feeds, the
model files (integrity + load) and the decision latency, with a fix for every problem it finds.

---

## Live mode (MetaTrader 5)

1. Open MetaTrader 5 and log in (demo account recommended).
2. Start the runtime:

```powershell
$env:NAGA_API_TOKEN = "choose-a-long-random-secret"      # optional; otherwise a token is generated and printed
naga-runtime live C:\path\to\NAGA_model --open
```

3. The dashboard opens in your browser — paste the token once.

What live mode does:

| Step | Detail |
|---|---|
| Market data | M5 / M15 / H1 history (exactly what the model needs), live bid/ask, account equity and open positions from MT5. Broker server time is converted to UTC automatically. |
| Symbols | Detected automatically, including broker suffixes (`XAUUSDm`, `EURUSD.r`, `GOLD`). Override with `--symbols XAUUSD=XAUUSDm,EURUSD=EURUSDm,GBPUSD=GBPUSDm`. |
| News | Free public RSS feeds (central banks, financial news), filtered to USD / EUR / GBP / gold. Refreshed every 2 minutes. |
| Calendar | This week's economic calendar (ForexFactory public feed), refreshed every 30 minutes. |
| Decisions | On every M5 bar close, high-impact headline, price move ≥ 0.8 × ATR, position change, and every `--refresh` seconds (default 30). |
| Account tracking | Peak and start-of-day equity are remembered across restarts (`runtime_state.json` in the model folder). |
| Output | Dashboard at `/`, `GET /v1/latest`, live stream `GET /v1/stream`, optional `--journal predictions.jsonl`. |
| Resilience | Reconnects to MT5 automatically; news/calendar outages never stop decisions; errors appear in `/v1/status`. |

Settings can also come from a `.env` file in the current folder (the same file the training system uses). Only these
keys are read: `NAGA_*`, `MT5_LOGIN`, `MT5_PASSWORD`, `MT5_SERVER`, `MT5_TERMINAL_PATH`, `SYMBOL_MAP`, `RSS_FEEDS`.
`MT5_*` are needed only to log in to a specific account; otherwise the open terminal is used.

Useful options: `--port 8000`, `--refresh 30`, `--no-news`, `--no-calendar`, `--journal FILE`, `--floor 10`,
`--utc-offset 3` (only if automatic server-time detection is wrong), `--device cpu`.

Windows shortcut: `scripts\start-live.ps1 -Model C:\path\to\NAGA_model`.

Live mode in your own Python code:

```python
from naga_runtime import NagaModel
from naga_runtime.feeds import MT5Feed, NewsFeed, CalendarFeed
from naga_runtime.live import LiveRunner

runner = LiveRunner(NagaModel.load("NAGA_model"), MT5Feed(), NewsFeed(), CalendarFeed())
runner.subscribe(lambda p: print(p.decisions["XAUUSD"].action, p.decisions["XAUUSD"].confidence))
runner.run_forever()
```

---

## Quick start (Python)

```python
from naga_runtime import NagaModel

model = NagaModel.load("NAGA_model")            # device="auto" → CUDA when available, else CPU

prediction = model.predict(
    bars={
        "XAUUSD": {"M1": xau_m1_df},           # completed bars, oldest → newest
        "EURUSD": {"M1": eur_m1_df},           # M5/M15/H1 are built automatically from M1
        "GBPUSD": {"M1": gbp_m1_df},           # (or pass them explicitly: {"M5": ..., "M15": ..., "H1": ...})
    },
    news=[{"time": "2026-10-30T13:31:00Z", "title": "Fed signals patience on rate cuts"}],
    events=[{"time": "2026-10-30T12:30:00Z", "name": "US CPI YoY", "currency": "USD",
             "impact": "high", "actual": 3.1, "forecast": 3.0, "previous": 2.9}],
    account={"equity": 1000},
)

for symbol, d in prediction.decisions.items():
    print(symbol, d.action, f"{d.confidence:.0%}", d.lots, d.sl_price, d.tp_price)

payload = prediction.to_dict()                  # plain JSON-serialisable dict
```

A complete runnable script is in [`examples/predict_example.py`](examples/predict_example.py).

**Fast refreshes.** `predict()` = `prepare()` + `decide()`. `prepare()` computes the bar features (the expensive part);
`decide()` is one forward pass. Between bar closes, keep the prepared state and call `decide()` with fresh quotes,
account and positions:

```python
state = model.prepare(bars, news, events)                    # once per new M5 bar
pred = model.decide(state, account=acc, positions=pos, quotes=q, now=time.time())   # as often as you like
model.update_context(state, news=latest_news)                # new headlines without recomputing bars
```

---

## Model folder

| File | Purpose |
|---|---|
| `model.safetensors` | Weights in the open safetensors format (contains numbers only — no executable code). |
| `model.safetensors.enc` | Encrypted weights (instead of the file above) — see [Security](#security). |
| `config.json` | Architecture, version, training step, selected confidence threshold and validation results. |
| `manifest.json` | SHA-256 of every file; checked automatically on load. |

The training system writes this folder automatically (`runs/NAGA_model/`) or on demand with
`python naga_hgt.py export`. Only fully trained models (stage `ppo`) are accepted.

---

## Input reference

### `bars` *(required)*

`{"XAUUSD": {...}, "EURUSD": {...}, "GBPUSD": {...}}` — all three symbols are required because the model reasons
across markets (for example, separating US-dollar moves from gold-specific moves).

Each symbol maps timeframe → bars. Supported timeframes: `M1`, `M5`, `M15`, `H1`.

| Column | Required | Aliases | Notes |
|---|---|---|---|
| `time` | ✔ | `t`, `timestamp`, `datetime`, `date` | **Bar open time.** Epoch seconds/milliseconds, ISO-8601 string or `datetime`. Naive times are treated as **UTC**. |
| `open`, `high`, `low`, `close` | ✔ | `o`, `h`, `l`, `c` | Bid prices. |
| `tick_volume` | | `v`, `volume`, `tickvol` | Number of ticks (as provided by MetaTrader). |
| `spread` | | `sp` | **Price units** (e.g. `0.00012` for EURUSD, `0.25` for XAUUSD). Defaults are used when missing. |

Accepted containers: `pandas.DataFrame`, `dict` of equal-length lists/arrays, or a list of rows
`[time, open, high, low, close, tick_volume, spread]`.

**History needed.** The model looks back 2 days of M5 (plus 1 week of M5 for the live cross-market correlation), 4 days of M15 and 20 days of H1. Provide at least
**30 calendar days** (≈ 22 trading days) of M1 or M5 data (or M15/H1 explicitly) for full-quality results. Shorter history still works —
missing context is masked — but `prediction.warnings` will say so.

**Completed bars only.** Pass bars that have closed. When `now` is given, any bar not yet closed at `now` is dropped
automatically.

**MetaTrader server time.** Convert broker server time to UTC before calling the model. Many brokers use
New York time + 7 hours; some (for example Exness) use UTC. `MT5Feed` and live mode do this automatically.

**Broker contract data** *(optional)*: `symbols_info={"XAUUSD": {"point": 0.01, "contract": 100}, ...}` makes lot sizing
use your broker's exact contract sizes. Live mode supplies it automatically.

### `news` *(optional)*

List of headlines: `{"time", "title", "body"?, "source"?, "sentiment"?, "currencies"?, "delayed"?}`.
Any language is accepted (the text encoder reads raw UTF-8 bytes). The most recent 36 items from the last 72 hours
are used; items timestamped after `now` are ignored.

### `events` *(optional)*

Economic calendar: `{"time", "name", "currency", "impact" ("high"|"medium"|"low"|1–3), "actual"?, "forecast"?, "previous"?}`.
Events within ±24 hours are used. For events still in the future, `actual` is ignored even if supplied.

### `account` *(optional)*

`{"equity", "margin"?, "peak_equity"?, "day_start_equity"?, "floor"?}` (USD). With `equity` the response includes
suggested **lots** that respect the equity floor (default 10 USD): if every open position and the new one hit their
stop-loss at once, equity would still stay above the floor.

### `positions` *(optional)*

Open positions per symbol: `{"side": "long"|"short", "lots", "entry", "sl", "tp", "open_time", "max_hold_minutes"?, "mfe"?, "mae"?, "profit"?}`.

### `quotes` *(optional)*

Live prices between bar closes: `{"EURUSD": {"bid": 1.0815, "ask": 1.0816}, ...}`. Improves intra-bar decisions, uses
the real spread for stop placement and entry price (ask for LONG, bid for SHORT), and blocks new entries when the
spread is more than 3× its normal level.

### `now` *(optional)*

Decision time (any accepted time format). Defaults to the close of the latest M5 bar.

---

## Output reference

`predict()` returns a `Prediction`; `prediction.to_dict()` / `to_json()` give plain JSON.

```jsonc
{
  "time": 1793376000,              // decision time (UTC epoch seconds)
  "model_version": "2.3.1",
  "latency_ms": 41.7,
  "bar_time": 1793375700,          // close time of the newest completed M5 bar used
  "warnings": [],                  // data-quality and guard notes (short history, daily loss limit, ...)
  "decisions": {
    "EURUSD": {
      "action": "LONG",            // HOLD | LONG | SHORT | CLOSE
      "confidence": 0.62,          // probability of the chosen action
      "probabilities": {"HOLD": 0.31, "LONG": 0.62, "SHORT": 0.05, "CLOSE": 0.02},
      "tradable": true,            // market open and not within 15 minutes of a close
      "market_note": "",           // e.g. "UK holiday", "rollover", "weekend"
      "last_close": 1.08123, "atr": 0.00031,
      "size_fraction": 0.42,       // 0–1 of the lot cap for the account size
      "lots": 0.02,                // null without account equity, 0 when the floor forbids a trade
      "sl_distance": 0.00084, "tp_distance": 0.00151,
      "sl_price": 1.08039, "tp_price": 1.08274,
      "max_hold_minutes": 95.0,
      "modify_sl_tp": false,       // open position: move SL/TP to the distances above
      "forecasts": [               // 5, 15, 60 and 240 minutes ahead
        {"minutes": 60, "median_price": 1.08141, "low_price": 1.08010, "high_price": 1.08277,
         "median_pct": 0.017, "low_pct": -0.104, "high_pct": 0.142}
      ],
      "volatility_ratio": 1.12,    // expected next-hour volatility ÷ current ATR level
      "mfe_1h": 0.00062, "mae_1h": 0.00041,   // expected best / worst excursion (price units)
      "mfe_4h": 0.00118, "mae_4h": 0.00087,
      "price": 1.08127,            // live mid price (quotes) or last close
      "spread": 0.00011,           // live spread or last bar spread
      "has_position": false
    }
  }
}
```

**Interpreting the decision**

* `HOLD` with no position = stay flat · `LONG`/`SHORT` with an opposite position = close and reverse ·
  `CLOSE` = exit the open position.
* `tradable: false` means new entries are not allowed right now; `market_note` says why (weekend, holiday, rollover,
  abnormally wide spread). After a 10 % loss since the start of the day (`account.day_start_equity`), new entries are
  blocked and a warning is added.
* The model applies a **confidence threshold** selected on validation data during training (`min_confidence`
  in `model.info`): below it, new entries become `HOLD`.
* `lots`, `sl_price` and `tp_price` are suggestions. Your execution layer must still respect broker limits
  (minimum stop level, volume step, margin).

---

## Command-line interface

```text
naga-runtime live     MODEL [--open --symbols --refresh --no-news --no-calendar --journal ...]   real time from MT5
naga-runtime serve    MODEL [--open --host --port --token --cors --rate --no-dashboard]         API + dashboard
naga-runtime doctor   [MODEL]                   check this computer, the feeds and the model
naga-runtime info     MODEL                     model details (version, parameters, validation results)
naga-runtime predict  MODEL request.json        one prediction (same JSON as the HTTP API); "-" reads stdin
naga-runtime bench    MODEL [request.json]      latency: full predict vs. quote refresh (synthetic data by default)
naga-runtime verify   MODEL                     check files against manifest.json
naga-runtime manifest MODEL                     (re)create manifest.json
naga-runtime encrypt  MODEL [--key | NAGA_MODEL_KEY] [--keep-plain]
```

Common options: `--device auto|cpu|cuda|cuda:N`, `--key` (prefer the `NAGA_MODEL_KEY` environment variable),
`--env-file PATH` (default `.env`), `-v` for debug logging.

---

## HTTP API for dashboards

```bash
export NAGA_API_TOKEN="a-long-random-secret"          # PowerShell: $env:NAGA_API_TOKEN="..."
naga-runtime serve NAGA_model --port 8000 --cors https://dashboard.example.com
```

| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/` | — (page) | Built-in live dashboard; its data calls use the token |
| `GET` | `/health` | — | Liveness probe → `{"status": "ok"}` |
| `GET` | `/v1/info` | Bearer | Model information |
| `GET` | `/v1/status` | Bearer | Live runner, MT5/news/calendar status, upcoming events, recent headlines, recent errors |
| `GET` | `/v1/latest` | Bearer | Most recent prediction (`404` until the first one) |
| `GET` | `/v1/history?limit=100` | Bearer | Recent predictions (up to 500) |
| `GET` | `/v1/stream` | Bearer | Server-Sent Events — every new prediction as it is made |
| `POST` | `/v1/predict` | Bearer | Body = the `predict()` arguments as JSON → the Prediction JSON |

Two ways to feed the model:

* **Live mode** (`naga-runtime live`): the runtime collects data itself; your dashboard only reads `latest` / `stream`.
* **Request mode** (`naga-runtime serve`): your system sends market data with `POST /v1/predict`. Each result is also
  published to `latest`, `history` and `stream` (send `"publish": false` to skip that).

Full request/response schema, status codes and a production checklist: [`docs/REST_API.md`](docs/REST_API.md).

---

## Client libraries

**Python** (standard library only — no PyTorch needed on the client machine):

```python
from naga_runtime.client import NagaClient
api = NagaClient("http://127.0.0.1:8000", token="...")        # or NAGA_API_TOKEN
print(api.latest()["decisions"]["XAUUSD"]["action"])
for pred in api.stream():                                        # reconnects automatically
    print(pred["time"], {s: d["action"] for s, d in pred["decisions"].items()})
```

**JavaScript** (browser or Node.js 18+, no dependencies) — copy [`clients/naga-client.mjs`](clients/naga-client.mjs):

```javascript
import { NagaClient } from "./naga-client.mjs";
const api = new NagaClient("http://127.0.0.1:8000", token);
const stop = api.stream((pred) => render(pred), (state) => setBadge(state));   // "live" | "reconnecting" | "unauthorised"
```

**Public websites:** never put the API token in browser code. Run a small backend that holds the token and forwards
results — [`examples/backend_proxy.mjs`](examples/backend_proxy.mjs) is a complete, dependency-free example.

---

## Security

| Concern | Protection |
|---|---|
| Tampered or corrupted model files | SHA-256 manifest verified on every load (`IntegrityError`). |
| Malicious model files | safetensors stores numbers only — loading never executes code (unlike pickle `.pt` files). |
| Model confidentiality | Optional AES-256-GCM encryption; key derived with scrypt; decrypted in memory only. |
| Leaking secrets | Keys and tokens are read from environment variables, never logged, compared in constant time. The `.env` reader ignores every key except the runtime's own. |
| Unauthorised API use | Bearer token on every `/v1` route, localhost binding by default, CORS allow-list, rate limit, stream cap. |
| Dashboard attacks | Strict per-request Content-Security-Policy (nonce), no framing, no third-party scripts, all data rendered as text; the token is never embedded in the page and is kept in tab storage unless "Remember" is ticked. |
| Accidental trading | MT5 access is read-only — the library contains no order functions. |
| Information disclosure | Errors return short messages only; no stack traces, headers or bodies are logged. |

Details and deployment guidance: [`docs/SECURITY.md`](docs/SECURITY.md).

---

## Performance

* Inference runs under `torch.inference_mode()` with automatic mixed precision on CUDA (BF16, or FP16 on older GPUs).
* On CPU, all cores are used (override with `NagaModel.load(..., threads=N)`).
* Bar features are computed once per new bar (`prepare`); refreshes on quotes, account or positions run only the model
  (`decide`). Live mode does this automatically.
* The model is warmed up at start, so the first live decision is as fast as the rest.
* Time columns are parsed vectorised; pass M5 + M15 + H1 (or ≈30 days of M1) — more history only costs time.
* `naga-runtime bench MODEL` reports median and p90 latency for both paths on your hardware.
* `NagaModel` is thread-safe; concurrent requests are serialised on the device. For high throughput, run several
  server processes behind a load balancer.

---

## Troubleshooting

| Message | Fix |
|---|---|
| `missing bars for GBPUSD` | All three symbols are required. |
| `cannot build H1 — provide H1 bars or a finer timeframe` | Supply M1 or M5 (or H1 directly). |
| warning `... needed for full context` | Provide ≥ 30 calendar days of history for full quality. |
| `IntegrityError: ... does not match manifest.json` | The download is corrupted or modified — obtain the model again. |
| `KeyRequiredError` | The model is encrypted: set `NAGA_MODEL_KEY` or pass `key=...`. |
| `only fully trained (PPO) models can make decisions` | Export the model after the PPO stage (`python naga_hgt.py export`). |
| HTTP `401` | Send `Authorization: Bearer <token>` matching `NAGA_API_TOKEN`. |
| Browser CORS error | Start the server with `--cors https://your-dashboard-origin`. |
| `MetaTrader 5 initialize failed` | Open the MT5 terminal and log in first, then start again. Algorithmic trading does **not** need to be enabled (access is read-only). |
| `symbol for XAUUSD not found at this broker` | Pass `--symbols XAUUSD=<your broker name>` (see the Market Watch window). |
| Dashboard shows "Reconnecting…" | The server stopped or the network dropped; it reconnects by itself. Check the terminal window for errors. |
| Times look shifted by hours | Start with `--utc-offset N` (your broker's server UTC offset). |

---

## Versioning and compatibility

* `naga-runtime 1.2.3` loads models exported by NAGA-HGT **2.5** and **2.4** (the variant is detected from `config.json`) — 1.2.3 uses much less VRAM on GPU (efficient attention).
* `naga-runtime 1.2.0` loads models exported by NAGA-HGT **2.4** (dynamic market graph, shared-expert MoE). Models
  exported by NAGA-HGT 2.3 need `naga-runtime 1.1` (`pip install "naga-runtime==1.1.0"`); loading one here stops
  with a clear `IntegrityError` instead of producing wrong numbers.
* The model architecture is fully described by `config.json`; different model sizes (small / medium / base) load
  with the same library version.
* The HTTP API is versioned (`/v1`); 1.x releases only add fields and endpoints.
* Run the test suite with `python tests/test_runtime.py` (set `NAGA_TRAINING_DIR` to the training folder to include
  the bit-exact parity check).
