Metadata-Version: 2.4
Name: atabet-terminal
Version: 0.1.1
Summary: Atabet Terminal: browser-based financial terminal for the Atabet Quant Intelligence line
Author: ATABET LIMITED
Author-email: ATABET LIMITED <info@atabet.com>
Maintainer: ATABET LIMITED
Maintainer-email: ATABET LIMITED <info@atabet.com>
License: Proprietary
Keywords: finance,terminal,charting,quant,trading
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Financial and Insurance Industry
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 :: Office/Business :: Financial
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: fastapi
Requires-Dist: uvicorn
Requires-Dist: pandas
Requires-Dist: numpy
Requires-Dist: pyarrow
Requires-Dist: pydantic
Provides-Extra: auth
Requires-Dist: atabet-token; extra == "auth"
Requires-Dist: atabet-data; extra == "auth"
Requires-Dist: atabet-trader; extra == "auth"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: httpx; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: mypy; extra == "dev"
Requires-Dist: pandas-stubs; extra == "dev"
Dynamic: author
Dynamic: maintainer

# Atabet-Terminal

A browser-based financial terminal for the Atabet Quant Intelligence product line: professional charting, built-in and custom indicators, paper and live trading, strategy visualization, and an in-terminal AI assistant.

**Status: Phases 1–11 complete (skeleton + demo data, licensing gate, real data channels, professional Atabet layout, default indicators, live updates, custom indicator editor, broker Engine, AI chatbox, Quant Intelligence launcher, strategy session).** Packaging polish is Phase 12 of the [build guide](../atabet-demo/enhancements/atabet-terminal/financial_terminal_prototype_build_guide.md). Product spec: [`07-atabet-terminal.md`](../atabet-demo/enhancements/atabet-quant-intelligence/07-atabet-terminal.md). Real channels flow through `atabet-data` and broker access through `atabet-trader` (never a vendor SDK directly).

## Features (current)

