Metadata-Version: 2.4
Name: aetherion-ai
Version: 2.4.0
Summary: Aetherion - autonomous market intelligence: a multi-agent research, risk-controlled paper-trading system with a terminal UI.
Author: Amit Singh
License: All rights reserved
Project-URL: Homepage, https://github.com/Amits64/aetherion
Project-URL: Repository, https://github.com/Amits64/aetherion
Project-URL: Issues, https://github.com/Amits64/aetherion/issues
Keywords: trading,agents,llm,langgraph,quantitative,paper-trading,terminal-ui
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
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.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: numpy>=1.24
Requires-Dist: pandas>=2.0
Requires-Dist: scipy>=1.10
Requires-Dist: scikit-learn>=1.3
Requires-Dist: matplotlib>=3.7
Requires-Dist: seaborn>=0.12
Requires-Dist: pyyaml>=6.0
Requires-Dist: requests>=2.31
Requires-Dist: httpx>=0.27
Requires-Dist: defusedxml>=0.7
Requires-Dist: python-dotenv>=1.0
Requires-Dist: yfinance>=0.2.40
Requires-Dist: backtrader>=1.9.78
Requires-Dist: rich>=13.4
Requires-Dist: textual>=0.80
Requires-Dist: fastapi>=0.100
Requires-Dist: uvicorn>=0.23
Requires-Dist: websockets>=12.0
Requires-Dist: pydantic>=2.0
Requires-Dist: langgraph>=1.2.6
Requires-Dist: langchain-core>=1.4.8
Requires-Dist: litellm>=1.0
Provides-Extra: crypto
Requires-Dist: python-binance>=1.0; extra == "crypto"
Requires-Dist: pycoingecko>=3.1; extra == "crypto"
Provides-Extra: india
Requires-Dist: dhanhq>=2.0; extra == "india"
Provides-Extra: cache
Requires-Dist: redis>=4.5; extra == "cache"
Provides-Extra: finnhub
Requires-Dist: finnhub-python>=2.4; extra == "finnhub"
Provides-Extra: analytics
Requires-Dist: empyrical>=0.5.5; extra == "analytics"
Provides-Extra: crew
Requires-Dist: crewai>=0.30; extra == "crew"
Provides-Extra: dev
Requires-Dist: pytest>=7.4; extra == "dev"
Requires-Dist: pytest-cov>=4.1; extra == "dev"
Requires-Dist: pytest-mock>=3.11; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"

<p align="center">
  <img src="https://lh3.googleusercontent.com/d/1WPZZ6r6eXOeWxYul0UjSryy5iBM7RefE" alt="AETHERION brand icon" width="160" />
</p>

<div align="center">

# AETHERION — Autonomous Market Intelligence

**Observe. Debate. Quantify. Protect.**

An evidence-driven, multi-agent market intelligence system built around quantitative research, disciplined risk controls, and continuous reflection.

<br />

### A.M.I.T.

**Aetherion Machine Intelligence Technology**

*“The Intelligence Behind AETHERION.”*

