Avanza-MCP
# Avanza-MCP
> [!WARNING]
> This is an experimental project. Use it at your own risk.
>
> Always start in **read-only** mode and **paper trading** mode first. Do not enable live write/trading until you have validated behavior end-to-end in paper mode.
>
> Notes below explain what paper trading mode is for and how to use it safely.
Single-script CLI + Textual TUI for Avanza portfolio monitoring, regular buy/sell orders, stop-loss management, MCP integration, and paper trading.
Provider-neutral agent context starts at `INSTRUCTIONS/PROVIDER_ENTRYPOINT.md`.
The root `AGENTS.md`, `CLAUDE.md`, and `GEMINI.md` files are symbolic links to
that single source, so Codex, Claude Code, and Gemini CLI receive the same
repository rules. Cross-client work is handed off through the ignored local
`INSTRUCTIONS/SESSION_HANDOFF.md`; see [LLM Client Interoperability](docs/clients.md).
Trading-assistant context lives in:
- `INSTRUCTIONS/INSTRUCTIONS.md`: standing operating rules and safety constraints.
- `INSTRUCTIONS/MEMORY.md`: timestamped lessons, mistakes, strategy updates, and checklist changes. This is historical context, not live portfolio state.
- `INSTRUCTIONS/WARMUP.md`: prompt for starting a fresh Codex trading session with the right context.
The `INSTRUCTIONS/` folder is kept visible in git, but its private contents are ignored and should remain local-only.
For trading analysis, always refresh live Avanza MCP data. Do not treat markdown memory as current holdings, orders, prices, account IDs, or stop-loss IDs.
Credentials are prompted at runtime:
- username: visible prompt, unless passed with `--username`
- password: masked
- current TOTP code: masked
Alternatively, pass `--onepassword-item ITEM` and optional `--onepassword-vault VAULT` to read the Avanza username, password, and current TOTP code through the 1Password CLI (`op`). The TUI has a matching `Login with 1Password` path. The tool does not store these secrets; `op` will ask you to authorize access through the local 1Password app.
The current TOTP code is passed to `avanza-api` as `totpToken`, which is the field name expected by the installed library version.
**TUI Demo:**
<img width="2935" height="1507" alt="TUI" src="https://github.com/user-attachments/assets/c905313c-d719-4c42-b546-78d116cfda2d" />
## Quick Start
```bash
# 1. Install uv (once), then dependencies
curl -LsSf https://astral.sh/uv/install.sh | sh
uv sync
# 2a. Run the terminal UI ...
uv run python avanza_cli.py tui
# 2b. ... or the browser UI (prints a one-time access token — paste it
# into the login form that opens; then sign in to Avanza from there)
uv run python avanza_cli.py web
# 2c. ... or scriptable console commands
uv run python avanza_cli.py accounts
uv run python avanza_cli.py portfolio summary
```
Log in with your Avanza username, password, and current TOTP code (or via
the 1Password CLI). **Paper trading mode is on by default** — order and
stop-loss tickets go to a local paper ledger until you explicitly switch to
live mode. The TUI and Web UI are mutually exclusive: run one at a time.
To expose the MCP tools to an agent: log in (TUI or Web), enable the MCP
toggle, then register `python avanza_cli.py mcp` in your MCP client — see
"MCP Server Registration & Run" below.
## Setup
```bash
uv sync --dev
chmod +x scripts/verify.sh .githooks/pre-commit .githooks/pre-push
git config core.hooksPath .githooks
```
If `uv` is not installed yet, install it first:
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
Command examples below can be run either inside an activated environment or by prefixing with `uv run`.
For example: `uv run python avanza_cli.py tui`.
Run the full quality gate at any time:
```bash
scripts/verify.sh
```
The quality gate also validates the private review artifacts without granting
trade authority. The instrument strategy master derives its current instrument
and exact account-position counts from row identities, binds them with SHA-256
identity digests, and requires matching objective, factor, and portfolio-control
scope. No historical instrument or position count is completion authority.
Buy-back governance has two separate evidence
layers: the August 6 daily ledger remains a fixed historical snapshot, while
the latest dated live-coverage artifact is rebuilt from the current dynamic
universe and has no fixed candidate count. The live validator derives account,
one-share, low-exposure, full-exit, state, and percentage-coverage totals from
its rows and rejects copied percentage vectors across different instruments.
It also checks
the authoritative factor, pending-order, capital-displacement, and
risk-governance overlays and ensures the objective-completion audit remains
explicitly open while live and forward evidence are missing. It also checks
the 12-measure forward KPI coverage audit and refuses to infer current returns,
benchmark results, drawdown, participation, or lifecycle outcomes from stale
or mixed-session snapshots. Generated portfolio artifacts are analysis
context, not broker instructions. Every
strategy-master account row also carries exact tenant/account/orderbook scope.
The clean-sheet, factor, pending-order, displacement, risk, and live
reconciliation overlays are checked for the same scope and authority flags.
They also carry explicit `STAMPED_ANALYSIS_SNAPSHOT` freshness metadata and
cannot claim current live state or authorize action without a new exact scoped
refresh.
Every current buy-back row carries an explicit protection classification,
coverage state, low-exposure decision, and exact next gate. Unsupported stages
remain `PERCENTAGE_NOT_SET`; an ordinary broker BUY row is not promoted into a
ladder. The fixed historical ledger still carries explicit promotion and
rejection/hold evidence and is never presented as current coverage.
Transaction history is audited independently: historical summary coverage,
manual sold slices, raw-source availability, and same-day BUY attribution are
kept separate, with missing recent/raw evidence failing closed.
The latest ignored `PORTFOLIO_FULL_HISTORY_CANONICAL_*` and
`PORTFOLIO_FULL_DYNAMIC_GOVERNANCE_MIRROR_*` artifacts additionally preserve
every current source identity, effective lineage, immutable sale lot, recovery
allocation, terminal closure, and official-close reachability row. Their
validator checks current raw-boundary parity and rejects omissions, duplicate
or mutated transaction identities, over-allocation, mixed attributed/unattributed
sources, stale terminal decisions, and rebounds that conceal an unserved
crossing. These files are audit evidence only and never authorize an order.
The scheduler ledger is separately checked for its 18-row Approval C queue,
non-terminal check windows, and explicit active/archive gaps.
Catalyst coverage separately prevents estimated dates or stale scanner labels
from being treated as verified issuer publication.
## Commands
Console commands print human-readable Rich tables and summaries, not raw API payloads.
Show running app version:
```bash
python avanza_cli.py --version
```
Launch the browser trading console (see the Web UI section):
```bash
python avanza_cli.py web
```
Show account overview:
```bash
python avanza_cli.py accounts
```
Show portfolio summary:
```bash
python avanza_cli.py portfolio summary
```
Show detailed portfolio positions:
```bash
python avanza_cli.py portfolio positions
```
Show transaction history (executed orders by default):
```bash
python avanza_cli.py transactions list
```
Pull broader history:
```bash
python avanza_cli.py transactions list --all --max-elements 5000
```
Search for a stock/order book:
```bash
python avanza_cli.py search-stock "VOLV B"
```
List active stop-loss orders:
```bash
python avanza_cli.py stoploss list
```
Dry-run a trailing/gliding sell stop-loss:
```bash
python avanza_cli.py stoploss set \
--account-id ACCOUNT_ID \
--order-book-id ORDER_BOOK_ID \
--trigger-type follow-upwards \
--trigger-value 5 \
--trigger-value-type % \
--order-type sell \
--order-price 1 \
--order-price-type % \
--volume 10
```
Place the order for real by adding `--confirm` after reviewing the dry-run output.
If `--valid-until` is omitted, `avanza_cli` automatically uses the longest currently allowed date (today + 90 days).
If `--order-valid-days` is omitted, `avanza_cli` uses the current Avanza-safe default (`1`).
Dry-run/preview now shows both trigger validity and derived triggered-order expiry (`if triggered today`).
For live non-SEK/foreign instruments, `order_valid_days > 1` is blocked to prevent Avanza `Ogiltigt giltighetsdatum` trigger failures.
Delete a stop-loss order dry-run:
```bash
python avanza_cli.py stoploss delete \
--account-id ACCOUNT_ID \
--stop-loss-id STOP_LOSS_ID
```
Delete for real by adding `--confirm`.
List open regular orders:
```bash
python avanza_cli.py orders list
```
Dry-run a regular buy/sell order:
```bash
python avanza_cli.py orders set \
--account-id ACCOUNT_ID \
--order-book-id ORDER_BOOK_ID \
--order-type buy \
--price 100 \
--valid-until 2026-05-28 \
--volume 10 \
--condition normal
```
Delete an order dry-run:
```bash
python avanza_cli.py orders delete \
--account-id ACCOUNT_ID \
--order-id ORDER_ID
```
## Textual TUI
Run the terminal UI from the same script:
```bash
python avanza_cli.py tui
```
The TUI masks password and TOTP inputs, clears those fields after a successful login, and hides the login screen. You can also enter a 1Password item name/ID and optional vault, then use `Login with 1Password` to let the local `op` CLI fetch username, password, and TOTP after your 1Password approval. Use `Review Only` first to validate and log an order request without creating a paper or live order.
Use the `Reload TUI` top-button to hard-restart `python avanza_cli.py tui` with the same arguments and load latest code changes without manually quitting/relaunching.
The bottom status bar includes an automatic GitHub update checker indicator; when a newer release exists, it flashes a warning status.
Controls: `AVANZA_UPDATE_CHECK_ENABLED=0` disables checks, `AVANZA_GITHUB_REPO=owner/repo` overrides source.
After login, the largest account by total value is selected by default. The top panel groups account metrics into colored cards, keeps action buttons together, and shows a live clock plus a weekday OMXS open/close countdown. The P/L metric cycles through `1D P/L`, `1W P/L`, `1M P/L`, `1Y P/L`, `Since Start P/L`, and `Total P/L`, with SEK and % values colored separately. The main table shows the selected account's stocks with day movement, profit state, a distinct header row, and a real-time quote indicator: green dot for real-time, yellow dot for delayed or unresolved status. The order ticket searches as you type by stock name, ticker, or ISIN, so it supports opening new positions as well as trading current holdings. The lower table shows stop-losses and open orders for the selected account, with trigger and price values labeled as `SEK` or `%`; its cancel column opens a guarded cancellation ticket. Buy/sell side cells are color-coded green/red. Click any table column header to sort by that column; click the same header again to reverse the order. Drag the horizontal divider between tables, the vertical divider beside Active Trades, or the left edge of the order/stop-loss ticket to resize panes. Position and order state refreshes live every 5 seconds.
Multi-session mode: use **Login extra account** to add more authenticated Avanza sessions without leaving TUI. The session selector lets you switch tenant context quickly; account drop-down and all tables follow the selected session. Background refresh keeps inactive tenant/account snapshots warm without visibly switching the active TUI context.
## Web UI
Run the browser-based trading console from the same script:
```bash
python avanza_cli.py web # 127.0.0.1:8787, opens the browser
python avanza_cli.py web --port 9000 --no-browser
```
The Web UI has feature parity with the TUI — portfolio with live 5-second
updates, order and stop-loss tickets (dry-run → review → typed `PLACE`),
guarded cancellations, multi-tenant session switching with re-auth, MCP
bridge management (bridge/R-W/live-authorization controls, token and proxy
command, streaming tool log), a dedicated Paper trading workspace,
TradingView lists, source-ranked research candidates, performance charting,
orders/transactions history, and a full-page configured stop-loss view
beside the compact dashboard stop-loss pane.
When using 1Password CLI login, the Web UI can remember browser-local
profiles containing only the 1Password item name, optional vault, and display
label, so future primary or extra-session logins can pull credentials from
1Password without retyping the item name.
The dashboard keeps view and trade actions in a second top-toolbar row;
Activity and MCP Live logs sit under Ongoing Orders, scroll independently,
and the main/side, portfolio/order/log, and Activity/MCP splits can be
drag-resized and are remembered locally.
It binds `127.0.0.1` only and is protected by a startup access token,
cookie session, CSRF header, and a strict CSP (self + two pinned CDN files
with SRI). See `docs/web.md` for the full security model, endpoint table,
and smoke checklist.
The Web UI and the TUI are mutually exclusive: one at a time per checkout
(enforced via `.avanza_ui.lock`). Both manage the same MCP bridge contract,
so `python avanza_cli.py mcp` works identically against either.
## MCP Server Registration & Run
This project exposes MCP through `python avanza_cli.py mcp` (stdio transport).
### 1) Start and authenticate the TUI
```bash
python avanza_cli.py tui
```
Log in, then enable the `MCP` tick box in the TUI. This starts the localhost bridge and writes `.avanza_mcp_session.json`.
By default, the MCP bridge token is saved to macOS Keychain (`security` CLI) and only metadata is written to the dotfile; if keychain is unavailable, it falls back to file storage. Override with `AVANZA_MCP_SESSION_BACKEND=keychain|file|auto` (default `auto`).
### 2) Register the MCP server in your client
Codex, Claude Code, and Gemini CLI all support the same local stdio proxy. The
recommended absolute-path command is:
```bash
uv run --project /ABSOLUTE/PATH/TO/avanza-mcp \
python /ABSOLUTE/PATH/TO/avanza-mcp/avanza_cli.py mcp
```
Registration commands and the provider-neutral handoff workflow are documented
in [LLM Client Interoperability](docs/clients.md).
### 3) Run from your MCP client
After registration, start or reload the selected MCP client. It will launch the
same proxy regardless of provider:
```bash
python avanza_cli.py mcp
```
The MCP proxy forwards tool calls to the authenticated TUI session through the localhost bridge. MCP starts read-only. Enable `Live R/W` in the TUI for live mutations; live stop-loss/order placement, edit, or deletion still requires MCP arguments to include `confirm: true`. MCP activity is shown in the lower-right log console.
For multi-session setups:
- use `avanza_sessions` to inspect loaded tenant sessions,
- use `avanza_select_session` to switch active context,
- account/session-context `avanza_*` tools accept optional `tenant_session_id` for explicit tenant routing.
- or pass `account_id` and the bridge auto-scopes to the session owning that account.
- paper-ledger tools use `session_id` for paper ledger grouping; use `tenant_session_id` when explicit tenant routing is required.
### Available MCP Tools
| Tool | Purpose |
|---|---|
| `avanza_status` | Show TUI MCP bridge status, selected account, and current safety mode. |
| `avanza_capabilities` | Return consolidated MCP safety/capability status for automation loops (paper/live guards, account context, and tool availability). |
| `avanza_live_session_authorize` | Explicitly enable live mutation permission for this active MCP or TUI session. |
| `avanza_live_session_revoke` | Disable live mutation permission for this MCP or TUI session and force paper-only mode. |
| `avanza_accounts` | List Avanza accounts currently visible to the authenticated TUI session. |
| `avanza_sessions` | List loaded authenticated Avanza tenant sessions in the running TUI. |
| `avanza_select_session` | Switch active MCP/TUI tenant session context. |
| `avanza_select_account` | Safely switch MCP or TUI selected account context. |
| `avanza_account_performance` | Read Avanza account performance/development for the selected or supplied account_id over a chosen period. |
| `avanza_instrument_chart` | Read authenticated daily or intraday Avanza OHLC history for one order book; read-only analysis input. |
| `avanza_account_cost_attribution` | Replay cash-flow-adjusted account return with posted commission and modeled FX removed; read-only and fail-closed on truncated history. |
| `avanza_frozen_holdings_attribution` | Reconstruct a frozen starting-holdings path from authenticated daily prices and account history; read-only and fail-closed on missing identity, prices, cash events, or truncated history. |
| `tv_scrape_symbol_analytics` | Fetch TradingView symbol analytics and technical recommendation barometers from public scanner data. |
| `tv_scrape_symbol_full` | Fetch rich TradingView symbol payload (scanner analytics + technical labels + symbol profile metadata) in LLM-friendly JSON. |
| `tv_auth_session_start` | Open TradingView login page in browser and show session setup instructions for authenticated MCP usage. |
| `tv_auth_session_set` | Persist TradingView session cookie for authenticated tv_auth_* MCP tools. |
| `tv_auth_session_login_auto` | Open instrumented browser, let user log in normally, and automatically capture/save TradingView session cookies. |
| `tv_auth_session_status` | Show saved TradingView authenticated session status used by tv_auth_* tools. |
| `tv_auth_session_clear` | Delete saved TradingView authenticated session cookie. |
| `tv_auth_symbol_analytics` | Fetch TradingView symbol analytics in authenticated mode (inherits account entitlements from supplied TradingView cookie/session). |
| `tv_auth_symbol_full` | Fetch rich TradingView symbol payload in authenticated mode (scanner analytics + technical labels + profile metadata + entitlement context). |
| `tv_preopen_symbol_snapshot` | Return a compact TradingView pre-open/extended-hours snapshot for one symbol. |
| `tv_preopen_batch_snapshot` | Return TradingView pre-open snapshots for a symbol list with per-symbol error isolation. |
| `tv_scrape_heatmap` | Fetch TradingView market heatmap rows with exchange, OTC, liquidity, market-cap, price, and sort filters. |
| `avanza_tv_preopen_portfolio_bundle` | Read-only bundle that merges Avanza portfolio state with TradingView context; exact SELL targets are optional and never inferred from full holdings. |
| `tv_auth_watchlist` | Best-effort TradingView watchlist monitor in authenticated mode (cookie/session required for private list context). |
| `tv_auth_custom_lists` | Load authenticated TradingView custom tracking lists and rows from your TradingView profile session. |
| `zacks_scrape_symbol` | Fetch Zacks rank via quote-feed and scrape symbol/report pages for Earnings ESP plus visible analysis text (best effort; HTML may be blocked). |
| `fmp_analyst_recommendations` | Fetch analyst recommendation history for a symbol from Financial Modeling Prep (requires FMP API key). |
| `polygon_analyst_insights` | Fetch analyst insights/ratings for a symbol from Polygon Benzinga feed (requires Polygon API key). |
| `sec_filings_recent` | Fetch recent SEC EDGAR filings by ticker or CIK (official SEC data). |
| `fred_series` | Fetch FRED macro observations (requires a free FRED API key via FRED_API_KEY or api_key input). |
| `data_source_status` | Return current health, freshness, and safety flags for Avanza, TradingView, Zacks, FMP, Polygon, SEC, and FRED source integrations. |
| `signal_context_bundle` | Build a compact cross-source signal bundle (TradingView technicals + SEC filings + optional Zacks/FMP/Polygon + optional FRED macro). |
| `avanza_portfolio` | List portfolio positions, optionally filtered by one instrument and compact mode. |
| `avanza_stoplosses` | List stop-loss orders with durable strategy intent and missing/mismatch metadata audit, optionally filtered by instrument, side, status, and compact mode. |
| `avanza_stoploss_strategy_audit` | Refresh active broker stops and verify that each exact row reloads with matching durable local strategy metadata. |
| `avanza_stoploss_strategy_register_batch` | Dry-run or atomically register reviewed intent for exact active broker rows; changes only the local registry, never Avanza. |
| `avanza_position_strategy_audit` | Refresh exact holdings plus aggregate active-stop/open-order exposure, return the read-only `event_protection_screen`, and fail closed when a reviewed per-position plan or explicit protection classification is missing, stale, contradictory, or marked `REPAIR_REQUIRED`. |
| `avanza_position_strategy_register_batch` | Dry-run or atomically register reviewed per-position plans and instrument-specific protection reasons against exact live account state; an explicit preservation flag can update semantics without rebaselining a holding-only exception, and changes only the local registry, never Avanza. |
| `avanza_open_orders` | List live open/pending regular orders, optionally filtered by instrument, side, or status. |
| `avanza_open_orders_raw` | Debug tool for normalized open orders plus optional raw Avanza order payload. |
| `avanza_ongoing_orders` | List ongoing orders for the selected account: live stop-losses + live open orders, with optional paper active orders. |
| `avanza_transactions` | List executed orders/history with optional account/date/type/instrument filters; `include_raw` opt-in preserves the unnormalized broker payload for read-only evidence. |
| `avanza_live_snapshot` | Read a decision-ready polling snapshot, with optional compact instrument filtering. |
| `avanza_position` | Read one account position by orderbook ID. |
| `avanza_instrument_stoplosses` | Read stop-loss rows for one instrument/account. |
| `avanza_instrument_open_orders` | Read open/pending regular orders for one instrument/account. |
| `avanza_instrument_transactions` | Read transactions for one instrument/account. |
| `avanza_instrument_state` | Read one instrument's quote, position, stops, orders, transactions, and mechanical full-holding diagnostic. |
| `avanza_protection_gaps` | Audit exact strategy SELL targets, failed SELL stops, and overcoverage without inferring a full-core exit. |
| `avanza_sold_today_buyback_state` | Summarize same-day sold instruments using fail-closed recovery attribution; same-day BUY fills offset sales, while pre-existing/generic BUY stops and unattributed regular BUY orders remain conditional exposure rather than assumed repair. |
| `avanza_recovery_reachability` | Audit every active BUY row for practical fixed-price distance, secondary/deep residuals, reversal-trigger width, and recovery coverage, while separately reconciling explicit named, locked, secondary, or dormant position-plan semantics; raw issues remain visible and thresholds are never placement advice. |
| `avanza_recent_fills_needing_protection` | Review recent BUY fills; report a SELL gap only against an explicit percentage or exact strategy target. |
| `avanza_verify_no_raw_failed_orders` | Compact post-mutation check for failed/rejected open orders. |
| `avanza_verify_protection` | Verify exact strategy SELL targets; default mode checks failed SELL rows and overcoverage only. |
| `avanza_realtime_quotes` | Fetch real-time quote snapshot for selected account holdings (best with a 5s polling loop). |
| `avanza_orderbook_quotes` | Fetch arbitrary quote snapshots for supplied orderbook IDs (supports 5s polling loops for 20-50 symbols). |
| `avanza_market_movers` | Fetch Avanza market movers (gainers/losers) with optional country/market/turnover filters. |
| `avanza_index_constituents` | Fetch index constituents (default OMXS30) with optional quote/spread enrichment for building a liquid scalp universe. |
| `avanza_fee_estimate` | Estimate courtage/FX costs and break-even move for a planned trade (conservative assumptions when exact class data is unavailable). |
| `avanza_search_stock` | Search Avanza stock/order book data by name, ticker, or ISIN. |
| `avanza_paper_stoploss_set` | Create a local paper stop-loss order. |
| `avanza_paper_orders` | List local paper-trading orders and events for the selected account, or a supplied account_id. |
| `avanza_paper_positions` | List paper positions for a selected account/session, with optional active-only filter. |
| `avanza_paper_trades` | List completed paper trades (entry+exit ledger rows) for account/session. |
| `avanza_paper_session_summary` | Return P/L summary for a paper trading session/account. |
| `avanza_paper_order_set` | Create a local paper buy/sell order. |
| `avanza_paper_order_exit` | Close an open paper position by position_id or orderbook_id and create a completed paper trade entry. |
| `avanza_paper_risk_state` | Evaluate paper-session guardrails before allowing a new trade entry. |
| `avanza_scalp_watchlist_set` | Store/update a named scalp watchlist (orderbook IDs + optional labels) in local paper session state. |
| `avanza_scalp_watchlist_get` | Load a named scalp watchlist and optionally include current quotes for all members. |
| `avanza_paper_cancel` | Cancel a local paper order. |
| `avanza_stoploss_set` | Dry-run or place a stop-loss order; live MCP placement requires an auditable strategy intent and reason. |
| `avanza_stoploss_set_batch` | Place multiple stop-loss orders with per-item strategy-intent validation and final readback. |
| `avanza_order_set` | Dry-run or place a regular buy/sell order. |
| `avanza_order_edit` | Dry-run or update an existing open order (price/volume/valid_until). |
| `avanza_open_order_edit` | Dry-run or update an existing open/pending regular order. |
| `avanza_order_delete` | Dry-run or delete a regular open order. |
| `avanza_open_order_cancel` | Dry-run or cancel an existing open/pending regular order. |
| `avanza_stoploss_delete` | Dry-run or delete a stop-loss order; live deletion requires intent/reason matching the exact durable stop metadata. |
| `avanza_stoploss_edit` | Dry-run or edit an existing stop-loss (delete old + place new). |
Canonical naming note:
- use `avanza_open_orders`
- use `avanza_stoplosses`
Stop-loss strategy metadata is stored locally in
`.avanza_stoploss_strategy.json` with owner-only permissions. The registry is
keyed by account and stop ID and fingerprints the complete broker row. Missing
or changed broker fields produce `MISSING` or `STALE_MISMATCH`; the old intent
is not trusted or silently transferred. This registry records why an order
exists, but never authorizes a broker mutation.
Reviewed whole-position plans are stored separately in
`.avanza_position_strategy.json`, also with owner-only permissions. Each
account-plus-orderbook record binds the intended thesis, horizon, priority,
strategy gate, and reviewed aggregate holding/stop/open-order exposure to exact
live state. A fill, holding change, stop edit, leftover order, missing plan, or
stale plan makes `avanza_position_strategy_audit` fail closed until the change
is reviewed. Rebaselining records a decision; it never authorizes a trade.
Intentional holding-only drift may be documented with an `audit_exception` whose
kind is `USER_CONTROLLED_ALLOCATION` or `POST_MANUAL_EXIT_DRIFT`, with an owner,
reason, review point, and `allowed_mismatches: ["holding"]`. The audit must still
remain incomplete, `rebaseline_authorized` is always false, and stop, order, or
other exposure drift is never acknowledged by this mechanism.
For an existing reviewed holding-only exception,
`avanza_position_strategy_register_batch` may set
`preserve_audit_exception_fingerprint: true` on that exact row to update only
semantic plan fields. The request must still match current live state exactly;
the stored fingerprint must differ only in `holding`; the existing exception
must explicitly set `rebaseline_authorized: false`; and supplied exception
metadata must be unchanged. Missing exceptions, no actual holding drift, or any
stop/open-order mismatch are rejected atomically. This remains a private
registry write and never mutates or authorizes Avanza.
Every reviewed row also requires one exact `protection_classification`:
`CALIBRATED_STOP_PROFIT_LADDER`, `CORE_HOLD_EXCEPTION`, `MARKER_EXCEPTION`,
`NAMED_EXCEPTION`, `NON_STOP_ELIGIBLE`, or `REPAIR_REQUIRED`, plus an
instrument-specific `protection_reason`. The registry validates the class
against current SELL exposure and live holding size. Missing or contradictory
metadata is rejected; `REPAIR_REQUIRED` is durable but blocks governance
completion. A valid acknowledged holding-only exception can therefore remain
strictly fingerprint-incomplete while still being governance-complete, without
silently changing its stored fingerprint.
The private `output/PORTFOLIO_GOVERNANCE_REVIEW_STREAK.json` ledger records
each scheduled twice-daily review. Validate it with:
```bash
python3 scripts/verify_governance_review_streak.py
```
The verifier rejects duplicate windows, missing account or gate evidence,
late backfills, false eligibility flags, premature completion claims, and any
review whose exact-account authorization state is not explicitly off. An
existing malformed historical attempt may be preserved only by appending a
current annotation bound to its immutable review ID, index, and canonical
SHA-256 hash; annotations cannot change an attempt to eligible or rewrite the
review. Goal
completion additionally requires ten eligible morning/evening reviews across
five regular market sessions; use `--require-complete` only when asserting that
terminal condition.
Mechanical registry equality does not prove that analysis sources carry the
same meaning. Use `avanza-strategy-audit` to compare every account-position
semantic field and live fingerprint across an instrument master, clean sheet,
and private registry. The command exits nonzero on missing coverage or any
field contradiction and includes source SHA-256 hashes in its JSON result.
`avanza-strategy-sync` is a separate guarded analysis-only maintenance command.
It defaults to dry-run, requires a documented `--reason`, and refuses to
synchronize semantics unless holdings and active BUY/SELL fingerprints already
match. `--confirm` changes only the supplied clean sheet and master; it never
changes Avanza or the private registry and never grants trade authority.
Run `avanza_recovery_reachability` separately for each exact account after
refreshing holdings and stops. Its default `8%` fixed-distance band is
practical participation; `8-15%` is secondary review only, `15%+` is deep
review, and `4%` is the maximum default reversal width. These are review
boundaries, not entry recommendations. A deep- or secondary-only row without
practical participation is blocked as recovery coverage. Event, thesis,
technical, risk, factor, capacity, and full-friction gates still determine
whether a correction is a nearer row or an explicit dormant review with no
active BUY.
The raw `complete`, `review_required`, `issue_count`, and per-instrument
`issues` fields remain mechanical and are never suppressed. The separate
`governance_complete` result is true only when every active BUY instrument has
a current exact-account position plan with a matching active-BUY fingerprint
and every raw issue is explicitly
classified as a named exception, locked residual, secondary review, or dormant
review. Missing or stale plans, `REPAIR_REQUIRED`, unclassifiable rows, live cleanup
requirements, and plan-versus-distance contradictions remain unresolved and
fail closed. An explained issue is review inventory, not practical recovery
coverage and not order authority.
```bash
avanza-strategy-audit \
--master output/PORTFOLIO_INSTRUMENT_STRATEGY_MASTER_20260731.json \
--clean-sheet output/PORTFOLIO_CLEAN_SHEET_POST_MINI_20260731.json \
--registry .avanza_position_strategy.json \
--account-map Personal=5227886 \
--account-map DarkCell=7616265
```
### SEC EDGAR access identity
`sec_filings_recent` uses a provider-specific declared identity for official
SEC requests. It does not reuse the browser-style user agent required by
TradingView. By default, Avanza-MCP derives the contact address from the local
`git config user.email` value without writing that address into the repository.
For a machine without Git contact configuration, set either a contact email or
the complete SEC user agent before starting the TUI/MCP bridge:
```bash
export AVANZA_SEC_CONTACT_EMAIL="admin@example.com"
# Or provide the complete declared identity:
export AVANZA_SEC_HTTP_USER_AGENT="Avanza-MCP/0.2.31 Example Company admin@example.com"
```
The ticker-to-CIK index is cached for 24 hours per process and SEC requests are
serialized below the official 10-requests-per-second ceiling. Advanced local
overrides are `AVANZA_SEC_TICKER_INDEX_CACHE_SECONDS` and
`AVANZA_SEC_REQUEST_MIN_INTERVAL_SECONDS`; the latter is always clamped to at
least `0.1` seconds. Restart the bridge after changing any of these settings.
### TradingView/Zacks scrape mode notes
- These tools are intentionally marked experimental.
- `tv_scrape_*` runs in free anonymous mode.
- `tv_auth_*` supports three auth paths:
- explicit tool input (`cookie` or `sessionid` + `sessionid_sign`),
- environment variables (`TRADINGVIEW_SESSIONID`, optional `TRADINGVIEW_SESSIONID_SIGN`),
- saved local session via `tv_auth_session_set`.
- Saved TradingView session storage defaults to macOS Keychain (`security` CLI) when available, with metadata in `.avanza_tradingview_session.json` (ignored by git). Fallback is file-only storage.
- Optional override: `AVANZA_TV_SESSION_BACKEND=keychain|file|auto` (default `auto`).
- Preferred path: `tv_auth_session_login_auto` to open an instrumented browser and capture cookies automatically after login.
- Browser-assisted flow:
1. call `tv_auth_session_login_auto`,
2. log in normally in opened browser window,
3. wait for auto-capture confirmation, then run `tv_auth_session_status`,
4. use `tv_auth_*` tools with no repeated cookie input.
- The TUI `TradingView Lists` tab uses the same authenticated profile and provides a dedicated custom-list monitor with list switching.
- The Web UI `Research candidates` view uses `/api/recommendations/stocks` to assemble a bounded, read-only candidate list from TradingView movers/technicals and Zacks rank/analysis summaries. Its source controls filter the loaded table instantly without another server request and report compact per-source enrichment health. It is research input only, not an order instruction.
- If auto mode is unavailable, fallback is `tv_auth_session_start` + manual `tv_auth_session_set`.
- If Codex or another agent does not expose `tv_*` tools as direct native calls, keep using the registered `avanza_cli` MCP server and call the tools through the local stdio/TUI bridge. The bridge command remains `python avanza_cli.py mcp`; it forwards to the authenticated TUI localhost session.
- Pre-open workflow:
1. start and authenticate the TUI, then enable MCP,
2. confirm `tv_auth_session_status` if authenticated TradingView data is needed,
3. call `tv_preopen_batch_snapshot` for watchlist/candidate symbols or `avanza_tv_preopen_portfolio_bundle` for an Avanza account review,
4. use `tv_scrape_heatmap` with filters such as `exchanges=["NASDAQ","NYSE","AMEX"]`, `exclude_otc=true`, `min_market_cap`, `min_price`, and `min_volume` to avoid OTC/microcap noise.
- Performance notes: `tv_preopen_batch_snapshot` uses bounded bulk TradingView scanner chunks for normal multi-symbol reviews, preserving per-symbol error isolation for large or mixed-exchange requests; Avanza MCP read tools keep a short in-process account cache to avoid repeated full portfolio/stop/order pulls during focused workflows; `avanza_orderbook_quotes` deduplicates IDs and can skip metadata enrichment when `fields` contains only price fields.
- TradingView extended-hours fields depend on TradingView entitlement/session and scanner availability. `premarket_close`, `postmarket_close`, `update_mode`, and quote freshness warnings are reported explicitly; missing fields are returned as `null` instead of inferred from Avanza.
- `zacks_scrape_symbol` is best effort; it uses Zacks quote-feed data for rank when available, then attempts the quote page and free Zacks equity-report page for visible analysis text. It returns `rank_source`, `quote_feed`, `analysis_summary`, `analysis_sources`, and `blocked_sources`. Zacks HTML pages can still return bot-protection pages unless a valid browser session/cookie is provided.
- Treat scrape output as decision support only. Keep live mutations behind Avanza read/write + explicit `confirm: true`.
- API-key tools:
- `fmp_analyst_recommendations`: pass `api_key` or set `FMP_API_KEY`.
- `polygon_analyst_insights`: pass `api_key` or set `POLYGON_API_KEY`.
### MCP Transaction History Quick Use
Use `avanza_transactions` to retrieve executed order history (BUY/SELL by default).
- Most recent 15 rows:
- `{"max_elements": 15}`
- Include dividends and interest:
- `{"types": ["BUY", "SELL", "DIVIDEND", "INTEREST"], "max_elements": 200}`
- Full export window:
- `{"transactions_from": "2026-01-01", "transactions_to": "2026-12-31", "executed_only": false, "max_elements": 5000}`
`avanza_transactions` is read-only and works while MCP remains read-only.
When preserving transaction evidence, pass `include_raw: true` and verify the
response contains `raw_payload`. `avanza_capabilities` also reports
`contract_features.transactions_include_raw`; a missing feature indicates that
the loaded MCP runtime must be reloaded before raw-source evidence can be
accepted.
Also verify `mcp_contract_revision` after a reload; the package version alone
is not sufficient to distinguish an older long-running bridge process.
### 4) Remote-only client note
ChatGPT developer mode currently expects remote MCP apps/connectors over SSE or streaming HTTP, so it cannot directly register this local stdio proxy.
For auto-trading experiments, use `avanza_live_snapshot` as the polling tool. It returns a decision-ready account snapshot and is safe to call every 5 seconds. Paper trading is available in read-only MCP mode through `avanza_paper_stoploss_set`, `avanza_paper_order_set`, `avanza_paper_orders`, and `avanza_paper_cancel`; paper state is stored in `.avanza_paper_session.json` and never places an Avanza order. The TUI's `Paper` tick box is on by default; while it is on, the order and stop-loss form submit buttons create local paper orders. Turn `Paper` off only when you intend to use live Avanza placement, which still requires typing `PLACE`. Regular live buy/sell orders are also exposed through `avanza_order_set` and `avanza_order_delete`, gated by MCP R/W mode and `confirm: true`.
TUI sessions write structured JSONL logs under `avanza-cli/logs/`: a timestamped session log plus persistent `app.jsonl`, `mcp.jsonl`, and `trading.jsonl`.
## Safety
This uses the unofficial `avanza-api` package. Start with `stoploss list` and dry-runs. Verify Avanza's live interpretation of `%` and gliding stop-loss fields with very small size before trusting it for meaningful orders.
Stop-losses are not guaranteed earnings-gap or overnight protection. A tight `Kurs 99%` can avoid a bad normal-session fill, but it can also fail, remain unfilled, or show `ERROR` if price gaps through the trigger after hours, before open, during a halt, or in a fast market. Treat `ERROR` rows as unprotected, and handle after-close/before-open catalysts with explicit sizing, trim, sell, hedge, or hold-and-accept decisions.
If an `ERROR` row reason contains `Ogiltigt giltighetsdatum`, the failure is usually triggered-order validity, not trigger logic or slippage. Set `order_valid_days=1` and replace the stop-loss.
## Credits
Creators: **Hamid Kashfi** and **Codex (OpenAI)**.
This project builds on the Python `avanza-api` library by fama93:
- PyPI: [avanza-api](https://pypi.org/project/avanza-api/)
- Source: [github.com/fhqvst/avanza](https://github.com/fhqvst/avanza)
TDQS
Scored across 82 tools
Many tools overlap in scope: avanza_status, avanza_capabilities, and data_source_status all report state, while multiple order and audit tools have near-identical names such as avanza_open_orders vs avanza_ongoing_orders and avanza_open_order_cancel vs avanza_order_delete. The descriptions help, but an agent would frequently struggle to pick the correct tool.
The server broadly uses avanza_* and tv_* prefixes with readable verb_noun names, but consistency breaks down with aliases like avanza_open_order_cancel vs avanza_order_delete, adjective-noun names like avanza_live_snapshot, and mixed provider prefixes like zacks_scrape_symbol vs fmp_analyst_recommendations.
82 tools is far beyond the typical well-scoped MCP server and includes many aliases, per-instrument read variants, and overlapping audit helpers. Even with multiple integrations, the tool surface feels inflated rather than carefully pruned.
Core workflows are well covered: live and paper order lifecycle, stop-loss management, portfolio/position reads, quotes, transactions, account selection, multi-source market data, and strategy audits. Minor gaps exist, such as no dedicated single-order status fetch or paper stop-loss edit tool, but the main strategy-protection workflow has no obvious dead ends.