- **Demo mode** — deterministic synthetic OHLCV for `DEMO.A` / `DEMO.B` / `DEMO.C` (~20k one-minute bars each), cached as Parquet
- **Bars API** — `/api/bars` with `1m/5m/15m/1h/1D` resampling; all times are UTC Unix seconds (Lightweight Charts contract). Search and source chips map a typed name onto the channel-native ticker (`700` → yahoo `0700.HK`, futu `HK.00700`) and refuse mismatches (`DEMO.A` on yahoo) with a suggested alternative.
- **Live stream** — `/ws/stream` subscribe/unsubscribe; demo keeps the Phase 1 simulator; real channels share a process-wide refresh hub that polls atabet-data (intraday 1–5 s, daily 30–60 s, no poll for `localcsv`/tushare) and pushes `{type:"bar"|"bar_new"}`. Futu/qmt overlay a live snapshot onto cached bars (no kline on each poll). N subscribers coalesce to one upstream request per (source, symbol); the task cancels when the last client unsubscribes
- **Licensing gate** — Quant Intelligence license (`com.atabet.chat`) validated at boot via `atabet-token`; `basic`/`professional` plan rules mirror `atabet-data`'s entitlements exactly; standalone key entry (`/api/license/activate`) or seat-sharing handoff from the app; 402 with upgrade message for Pro-only channels; demo mode otherwise
- **Real data channels** — Basic: `yahoo`, `localcsv`, `tushare`. Pro: `futu`, `ib`, `qmt`. Interactive Brokers is both a **broker** gateway and an atabet-data chart channel (`source='ib'`) once TWS / IB Gateway is running. Simulate/Live with `live_source='ib'` switches the chart to IB history + snapshot polls. Catalog comes from `atabet-data` (`hidden` adapters are omitted, not deleted). Parquet cache per (source, ticker, granularity): intraday charts store raw 1m bars and resample; daily charts store native 1D bars. Timeframe switches reuse that 1m store (with a small slack so “now” does not force a re-download); only missing head/tail gaps hit the vendor, and a rate-limit after a successful fill still serves the cache. Yahoo 1m is fetched in ≤6-day slices (vendor cap is 7 calendar days per call). Bound `localcsv` charts default to the last 500 bars actually in the folder (4000 days on 1D) and pick `K_1M` / `K_1D` from the timeframe; they do not assume a live tail ending now. Channel errors surface honestly (422 range / 501 unsupported / 502 transport or vendor runtime)
- **Channel catalog** — `GET /api/channels` renders the operation matrix (operations, params, ticker conventions, `requires_pro`, `intraday_capable`) straight from `atabet_data.channel_catalog.json`
- **Health** — `/api/health` reports status, mode, licensing state
- **Professional layout** — persistent **beta banner** (preview status; copy follows locale), TopBar (logo + two-line brand, symbol-search typeahead with recents, channel chips with 🔒 for Pro-only, plan badge, timezone + locale pickers), region grid (watchlist rail | chart + status bar | collapsible AI drawer with persisted width). The status bar (feed time, source, mode, short license expiry, clock in the display timezone) stays under the chart so it does not cross the composer. Visual language of the Atabet training site (forest/gold/cream, serif headings, pill chips)
- **Charting** — Lightweight Charts v5 candlesticks + volume, crosshair O/H/L/C/V legend, display timezone (TopBar + Settings, persisted; bars stay UTC, axis/legend/clocks format in the chosen zone), timeframe buttons disabled on channels that aren't `intraday_capable` (auto-fallback to 1D), one ref-counted WebSocket for live bars
- **Default indicators** — registry-backed SMA, EMA, RSI, MACD, BBANDS, ATR, Stochastic, OBV computed on the backend through `atabet_data.ta` (`GET /api/indicators`, `POST /api/indicators/compute`, same gated bar path as `/api/bars`); searchable picker with ParamSpec-driven settings dialogs; overlays render on the price pane, oscillators in their own panes (level lines, histogram coloring, value badges); debounced ≥1 s recompute on live bar closes; active set persisted in `localStorage`
- **Custom indicators** — Monaco drawer (`ƒ Indicator editor`) with debounce-lint, Run / Validate / Save / Add to chart; user scripts are `META` + `compute(df, **params)` and never `exec`'d in FastAPI — always `python -I worker.py` with CPU/AS limits and a 10 s parent timeout; 9-check validation suite (including optional reference vs `atabet_data.ta`); saved scripts get `custom:` ids; `kind: "signal"` outputs render as candle markers (illustrative-only stats strip)
- **Broker** — one `atabet-trader` Engine on a worker thread; paper mode (`BacktestGateway` + kline CSVs from the Phase 3 cache) on any licensed plan; Futu / IB / QMT simulate+live require Pro; IB Connect needs the official TWS API **ibapi 10.48** in the Terminal interpreter (`atabet_env` — not the PyPI 9.81 package, and not only `atabet_trader_env`); OrderTicket is Confirm-only; kill switch cancels open orders and blocks new ones until unlock; LIVE badge + statusbar warning on a live gateway; JSONL audit even for paper. CQG is not exposed. A ticket session and a strategy session are mutually exclusive.
- **Strategy session** — Load / Save / Start / Stop. Load inspects the folder and checks data (backtest) or config + trading-channel connectivity (simulate/live) without starting Engine. After Load, start/end/kwargs are editable; Save writes `atabet_terminal.json` in the folder. Start runs the Engine (Arm still required for simulate/live). Candle B/S markers are the focused ticker only; several fills on one bar share one B and one S arrow, and hover lists every order. Done opens a performance sheet (CAGR, Sharpe, Sortino, Calmar, win rate, best/worst period, equity curve). Hide Chart gives the sheet the candle region's space; a watchlist click restores candles. Sample: `samples/strategy_session/`.
- **AI chatbox** — one-turn assistant with token-by-token SSE (Grok / OpenAI-compatible, Gemini, Anthropic), collapsible tool cards, and chart add/remove events. Keys from `ATABET_AI_*` (QI handoff) or Settings (password field → `~/.atabet/terminal/ai.json`). Tools: bars, localcsv file coverage, channels, compute/add/remove indicator. Chart context includes the bound CSV datetime span so a date-range question does not need `get_bars`. The assistant cannot place orders. Green/red drawer status; Stop cancels the in-flight turn.
- **Quant Intelligence launcher** — Settings → **Local runtime** in the desktop app starts this backend on loopback and opens it in an Atabet window of that app (not the OS browser). The app license and seat are reused (`seat: "app"`). The selected model key is passed in as `ATABET_AI_*` to that local process only. The primary workspace folder is passed as `ATABET_WORKSPACE_ROOT` so the Strategy Load field defaults to it. Closing the Terminal window or Quant Intelligence stops uvicorn. Build `frontend/` (`npm run build`) so `/` serves the UI; otherwise `/` is a splash page.
- **i18n** — English / 简体中文 / 繁體中文 UI, locale persisted in `localStorage`

## Project Structure