[![Quality gate status](https://sonarcloud.io/api/project_badges/measure?project=chauhanamit090_Aetherion&metric=alert_status&token=dca955e7483d7fbc9cf075158b16202c1de761fb)](https://sonarcloud.io/summary/new_code?id=chauhanamit090_Aetherion) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=chauhanamit090_Aetherion&metric=coverage&token=dca955e7483d7fbc9cf075158b16202c1de761fb)](https://sonarcloud.io/summary/new_code?id=chauhanamit090_Aetherion) [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=chauhanamit090_Aetherion&metric=duplicated_lines_density&token=dca955e7483d7fbc9cf075158b16202c1de761fb)](https://sonarcloud.io/summary/new_code?id=chauhanamit090_Aetherion) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=chauhanamit090_Aetherion&metric=ncloc&token=dca955e7483d7fbc9cf075158b16202c1de761fb)](https://sonarcloud.io/summary/new_code?id=chauhanamit090_Aetherion) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=chauhanamit090_Aetherion&metric=reliability_rating&token=dca955e7483d7fbc9cf075158b16202c1de761fb)](https://sonarcloud.io/summary/new_code?id=chauhanamit090_Aetherion) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=chauhanamit090_Aetherion&metric=security_rating&token=dca955e7483d7fbc9cf075158b16202c1de761fb)](https://sonarcloud.io/summary/new_code?id=chauhanamit090_Aetherion) [![Maintainability issues](https://sonarcloud.io/api/project_badges/measure?project=chauhanamit090_Aetherion&metric=software_quality_maintainability_issues&token=dca955e7483d7fbc9cf075158b16202c1de761fb)](https://sonarcloud.io/summary/new_code?id=chauhanamit090_Aetherion) [![Reliability issues](https://sonarcloud.io/api/project_badges/measure?project=chauhanamit090_Aetherion&metric=software_quality_reliability_issues&token=dca955e7483d7fbc9cf075158b16202c1de761fb)](https://sonarcloud.io/summary/new_code?id=chauhanamit090_Aetherion) [![Security issues](https://sonarcloud.io/api/project_badges/measure?project=chauhanamit090_Aetherion&metric=software_quality_security_issues&token=dca955e7483d7fbc9cf075158b16202c1de761fb)](https://sonarcloud.io/summary/new_code?id=chauhanamit090_Aetherion) [![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=chauhanamit090_Aetherion&metric=sqale_index&token=dca955e7483d7fbc9cf075158b16202c1de761fb)](https://sonarcloud.io/summary/new_code?id=chauhanamit090_Aetherion) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=chauhanamit090_Aetherion&metric=sqale_rating&token=dca955e7483d7fbc9cf075158b16202c1de761fb)](https://sonarcloud.io/summary/new_code?id=chauhanamit090_Aetherion)

</div>

**A.M.I.T. and AETHERION.** A.M.I.T. (Aetherion Machine Intelligence Technology) is your personal AI intelligence system, the assistant that observes,
debates and explains. AETHERION is the broader AI and quantitative trading ecosystem around it: the data, research, risk controls and paper-trading platform. They are
distinct but connected identities: A.M.I.T. is the intelligence, AETHERION is the system it works inside.

An agentic trading research and **paper-trading** platform. A LangGraph pipeline runs an
analyst → bull/bear debate → risk committee → execution cycle over a screened universe
(crypto, US/India equities, commodity futures), with LLM agents kept **advisory** behind
deterministic risk controls. A rule-based, validated baseline strategy (time-series
momentum) runs alongside the agents. A FastAPI backend streams everything to the terminal UI.

> **Status: research / paper trading.** Nothing here is financial advice, and nothing is
> wired to trade real money by default. Most of what the research in this repo found is
> *negative* (see [Research findings](#research-findings)) — the value of the system is
> measurement honesty and risk control, not a claimed edge.

---

## How a cycle works

```
memory → universe (screener) → market_data → regime
      → technical  ┐
      → fundamental├→ bull → bear → research_manager → trader ┐
      → sentiment  │                                          ├→ risk committee
      → macro      ┘                         baseline (TSMOM) ┘   (aggressive / neutral / conservative)
      → risk_manager → portfolio_manager → human_approval → execution → reflection
```

- **Agents are advisory.** Every LLM/skill call has a deterministic fallback, and a per-cycle
  wall-clock budget (`llm_time_budget_s`, default 270 s) bounds latency when providers degrade.
- **Hard limits are deterministic.** The risk manager, execution node and position manager
  enforce limits regardless of what an agent says.
- **Two LLM paths.** The real Hermes Agent CLI (tools: web,
  Yahoo Finance MCP, …) when available, falling back to an in-process call to the
  `aetherion/skills/*/SKILL.md` prompts through OpenRouter (free-model pool) → Ollama cloud → local Ollama.

## Real-time layer

The LLM cycle runs every ~30 minutes (it takes minutes per pass), but everything deterministic follows the
tape in seconds over the shared `/ws/live` WebSocket:

| Event | Cadence | What it carries |
|---|---|---|
| `price_tick` | 2 s | Latest tick per watched symbol (Binance WSS for crypto, Yahoo WSS for equities/futures) |
| `feed_status` | 2 s | `live` / `closed` / `stalled` / `polling` per symbol, so a quiet chart is never ambiguous |
| `ta_update` | 5 s | Technical read recomputed against the forming bar — same scoring as the agent cycle (`aetherion/graph/nodes/technical.technical_score`) |
| `pnl_update` + exits | 5 s with live ticks, else 30 s | Position marks and stop/target/trailing exits |

The chart keeps a forming candle (running high/low, new bar on rollover) and survives the 60 s refetch
without snapping back. **A closed market cannot tick:** gold, silver and crude (CME futures) are closed
from Friday 17:00 ET to Sunday 18:00 ET (and 17:00–18:00 ET daily), US equities outside 09:30–16:00 ET;
the terminal shows `MARKET CLOSED` instead of a frozen chart. Calendars live in `aetherion/execution/market_hours.py`
(exchange holidays are not modelled; a holiday shows as `FEED STALLED`, which is accurate).

### Multi-timeframe context

`aetherion/analytics/mtf.py` builds one per-timeframe view (5m, 15m, 1h, 4h, 1d: direction, EMA trend, RSI/ADX, MACD,
swing structure, and the patterns detected on that timeframe's own candles) and a weighted alignment
(5m 0.5 · 15m 0.75 · 1h 1 · 4h 1.5 · 1d 2). The agent cycle, the real-time loop and the chart read the same
builder, and the bull/bear/research/trade-synthesis agents receive its compact summary through `slim_tech`.
`GraphConfig.mtf_timeframes` selects which timeframes vote in the technical score (a code-level field;
there is no API or env setting yet). `["1h","4h","1d"]` reproduces the pre-5m/15m technical score exactly:
count-vote alignment, the legacy (unscaled) pattern tilt and trader geometry, and the legacy 4h/1d row counts
(200/60). Volatility-scaled patterns appear only in the MTF context, the chart and the agents' summary. Pattern tolerances scale with each timeframe's own volatility;
calibration and before/after density are in `docs/research/pattern-calibration.md`
(`python scripts/calibrate_patterns.py --check` re-verifies it).

## Risk and reliability controls

| Control | Where | Behavior |
|---|---|---|
| Daily-loss kill switch | `aetherion/graph/nodes/execution.py` | Halts all orders when daily loss ≥ `max_daily_loss_pct`; optional book flatten |
| **Drawdown guard** | `aetherion/execution/risk_guard.py` | Persisted peak-to-trough halt of *new entries*, restart-proof daily baseline, loss-streak breaker, de-risk ladder (size only ever scaled down). Every halt has a cooldown and auto-resets; fails closed on bad data |
| **Extreme-shock gate** | `aetherion/analytics/shock.py` | Lagged-window return z-score + volatility expansion (+ volume confirmation): warning → half size, critical → block new entries. Closes are never blocked |
| Stale-data gate | `aetherion/graph/nodes/market_data.py` | Analysis-only when bars are stale (e.g. market closed) |
| Stale-entry guard | `aetherion/graph/nodes/execution.py` | Rejects an entry if live price drifted > `max_entry_deviation_pct` from the analyzed price |
| Position manager | `aetherion/execution/position_manager.py` | Stop/target, breakeven + trailing stop, time stop, flatten-all |
| Expectancy throttle | `aetherion/analytics/expectancy.py` | Halves size for setup buckets with ≥10 closed trades and negative mean R |
| **Macro release blackout** | `aetherion/data/macro_events.py`, `graph/risk/gates.py` | Refuses new entries 30 min before / after a US CPI, jobs-report or FOMC release (tab 9 sets the window or turns it off; BLS dates need `AETHERION_CONTACT_EMAIL`). The macro node also hands every agent the next release and its time. An unreadable calendar changes nothing |
| Re-entry cooldown | `aetherion/graph/nodes/risk_manager.py` | Blocks immediate re-entry after a close |
| Strategy governance | `aetherion/strategies/governance.py` | Strategies must be registered/approved before live use |
| Calibration report | `scripts/calibration_report.py` | Tests whether stated trade confidence carries information (AUC permutation test, Brier skill, ECE) |

## Quick start

Requirements: Python ≥ 3.11. Optional: [Ollama](https://ollama.com) and the Hermes Agent CLI for the CLI LLM tier.
There is no browser dashboard (retired in 2.0): the terminal UI is the interface.

### Install from PyPI

```bash
pip install aetherion-ai            # the command it installs is `aetherion`
aetherion                           # opens the terminal: pick symbols, press START
```

`aetherion` starts the backend for you, shows a symbol selector, and fades into **Mission Control** once trading
begins. Everything else:

```bash
aetherion --demo                    # synthetic engine: no backend, no API keys, nothing to configure
aetherion --attach                  # skip the selector, watch an engine that is already running
aetherion serve [--port 8001]       # only the API (http://127.0.0.1:8001)
aetherion doctor                    # check the installation, LLM keys and data folder
aetherion pipeline --help           # research / backtest command line
pip install "aetherion-ai[crypto,india,cache,analytics,crew]"   # optional integrations
```

**Ollama Cloud (no local model needed).** Pick *Ollama Cloud* as the provider, paste the key from your own Ollama
account (ollama.com/settings/keys; the free plan works, within Ollama's usage limits) and tick the hosted models you want
(`gpt-oss:120b`, `gpt-oss:20b`, `glm`, `deepseek`, ... fetched live; the `-cloud` spelling is accepted too). The first
ticked model is preferred; the others are fallbacks, and a model that hits its usage limit is skipped automatically.
Models installed on your PC are never used unless you choose the *Ollama (models installed on this PC)* provider or set
`AETHERION_ALLOW_LOCAL_MODELS=1`.

**Choosing your model.** The launch screen has a *Provider / API key* row and a *Fallback* row. You pick a **provider**; the
model is chosen for you and shown with its live status:

* **NVIDIA NIM** and **OpenRouter** use their free models. The screen asks the provider's best-ranked free model one tiny question
  and keeps it if it answers (`● OK 1.2 s`); if it is slow or down it tries the next ones and uses the first that works. The last
  working choice is remembered for six hours, so starting is instant.
* The **Fallback** works the same way: pick a provider (it needs a saved key) and its best working free model is selected.
* **The chain.** Every call tries, in order: the **primary**, the **fallback** you chose, then **Ollama Cloud** as the *last resort*.
  Each tier is used only when the one before returned nothing. A NVIDIA call that gets no answer within **40 seconds** moves on; OpenRouter
  stops at its **daily request limit** (1,000 for a funded account, adjustable in tab 9) and goes to the next tier; OpenRouter is used for
  **free models only** (a paid model id is replaced by the best free one, so a stale choice can never spend credits). With NVIDIA as the
  primary, OpenRouter is pre-selected as the fallback (and the reverse) once both keys are saved. The *Last resort* row takes your
  Ollama Cloud key (saved on the welcome screen) and can be switched off; with no key the agents use their rules-based result.
* **Local model (Ollama), prepared before the welcome screen.** On start Aetherion checks for Ollama: if it is not installed it installs
  it (Windows: winget, or the official installer from ollama.com, run only if its signature is valid; macOS: Homebrew; Linux: the
  official script when no password is needed, otherwise it prints the one-line command), starts its server, pulls `qwen3.5:0.8b`
  (1.2 GB, once) if no Qwen model is installed, and loads it into memory. Anything already in place is skipped. Then *Ollama (local
  models)* can be the primary or the fallback, with the model chosen automatically from what is installed. On a CPU a call takes
  roughly 9 s (0.8B), 15 s (2B). Turn it off with `--no-ollama` or `AETHERION_SKIP_OLLAMA=1`;
  `AETHERION_OLLAMA_MODEL` picks another model to pull; `python -m aetherion.ollama_setup` runs the preparation by itself.
* Other providers (Anthropic, OpenAI, Gemini, Groq, DeepSeek, Mistral, Ollama Cloud) use their default model; only *Custom* and
  *local Ollama* ask for a model id, because nothing can choose it for you.
* **CHECK MODELS** looks again (for example after pasting a key). The same status is on the LLM tab (6) and at
  `GET /api/llm-status` (`?refresh=true` asks the provider again).
* At the start of every cycle the primary is probed in the background; if it stopped answering, Aetherion switches to another free
  model of the same provider and says so in the log, instead of every agent waiting on the dead one.

* **Requests used.** Providers meter requests, not cycles, and a cycle makes about 12-14 calls for two symbols. The LLM tab shows,
  for each provider, the requests made today (UTC) and in the last minute against your plan's limits: OpenRouter 1,000 a day and 20 a
  minute for a funded account, NVIDIA's free tier 40 a minute and no published daily cap. Change the limits in tab 9 (LLM group).
  It informs and logs a warning at 90% and 100%; it never blocks a call. Requests the HTTP library retries internally are not
  visible to it, so the provider's own count can be a little higher.

The choice is saved: next time the screen opens on the same provider with your key shown masked. Keys are stored in
`~/.aetherion/.env` (owner-only permissions) and the chosen default in `~/.aetherion/llm.json`; keys are never logged, never put in
`llm.json`, and only sent to a backend running on your own machine. An NVIDIA key saved earlier under *Custom endpoint* is recognised
as your NVIDIA key.

Configuration: environment variables, or a `.env` in the current folder or in `~/.aetherion/.env` (LLM keys such as
`OPENROUTER_API_KEY`; all optional for paper mode). State, logs and memory live in `~/.aetherion/`.

> The PyPI name `aetherion` belongs to an unrelated project, so the package is published as **`aetherion-ai`**.

Aetherion follows [Semantic Versioning](https://semver.org); releases are derived from the commit history (`aetherion --version`, `CHANGELOG.md`, `docs/versioning.md`).

### From source

```bash
git clone git@github.com:Amits64/aetherion.git
cd aetherion

python -m venv .venv && source .venv/bin/activate      # Windows: .venv\Scriptsctivate
pip install -e ".[dev]"

cp .env.example .env        # then fill in the keys you need (all optional for paper mode)

aetherion                   # terminal UI (starts the backend itself)
aetherion serve             # or: API only on :8001
```

Pick symbols (or leave empty for the auto-screener) and press **Start**.
The default broker is an in-memory **paper broker** that restarts at $100,000 each process.

AUTO-SCREEN uses the evidence-based implementation in `aetherion/screening/` by default (`screener_agentic=true`). It uses
closed candles, Binance derivatives context for crypto, and cached daily trend/liquidity/benchmark evidence for other assets.
The launcher shows the top 50 live Binance USDT perpetuals (cached for 10 minutes, with an instant static fallback) plus
three commodities, three ETFs, and four large-cap stocks. Existing market-hours, rotation, canonical-symbol, hand-picked
symbol, and diversification rules still apply. Set `screener_agentic=false` to use the legacy ranker. The optional LLM
committee is off by default, advisory-only, may demote/reorder only the deterministic shortlist, and is skipped if the
shared cycle LLM budget is exhausted or no model is configured.

If you use the Hermes CLI tier, run `ollama serve` first. When the CLI's provider is
unreachable the client skips it instantly and uses the in-process fallback.

The Hermes CLI runs skills from **its own registry** as `aetherion-<name>`, not from this repo's `aetherion/skills/`
folder. The repo is the source of truth; register (or refresh) the 13 skills the pipeline uses with:

```bash
python scripts/register_hermes_skills.py --check     # is Hermes up to date with aetherion/skills/?
python scripts/register_hermes_skills.py             # write/refresh the aetherion-* copies
```

(`--dest DIR` overrides the Hermes skills directory; other skills there are never touched.)

### Command line

```bash
aetherion pipeline --help      # data, strategies, backtest, validate, optimize, regime, paper, performance, live
aetherion pipeline strategies --help # strategy management
python scripts/calibration_report.py          # is trade confidence informative? (needs closed trades)
```

## Configuration

Runtime behavior is controlled by `GraphConfig` (`aetherion/graph/state.py`); the backend builds it from the
dashboard request. Notable knobs:

| Setting | Default | Meaning |
|---|---|---|
| `max_daily_loss_pct` | 0.02 | Daily kill-switch threshold (fraction) |
| `max_drawdown_pct` | 0.10 | Peak-to-trough halt of new entries (fraction) |
| `max_loss_streak` | 4 | Consecutive losing trades before a cooldown halt |
| `shock_gate_enabled` | True | Extreme-shock gate on new entries |
| `risk_per_trade_pct` / `max_position_pct` | 0.01 / 0.20 | Sizing |
| `baseline_strategies` | `["tsmom"]` | Deterministic baseline layer (empty list disables) |
| `llm_time_budget_s` | 270 | Per-cycle LLM wall-clock budget |
| `kill_switch_flatten` | False | Also close the book on a daily-loss breach |

Environment variables are listed in [`.env.example`](.env.example) (names only — never commit `.env`).
Useful switches: `COMMODITY_DATA_SOURCE` (`yfinance` default | `binance`), `HERMES_CLI_CONCURRENCY`,
`HERMES_CLI_PROVIDER_URL`, `OLLAMA_*`, `OPENROUTER_*`, `TRADING_MODE`.

**Data providers** (`aetherion/data/providers/`): Binance, Yahoo Finance, CoinGecko, FRED, Dhan (NSE/BSE),
local CSV, with an on-disk/in-memory cache and a negative cache for failing symbols. Gold/silver/crude
futures (`GC=F`, `SI=F`, `CL=F`) use Yahoo (COMEX) by default so analysis, live price patching and the
stale-entry guard all see one instrument. Binance's USDT-margined commodity perpetuals (`XAUUSDT`, `XAGUSDT`, `CLUSDT`, `BZUSDT`; `XAUUSDT.P` on
TradingView) can also be used **by name**, on their own: they are fetched from Binance's futures API (they are not listed on spot), are commodities for
correlation and benchmark purposes (gold reads the gold news terms and the GLD benchmark), trade around the clock, and are on the watchlist next to the COMEX
tickers. Any spelling (`XAUUSDT.P`, `xauusdt`) is the same symbol. They carry only a few hundred daily candles (history started late), so a 200-day
average on them is short of data where the COMEX series is not; they have no live tick stream yet (the last candle stands in); and, like every USDT symbol, they
count in the crypto segment for position limits. `COMMODITY_DATA_SOURCE=binance` still maps the COMEX tickers onto them.

**Sentiment & macro inputs** (no API keys needed): `aetherion/data/social.py` pre-fetches StockTwits (Bullish/Bearish
tags; GLD/SLV/USO cashtags stand in for gold/silver/crude futures) and Reddit RSS in code and hands them to
the sentiment skill as data, so a Hermes outage no longer means no social signal. A failed fetch is
reported as `unavailable`, never as silence. The report carries a six-band `sentiment_band`
(`Mixed` = sources disagree), a deterministic `divergence` flag, `contrarian_extreme` (>=90/10 crowd) and
`data_gaps`. `aetherion/data/macro_feed.py` adds FRED rates (10y, real 10y, fed funds, 10y-2y, CPI, broad dollar via
the keyless `fredgraph.csv`) with a `metals_pressure` read, and Polymarket event odds (Fed cuts, recession,
gold/oil/bitcoin). The approach follows TauricResearch/TradingAgents' analysts; see
`docs/research/tradingagents-analyst-comparison.md`.

## Terminal UI (replaces raw logs)

A Bloomberg-style Textual terminal that shows what the agents are doing instead of Python log lines.

```
aetherion                       # opens the launcher; the backend is started (and stopped) for you
aetherion --attach              # attach to an engine already running at http://localhost:8001 (--url, --api-key)
aetherion --demo                # no backend at all
AETHERION_QUIET_LOGS=1 aetherion serve    # engine only; console shows warnings, full log -> ~/.aetherion/logs/aetherion.log
```
Run the engine and the terminal in two shells; the terminal attaches to a running engine and can be closed and reopened freely.

**Mission Control** - six screens (keys `1`-`6`): **1 Mission Control** (8-panel mosaic: Portfolio Command Center,
Neural Agent Network, Active Agent Grid, AI Thinking Feed, Risk Matrix, Opportunity Scanner, Execution Queue, Reflection
Memory), **2 Agent Tree** (the cycle as a collapsible tree: cluster → agent → every skill/LLM call, with a detail pane showing the full result text; `x`/`z` expand/collapse all, `s` newest-first; the Mission tab keeps the card grid. Each call is also its own card there: `Research Agent #01, #02, #03` with start time, runtime, tokens,
symbol, confidence and a result summary; running cards pulse, finished ones show an animated check, failures flash red),
**3 Timeline** (every execution in chronological order with a Gantt bar, so you see how a decision was formed),
**4 Neural Net** (Memory, Research, Technical, Risk, Approval, Execution, Reflection as connected nodes; packets travel
along the links when information flows), **5 Intel**, **6 LLM**, **7 Portfolio & Risk** (the side columns at full size - use it when the window is too narrow to show them on the Mission tab). Keys: `s` sort cards, `f` filter thoughts, `c` clear
the activity strip, `q` quit. An animation clock (12 Hz) re-renders only the panels that have motion.

**8 Chart** works like TradingView's: click the chart (or Tab to it) and
- **scroll through history** by dragging, with `←` `→` (one candle), `Shift`+`←` `→` (a page), `Home` (oldest loaded) and `End` (back to the live edge; double-click does the same).
  Keep going right past the newest candle (`→`, drag left) to see empty space ahead of the price, as in TradingView (`◀ N back` / `▶ N ahead` in the header). New bars never move what you are looking at while you are scrolled back; at the left edge the terminal asks the backend for 300 older candles, up to 2,000;
- **zoom the candles** with the mouse wheel (about the pointer; at the live edge the newest candle stays at the right edge) or `+` / `-` (1 to 8 cells per candle);
- **scale the price axis** by dragging it (up zooms in), with `↑` `↓`, move it with `Shift`+drag or `Shift`+`↑` `↓`; `a` or a double-click on the axis fits it to the visible candles again, `r` resets the whole view;
- hover for a **crosshair**: the candle's time and O/H/L/C in the header, the price at the pointer on the axis, the time under it;
- **bar-close countdown**: "closes in 12:34" in the header and under the last-price badge, counting down to the close of the current candle (hours for a 4h bar, days for a daily one). It is
  hidden when the newest candle is long past (a closed market), and a bar that has just closed is fetched at once.
`Z` shows the chart full screen. Symbol and timeframe: the pickers, `[` `]` and `t`.

```
graph nodes --wrap_node--> EventBus <--publish-- HermesClient.run_skill / maybe_invoke_model
                              |                         (observability/)
                              +--> app_state.push_event --> /ws/live --> terminal.client --> terminal.store.reduce --> widgets
GET /api/events/recent  <-- replay buffer (late attach backfill)       REST pollers: portfolio, status, risk, llm-health, assets
```

`aetherion/observability/` holds the schema (21-agent registry), the thread-safe bus and the humanizer ("Risk Agent approved 1,
rejected 3 (confidence x2, shock block)"); every graph node is wrapped once in `aetherion/graph/build.py`. `aetherion/terminal/store.py` is a
pure reducer, so the whole data layer is unit-tested without a UI.

## Multi-asset correlation

`aetherion/analytics/correlation.py` (`/api/correlation`, `/api/correlation/analyze`).
Correlations are on log *returns* (never price levels) over the timestamps both assets traded, so a crypto Fri->Mon
move is compared with an equity Fri->Mon move. Per pair: short/mid/long windows with the short-vs-long change
(regime breaks, sign flips), stress correlation (worst 25% of days), rolling series, lead-lag and beta to SPY.
Basket level: hierarchical clusters, average pairwise correlation, first-PC share, effective independent bets
(also for your open positions), and a regime block (rolling mean correlation, correlation entropy, cross-asset
volatility dispersion, tail kurtosis/skew; adapted from an earlier standalone analyzer). Intervals 1d/4h/1h/15m.
"Analyze with AI" runs the Hermes `correlation-analysis` skill on a compact numeric digest and falls back to a
deterministic analysis in the same schema when no LLM answers.

## Neural-network signal filter (opt-in)

Any strategy can be wrapped with a small NumPy network that scores its BUY signals (meta-labeling): set
`"nn_filter": true` in the strategy params (API/CLI/`create_strategy`). The strategy still decides *when* a signal
exists; the network, trained only on that strategy's own past signals and their triple-barrier outcomes, vetoes
those with P(win) below `nn_min_prob`. It fails open: until it has `nn_min_signals` resolved signals **and** its
holdout AUC's 95% lower bound exceeds 0.5, the base strategy trades unchanged. SELLs (exits) always pass.
Code: `aetherion/analytics/meta_label.py`, `aetherion/strategies/nn_overlay.py`; A/B harness: `scripts/eval_nn_overlay.py`.
**Measured result (BTC/ETH/GC=F 1h, 3000 bars, walk-forward): no reliable improvement** (see
`docs/research/nn-overlay-ab.md`), so it stays off by default.

## Outcome-settled memory

Every trade idea (executed or not) is logged as a *pending* decision in `~/.aetherion/memory/decision_outcomes.jsonl`.
Once `outcome_horizon_hours` (24h) has traded, the next cycle's memory node settles it against realised prices:
directional return, R-multiple (from the stop) and alpha vs a benchmark (BTC for altcoins, SPY for equities,
none for BTC/commodities). A two-sentence lesson that cites the number is written (LLM when the budget allows,
deterministic otherwise) and the per-symbol track record plus recent lessons lead `memory_context`, point-in-time
(only outcomes already known). Design from TauricResearch/TradingAgents' memory log; code in
`aetherion/graph/outcome_memory.py` and `aetherion/graph/outcome_loop.py`. Settings: `outcome_memory_enabled`,
`outcome_memory_path`, `outcome_horizon_hours`, `outcome_max_reflections`.

## Strategies

Registered in `aetherion/strategies/implementations/`: `tsmom` (deployed baseline), `smart_money`,
`trend_following`, `mean_reversion`, `chart_patterns`, `neural_network`, `quantum_resonance`.
The backtest engine (`aetherion/strategies/backtest.py`, Backtrader) and walk-forward tools
(`aetherion/validation/walk_forward.py`) back the evaluations below.

## Research findings

Summary of the evaluations behind the design choices (details in
[`docs/research/strategy-findings.md`](docs/research/strategy-findings.md)):

| Question | Result |
|---|---|
| Does time-series momentum (long above SMA200) work? | **Yes, as drawdown control** — on a diversified basket Sharpe ≈ buy-and-hold with a 27.7% vs 67% max drawdown. Deployed as the baseline. Single-symbol results are weak; the value is the basket |
| Do per-symbol tuned strategies hold up out-of-sample? | **No** — full-sample winners collapsed walk-forward (curve-fits) |
| Gold / silver / crude: any tradeable edge beyond trend risk-reduction? | **No** — shorts lose on every family; long/flat trend never beats buy-and-hold Sharpe; ratio mean-reversion and cross-sectional momentum ≈ 0 |
| Can neural nets predict daily direction? | **No** — pooled walk-forward AUC ≈ 0.50 for logistic/MLP/gradient boosting |
| Can ML forecast volatility? | Yes (R² 0.67 vs 0.59 trailing) — but the trading gain is negligible (+0.01–0.03 Sharpe), so it is not deployed |
| Do chart patterns work standalone? | **No** — kept only as confluence |

## Testing

```bash
pytest                      # 480 tests
pytest tests/unit/test_risk_guard.py -v
```

Tests isolate the expectancy ledger and the risk-guard state (`tests/conftest.py`) so they never
touch `~/.aetherion`. Safety controls are test-driven: each has a test proving it fires, plus mutation
checks that fail when the control is broken.

## Repository layout

```
backend/            FastAPI app (REST + /ws/live), starts/stops the trading loop
graph/              LangGraph pipeline: build.py, state.py, nodes/ (one module per stage), utils/
skills/             Agent skill prompts (SKILL.md): source of truth for Hermes (via register_hermes_skills.py) and the in-process fallback
strategies/         Strategy base/registry/governance, backtest engine, implementations/
execution/          Brokers (paper, Dhan), position manager, risk_guard, live tick streams
analytics/          Expectancy ledger, shock gate, calibration, regime, performance, patterns
portfolio/          Optimizer, covariance estimators, risk models
validation/         Walk-forward validation, deflated-Sharpe helpers
data/               Data pipeline + providers + cache
crew/, miaoxiang/   Auxiliary orchestration and backtest helpers
config/             costs.yaml, risk.yaml, settings.yaml
scripts/            calibration_report.py, register_hermes_skills.py, TradingView shortlist, MCP patch helpers
examples/           Small standalone examples
docs/               Research findings, design notes, specs/plans, archive/
tests/              unit/ and integration/
hermes_client.py    Hermes CLI + in-process LLM fallback client
llm_config.py, llm_rotation.py   LLM backends and the free-model rotation pool
research_*.py       Scheduled research briefings (see below)
```

### Scheduled research scripts

`research_hourly.py` (and the helpers `research_hourly_news.py`, `research_briefing.py`) are run
hourly by a Windows Task Scheduler job — see
[`docs/research/HOURLY_RESEARCH_SETUP_SUMMARY.md`](docs/research/HOURLY_RESEARCH_SETUP_SUMMARY.md).
They stay at the repository root because the scheduled task references that path; moving them
requires updating the task.

## Known limitations

- Paper broker state is in-memory and resets per process; the guard's saved state is scoped to the
  broker session so it never mistakes a reset for a drawdown.
- Free-tier LLM providers are rate-limited; the per-cycle budget and fallbacks exist for that reason.
- Commodity futures are continuous (unadjusted-roll) series from Yahoo; volume on `GC=F`/`SI=F` is
  noisy around contract rolls, so volume is only used to *confirm* price-based shock signals.
- No license has been specified.

## My trade setups (personal preferences)

Tab **0 MY SETUPS** lets you describe a preferred setup in plain words, check the structured rules AETHERION understood, save it, and have the
agents consider it in future cycles. Pick the symbol, type something like

> For XAUUSDT, only consider longs when the 1-hour trend is bullish and price pulls back to the 21 EMA. Require a bullish 15-minute confirmation
> candle. Avoid entries during major US data releases. Use a stop below the setup's swing low and aim for at least a 2:1 reward-to-risk ratio.

and press **Interpret Setup**. Nothing is saved yet. The preview lists every rule with what it will do (*required*, *advisory* or *risk
preference*), what it needs to be evaluated, and whether AETHERION can evaluate it at all. Anything unclear becomes a question (how many bars
define "the swing low"?); a clause with no evaluator is kept as **unsupported** and is never counted as a pass. Only **Save Setup** followed by the confirmation box persists it.
The list on the right shows each saved setup (symbol, name, direction, timeframes, status, updated, rules) with **View**, **Edit** (re-describe it;
saves the next version), **On/Off** (kept, not deleted), **Delete** and **Clear all** (both ask first; Clear all shows the count and what it does
not touch).

**Where it lives.** `~/.aetherion/setups.sqlite`, a database of its own (transactional, WAL, schema-versioned, every statement filtered by profile). It is
separate from `trading_memory.md`, `decision_outcomes.jsonl`, the audit log and everything else; Clear all removes only the setup rows of your profile.
The profile is `AETHERION_PROFILE` (default `default`): one profile cannot read, edit or delete another's setups.

**US data releases (CPI, jobs report, FOMC).** An event filter such as "no entries 30 minutes before or after CPI, NFP or FOMC" (rule `event_blackout`) is
checked against the official calendars: the BLS release feed (CPI and the Employment Situation report) and the Federal Reserve's FOMC page (the decision
at 2:00 pm New York). BLS refuses anonymous clients, so set a contact address in `~/.aetherion/.env`:

```dotenv
AETHERION_CONTACT_EMAIL=you@yourdomain.com
```

| Variable | Needed | What it does |
|---|---|---|
| `AETHERION_CONTACT_EMAIL` | for CPI and jobs dates | A mailbox BLS can reach. It is sent only in the User-Agent of the request to bls.gov, never anywhere else. Without it the BLS part is not fetched and the rule is *not evaluable*. Use a real address you control; `example.com` addresses do not receive mail |

Alternatively, list the release times yourself in `~/.aetherion/macro_events.json` (ISO datetimes, or `{"name": "CPI", "time": "2026-11-10T08:30:00"}`; a
time without a zone is New York time); when that file exists it replaces both sources. If the calendar cannot be read, is only half read (BLS without the Fed
page, or the reverse) or has no release still ahead, the rule is *not evaluable* and the setup does not match: an unknown calendar is never a pass.

**How it enters a cycle.** Two LangGraph nodes, no second engine:

```
regime -> setups -> technical / fundamental / sentiment / macro -> bull -> bear -> research manager -> trader -> setup_gate -> baseline -> risk committees -> risk manager -> ...
```

`setups` loads the enabled setups for each analysed symbol (a symbol only ever gets its own) and evaluates them with deterministic code against
closed candles, so the analysts and the bull/bear researchers see the user's rules and where they stand (structured fields only, never the free
text). `setup_gate` judges the trader's ideas: a matching strict setup attributes the idea to itself (setup id, version and the evidence are recorded
in the idea, the decision and the audit log); if a symbol has strict setups and none matches, or a required rule cannot be evaluated or is unsupported,
that idea is dropped with the reason (**missing data never counts as a match**); advisory setups only annotate. Size preferences can only shrink an
idea. Match states: `matched`, `not_matched`, `not_evaluable`, `unsupported_rule`.

**What stays in charge.** A setup is a preference, not an order. It cannot create a trade, turn a HOLD into a BUY, or relax the risk gates
(position, exposure, leverage, VaR, shock, drawdown and daily-loss limits), the stale-data and stale-entry guards, strategy governance, broker
constraints or human approval: the gate sits before them and its output cannot reach them. The baseline (TSMOM) ideas are added after the gate and are
never labelled as coming from a setup. With no saved setups both nodes change nothing. A setup's text and the model's answer are untrusted: the text is
passed to the interpreter as data, every rule is validated against the schema (unknown parameters, NaN/Infinity and out-of-range values are refused),
and nothing the model says can authorise an order.

**Supported rule types:** `trend` (EMA 20 vs 50 and slope), `price_vs_ema` (above, below, pulled back to), `rsi_range`, `confirmation_candle`,
`volume_ratio_min`, `regime_in`, `session_window` (UTC), `reward_risk_min` and `stop_beyond_swing` (judged on the trader's actual proposal), plus three that come
from the finance-skills ports below: `trend_template` (Minervini's conditions 1-7 on daily candles), `liquidity_min_adtv` (average daily dollar volume) and
`earnings_blackout` (a stock's earnings dates from Yahoo Finance; non-stocks have none, and an unreachable calendar is *not evaluable*, never a pass). Macro
releases (`event_blackout`: CPI, FOMC...) have no calendar and free-form clauses are recorded as unsupported.

| Endpoint (`/api/setups`) | What it does |
|---|---|
| `POST /interpret`, `POST /validate` | text to candidate rules / check a candidate; never persist |
| `POST /` | save a validated candidate; needs `"confirmed": true` |
| `GET /`, `GET /{id}`, `GET /applicable/{symbol}` | list, read, and the enabled setups that apply to a symbol |
| `PUT /{id}` | save an edit as the next version; needs `confirmed`, optional `expected_version` (409 if stale) |
| `POST /{id}/enabled` | enable or disable (idempotent) |
| `DELETE /{id}?confirm=true` | delete one (deleting twice is not an error) |
| `POST /clear` | remove all of this profile's setups; needs the phrase `CLEAR ALL SETUPS` and the `expected_count` you were shown |
| `POST /{id}/preview` | how the setup reads the market now (market-data rules only) |

Requests are size-bounded and typed, the profile comes from the `X-Aetherion-Profile` header, and the API's own protections (loopback host check, optional
`AETHERION_API_KEY`, cross-origin refusal) apply.

## Ideas from finance-skills (SEPA, liquidity, earnings)

Three methods from [himself65/finance-skills](https://github.com/himself65/finance-skills) (MIT licence) are re-implemented here as plain, tested Python with no
model call and no extra dependency (the skills themselves are prompts for a tool-using assistant and are not vendored):

| Module | What it does | Where it is used |
|---|---|---|
| `analytics/sepa.py` | Minervini's 8-condition trend template (each `passed` / `failed` / `unknown`; without a relative-strength rating condition 8 is unknown and the template does not qualify) and the risk-based position-size rule (shares = equity x risk % / stop distance) | setup rule `trend_template` |
| `analytics/liquidity.py` | average daily dollar volume, Amihud illiquidity, a high-low spread estimate, square-root market impact and a grade | setup rule `liquidity_min_adtv`, and the **liquidity cap** below |
| `data/events.py` | earnings dates (Yahoo Finance, cached for 6 hours; non-stocks have none) | setup rule `earnings_blackout` |

The **liquidity cap** is optional and off by default (tab 9, Risk: *Liquidity cap* and *share of daily dollar volume*): a risk gate placed after base sizing that
reduces an order to at most that share (1% by default) of the asset's average daily dollar volume, measured from the cycle's own candles. It only ever shrinks a
position; when liquidity cannot be measured it changes nothing. Not ported: the VCP / base-pattern detection, the fundamentals checks, and the data-provider and
social-reader skills. Educational and informational only, not investment advice.

## Sharing executed trades (optional)

Off by default. Tab 9 → **Sharing** → "Share executed trades on social media". Switching it on first verifies your credentials with each
provider; if no channel authenticates the switch is refused, and every post re-checks authentication. One working channel is enough; a
channel that is missing any of its variables is simply skipped.

Credentials live **only** in `~/.aetherion/.env` (never in the repository, the logs or the terminal). The file is re-read on every check, so
you can add keys while Aetherion is running and switch sharing on without a restart. Copy the block for each channel you want:

```dotenv
# X (Twitter): all four are required. Create an app at developer.x.com with read and write permission, then generate the access token and secret
AETHERION_X_API_KEY=
AETHERION_X_API_SECRET=
AETHERION_X_ACCESS_TOKEN=
AETHERION_X_ACCESS_SECRET=

# Facebook Page: both required (a Page access token with pages_manage_posts)
AETHERION_FACEBOOK_PAGE_ID=
AETHERION_FACEBOOK_PAGE_TOKEN=

# WhatsApp Cloud API: all three required
AETHERION_WHATSAPP_PHONE_ID=
AETHERION_WHATSAPP_TOKEN=
AETHERION_WHATSAPP_TO=

# Instagram: all three required (Instagram publishes images only)
AETHERION_INSTAGRAM_USER_ID=
AETHERION_INSTAGRAM_TOKEN=
AETHERION_INSTAGRAM_IMAGE_URL=

# Slack: both required (a Slack app's bot token with the chat:write scope, and the channel the bot was invited to)
AETHERION_SLACK_BOT_TOKEN=
AETHERION_SLACK_CHANNEL=
```

| Channel | Variables (all required) | What each one is |
|---|---|---|
| X (Twitter) | `AETHERION_X_API_KEY`, `AETHERION_X_API_SECRET`, `AETHERION_X_ACCESS_TOKEN`, `AETHERION_X_ACCESS_SECRET` | the app's API key and secret and the account's access token and secret (OAuth 1.0a, user context). Long posts become a thread |
| Facebook Page | `AETHERION_FACEBOOK_PAGE_ID`, `AETHERION_FACEBOOK_PAGE_TOKEN` | the Page's numeric id and a Page access token |
| WhatsApp | `AETHERION_WHATSAPP_PHONE_ID`, `AETHERION_WHATSAPP_TOKEN`, `AETHERION_WHATSAPP_TO` | Cloud API phone-number id, its access token, and the recipient's number (international format, digits only). Free text only reaches a recipient inside WhatsApp's 24-hour window |
| Instagram | `AETHERION_INSTAGRAM_USER_ID`, `AETHERION_INSTAGRAM_TOKEN`, `AETHERION_INSTAGRAM_IMAGE_URL` | the Instagram business account id, its access token, and a public image URL: Instagram only publishes images, so the post is the caption of that image |
| Slack | `AETHERION_SLACK_BOT_TOKEN`, `AETHERION_SLACK_CHANNEL` | the bot token (`xoxb-...`) and the channel id (`C0123...`) or name; invite the bot to the channel (`/invite @your-bot`). Authentication is checked with `auth.test` |

If switching sharing on is refused, the message under the table in tab 9 names the missing variable or the provider's refusal (for example
Slack's `invalid_auth` or `channel_not_found`), never the credential itself. Names must match exactly, with no spaces around the `=`.

Each executed entry produces one short post: decision, entry/stop/target, confidence, the idea, headlines and a one-line analysis, with
emoji and a paper-trading disclaimer. `GET /api/social` shows which channels are configured and authenticated.