```
├── atabet_terminal/          # backend package (Cython-protected wheel)
│   ├── main.py               # FastAPI app factory + license lifespan + live hub + /api/health + SPA
│   ├── static_ui.py          # Serve frontend/dist (or a splash) from the backend port
│   ├── live.py               # coalesced per-subscription refresh tasks (Phase 6)
│   ├── config.py             # env-driven settings (data dir, licensing server URL, workspace root)
│   ├── strategy/             # folder inspect + Engine session + MonitorSink adapter (Phase 11)
│   ├── licensing/
│   │   ├── gate.py           # basic/professional plan rules (mirror of entitlement.py)
│   │   └── seats.py          # LicenseManager: validate, heartbeat, activate, logout
│   ├── data/
│   │   ├── demo.py           # deterministic synthetic generator
│   │   ├── store.py          # Parquet cache + timeframe resampling
│   │   ├── atadata.py        # atabet-data adapter: the ONLY path to vendor data
│   │   ├── cache.py          # per-(source, ticker, granularity) real-channel cache
│   │   └── tickers.py        # user phrase → channel-native ticker (search + /api/bars)
│   ├── ai/                   # prompt, provider adapters, tools, one-turn SSE loop
│   ├── broker/               # Engine wall, paper CSV export, live gateway factory, audit JSONL
│   ├── indicators/
│   │   ├── base.py           # pydantic ParamSpec/OutputSpec/IndicatorMeta contract
│   │   ├── ta_registry.py    # thin adapters over atabet_data.ta (SMA/EMA/RSI/MACD/BBANDS/ATR/Stoch/OBV)
│   │   ├── custom_store.py   # saved scripts under <data_dir>/custom/
│   │   ├── sandbox/          # AST checks + isolated worker.py (kept as source in the wheel)
│   │   └── validation/       # 9-check suite vs contract / lookahead / atabet_data.ta
│   └── routers/
│       ├── market.py         # /api/symbols, gated /api/bars, /ws/stream (demo sim + live hub)
│       ├── indicators.py     # /api/indicators (registry + custom:), /api/indicators/compute
│       ├── custom.py         # /api/custom check/run/validate/save
│       ├── license.py        # /api/license/status, /api/license/activate
│       ├── channels.py       # /api/channels (operation matrix from the catalog)
│       ├── broker.py         # /api/broker session, orders, positions, kill
│       ├── strategy.py       # /api/strategy load/save/start/stop + /ws/strategy
│       └── chat.py           # /api/chat SSE, status, Settings key, stop
├── frontend/                 # React 18 + Vite + TypeScript (dev proxy -> :8000)
│   └── src/
│       ├── layout/           # Shell grid + beta banner, TopBar (search/channels/timezone/locale/LIVE/kill/settings), StatusBar
│       ├── chat/             # ChatDrawer + tool cards + SettingsModal
│       ├── broker/           # OrderTicket (Confirm only) + BrokerPanel
│       ├── strategy/         # StrategyPanel (lazy-loaded; Load/Save/Start/Stop + performance)
│       ├── chart/            # ChartRegion + seriesManager (LWC v5 panes + Deal markers + equity)
│       ├── editor/           # Monaco drawer + validation report (lazy-loaded)
│       ├── api/              # typed REST client + ref-counted stream client
│       ├── lib/              # tickerMap + display timezone helpers
│       └── i18n.ts           # en / zh-CN / zh-TW strings
├── tests/                    # pytest suite (mock licensing + fake channels + TA + live hub + sandbox + paper broker + mock chat + strategy session)
├── samples/                  # run_terminal.py + custom_rsi.py + strategy_session/ (DemoStrategy folder + kline)
├── scripts/smoke.sh          # end-to-end smoke (CI-safe, no license server)
├── environment.yml           # conda env: atabet_terminal_env
├── pyproject.toml            # deps, pytest/ruff/mypy/cibuildwheel config
├── setup.py + build_wheel.py # Cython-compiled, source-protected wheels
└── .github/workflows/        # tests on push; wheels + publish on "[build ci]"
```

## Setup

Quant Intelligence users: create the combined stack with
[`../atabet-quant-intelligence/environment.yml`](../atabet-quant-intelligence/environment.yml)
(`atabet_env`) instead of a terminal-only env.

```bash
conda env create -f environment.yml
conda activate atabet_terminal_env
```

Run the backend:

```bash
# From the repo root (dev runs use the source tree; pip install -e . also works
# but triggers Cython compilation of the package)
ATABET_TERMINAL_DATA_DIR=sample_data python samples/run_terminal.py
# -> http://127.0.0.1:8000  (uvicorn atabet_terminal.main:app)
```

Run the frontend (in a second terminal):

```bash
cd frontend
npm install
npm run dev
# -> http://localhost:5173  (Vite proxies /api and /ws to the backend)
```

> On macOS the Vite dev server binds IPv6 (`http://localhost:5173`), while the backend binds `127.0.0.1:8000` — both defaults work with the bundled proxy config.

Production UI on the backend port (Quant Intelligence launcher):

```bash
cd frontend && npm install && npm run build
# uvicorn then serves frontend/dist at GET http://127.0.0.1:<port>/
```

From the Quant Intelligence desktop app: Settings → **Local runtime** → Atabet Terminal. Point it at this checkout, use a Python that can import `atabet_terminal` (the combined `atabet_env` from Quant Intelligence `environment.yml`, or `atabet_terminal_env`), then **Open Terminal**. That process receives this install's license JWT, device/session seat, the selected model's API key as `ATABET_AI_*`, and the open workspace as `ATABET_WORKSPACE_ROOT`. Closing Quant Intelligence stops uvicorn.

### Strategy folder

Write a `BaseStrategy` in Quant Intelligence (or any local folder). The folder must contain `atabet_trader_config.py` with `DATA_PATH["kline"]` pointing at the parent of `K_1D` / `K_1M`, plus one `BaseStrategy` subclass (in `*strategy*.py`, or named by optional `terminal_session.py`). Daily trees use `K_1D/{code}/ohlcv.csv`; 1-minute trees use `K_1M/{code}/YYYY-MM-DD.csv` (`TIME_STEP=60000`). In the Strategy panel: **Load** prepares and validates (it does not start); edit start/end/other kwargs and **Save** to write `atabet_terminal.json`; **Start** runs the Engine; **Stop** ends it. The watchlist becomes that folder's `localcsv` universe. The chart then uses that tree's last 500 bars (4000 days on 1D) instead of a window ending now, and `1m`/`1D` pick `K_1M`/`K_1D` under the same kline root. A running backtest keeps the focused candles and overlays B/S markers for that ticker (hover lists every fill on the bar). **Done** opens performance stats and the equity curve; Hide Chart expands that sheet, and clicking a watchlist ticker restores the candles. Arm + a Pro license switches the same class onto Futu / IB / QMT (`terminal_session.live_source` pre-selects the gateway on first Load). Backtest mode shows only the Backtest gateway; Futu / IB / QMT appear when Mode is Simulate or Live. Simulate/Live runs from now until **Stop** (or an optional **Auto-stop** clock in the display timezone). Start/End stay visible but greyed (backtest-only) and are not written from that clock. Mode and Gateway stay editable except while Starting/Running — click **Stop** first. After a run finishes, the strategy broker session is released so the Broker strip unlocks. Changing Mode or Gateway greys **Start** until you **Load** again (that re-runs IB/Futu/QMT checks). Stop, then Start in Simulate/Live, reuses that folder; `BarEventEngine.run` starts Engine so the sqlite persist thread is not launched twice. Strategies are trusted author code — they are not run through the custom-indicator sandbox. See `samples/strategy_session/` and Decision #55. Without `terminal_session.py`, the first `BaseStrategy` in alphabetically first `*strategy*.py` is used. A 1-minute example for QMT, Futu, and IB (the clock picks `600519.SH`, `HK.00700` / `US.AAPL`, or weekend `BTC.USD`) lives at `../atabet-demo/demo/single_ticker_intraday_strategy`.

## Environment variables

| Variable | Purpose |
|---|---|
| `ATABET_TERMINAL_DATA_DIR` | Directory for the bar cache (default `sample_data`) |
| `LICENSING_SERVER_URL` | Licensing server base URL (default `http://39.101.65.167:5001`). The QI launcher copies its `LICENSE_SERVER_URL` into this name. |
| `ATABET_TERMINAL_FRONTEND_DIST` | Override path to the Vite `frontend/dist` folder (tests) |
| `ATABET_TERMINAL_LICENSE_FILE` | Where the standalone license key is kept (default `~/.atabet/terminal/license.json`) |
| `ATABET_DATA_TOKEN` / `ATABET_TRADER_TOKEN` | License JWT handed off by the Quant Intelligence app launcher (selects "app" seat) |
| `ATABET_DEVICE_NAME` / `ATABET_CLIENT_ID` | Device identity for the seat (default: hostname) |
| `ATABET_SESSION_TOKEN` | Handed-off session token — shares the app's seat instead of taking a new one |
| `ATABET_SESSION_STORE` | atabet-token session store (default `~/.atabet/session.json`) |
| `ATABET_TERMINAL_LIVE_INTRADAY_POLL` | Live hub poll seconds for intraday charts (default `2`, range 1–5) |
| `ATABET_TERMINAL_LIVE_DAILY_POLL` | Live hub poll seconds for daily charts (default `45`, range 30–60) |
| `ATABET_FUTU_HOST` / `_PORT` / `_ACCOUNT` / `_PWD_UNLOCK` | Futu OpenD overlays for `GATEWAYS['Futu']` |
| `ATABET_IB_HOST` / `_PORT` / `_CLIENTID` / `_ACCOUNT` | IB Gateway / TWS overlays |
| `ATABET_QMT_REDIS_HOST` / `_PORT` / `_PASSWORD` / `_DB` / `ATABET_QMT_KEY_PREFIX` | Big QMT Redis bridge (names match atabet-trader `GATEWAYS['Qmt']`; default prefix `atabet:qmt`) |
| `ATABET_AI_PROVIDER` / `_API_KEY` / `_MODEL` / `_BASE_URL` | QI AI handoff (wins over Settings). Provider ids match QI: `google`, `openai`, `anthropic`, `deepseek`, `kimi`, `minimax`, `qwen`, `huggingface`, `grok` |
| `ATABET_WORKSPACE_ROOT` | QI primary workspace folder. The Strategy Load dialog defaults to this path. Custom env cannot keep a leftover value; the launcher overwrites it. |
| `ATABET_TERMINAL_AI_FILE` | Standalone AI key file (default `~/.atabet/terminal/ai.json`) |

Without any token the terminal boots in **demo mode**: no seat is taken, and
any request for a real channel answers **402** with the subscribe/upgrade
message. Standalone users enter their key via `/api/license/activate`; the
terminal then owns its own seat and frees it on clean shutdown.

Standalone AI keys are entered in Settings (password field). Env handoff always wins.

## Tests

```bash
python -m pytest tests/ -q        # no network needed
sh scripts/smoke.sh               # end-to-end: boot -> /api/bars -> RSI compute -> good_rsi validate -> broker gateways -> chat status -> stop
```

Real-channel requests in tests run against a `FakeChannelClient` that
speaks the exact atabet-data frame contract (multi-index `FLDS` frames),
so the adapter path (request → gate → cache → resample → rows, including
timezone conversion) is verified offline. Licensed live smokes (Yahoo
`AAPL` daily, Futu `HK.00700` with OpenD) are manual steps from the build
guide.

```bash
# Manual live smoke (needs a license and a channel, e.g. yahoo + yfinance):
ATABET_DATA_TOKEN=<jwt> ATABET_TERMINAL_DATA_DIR=sample_data \
  python samples/run_terminal.py
# then: curl 'http://127.0.0.1:8000/api/bars?symbol=AAPL&source=yahoo&timeframe=1D'

# Paper broker (needs a license + atabet-trader from local source):
#   pip install -e ../atabet-trader
# then connect Backtest in the Broker panel, Confirm a size-1 market buy.
# Manual Futu simulate smoke (Pro token, OpenD running, market hours):
#   ATABET_DATA_TOKEN=<jwt> ATABET_FUTU_HOST=127.0.0.1 ATABET_FUTU_PORT=11111 \
#     python samples/run_terminal.py
# then: Connect Futu / simulate on HK.00700, Confirm size 1 (one board lot =
#   100 shares), Kill a resting far-limit. US names may be unsupported on
#   some OpenD simulate accounts.

# Manual Gemini chat smoke (needs ATABET_AI_API_KEY, chart on AAPL/1D):
#   ATABET_AI_PROVIDER=google ATABET_AI_API_KEY=<key> \
#     python samples/run_terminal.py
# then ask "what's this chart" and "add RSI(14)" — the RSI pane should appear.
```

## Quality gates

- `ruff check atabet_terminal tests` and `mypy` (strict) on the package
- Frontend: `npm run build` runs `tsc` + a production build (Lightweight Charts 5 via `addSeries`)
- Wheels: build all three platforms with a commit message containing `[build ci]` (GitHub Actions), or locally via `build_wheel.py`

## License

Atabet Terminal is licensed under the Quant Intelligence license (`com.atabet.chat`), validated against the Atabet licensing server through `atabet-token` — the same token and seat used by `atabet-data` and `atabet-trader`. Launched from the Quant Intelligence app, the terminal shares the app's seat (no second device seat, no logout on exit); launched standalone, it activates with the license key and frees its seat on shutdown. Basic covers `yahoo` / `localcsv` / `tushare` and backtesting; Professional adds `futu` / `ib` / `qmt` data and the live broker gateways.
