# StockWise — full documentation Every page of https://iamsaquib8.github.io/stockwise/ in one file, in the order of the site's table of contents. ---
` | `STOCKWISE_QUOTE_CURRENCY` | Currency bare crypto tickers are quoted in with `-m crypto`. Default `usd`; `inr` gives `BTC-INR` |
| `--lang ` | `STOCKWISE_LANG` | Language for help groups, tables and messages. Default: your locale (`LC_ALL` / `LC_MESSAGES` / `LANG` starting with `hi` picks Hindi), else English. `--json` and `--csv` output is never translated |
| `--ai` | `STOCKWISE_AI` | Add an AI section to `analyze`, `technical`, `compare`, `screen`, `backtest`, `deep` (slow with a local model; see [ai.md](https://iamsaquib8.github.io/stockwise/ai.html)) |
| `--no-ai` | | Turn the AI section off for one run, even if `STOCKWISE_AI` or the config file turn it on |
| `--portfolio ` | `STOCKWISE_PORTFOLIO` | Use a named portfolio (`portfolios/NAME.json`) instead of the default `portfolio.json`. Applies to `portfolio`, `import`, `tax`, `wealth`, `dashboard`, `trade holdings` and every other command that reads holdings |
| `--profile ` | `STOCKWISE_PROFILE` | Investor profile mode (`passive`, `active`, `active:low`, `active:med`, `active:high`, `ultra`) and personal guardrails |
Other environment variables: `STOCKWISE_HOME` (data directory),
`STOCKWISE_REDIS_URL` (cache server, or `off`), `STOCKWISE_DATA_PROVIDER`
and its API key ([data-providers.md](https://iamsaquib8.github.io/stockwise/data-providers.html)),
`STOCKWISE_AI_PROVIDER`, `STOCKWISE_MODEL`, `OLLAMA_HOST` and the cloud AI keys
([ai.md](https://iamsaquib8.github.io/stockwise/ai.html)), `STOCKWISE_CONFIG` (config file path), `STOCKWISE_BIND` and
`STOCKWISE_API_TOKEN` (`serve`), and the notification variables
([bots.md](https://iamsaquib8.github.io/stockwise/bots.html#notifications)). `NO_COLOR` disables colour; colour is also
off when output is piped.
Defaults for most of these, and for each command's `-p`, can live in a
[config file](#configuration-file). Precedence: command-line flag >
environment variable > config file > built-in default. API keys and the server
token are read from the environment only.
**Crypto and FX**: prices show with the precision they need (FX pairs 4
decimals, coins under 1 up to 8) and `quote` notes trading hours (crypto 24/7;
FX 24 hours Monday–Friday, closed at weekends). Commands built on exchange
sessions, sectors or fundamentals — `sectors`, `rotation`, `sectorcmp`,
`screen`, `picks`, `matrix`, `surprise`, `peers`, `intraday`, `longterm`,
`daemon`, `sim start` — refuse `-m crypto` / `-m fx` with an explanation;
`movers`, `heatmap`, `history`, `technical`, `backtest` and friends work.
**Global exchanges** (`-m uk|de|jp|hk|sg|ca|au`): quotes, history, charts,
technicals, `movers` (each exchange's large caps), `markets` and alerts work as
for India and the US. Prices show in the local currency (`£`, `€`, `¥`, `HK$`,
`S$`, `C$`, `A$`); London quotes in pence, so `VOD.L` shows `118.20p` while its
market cap shows in pounds. `sectors`, `rotation` and `sectorcmp` need sector
indices or ETFs, which Yahoo has for Japan (TOPIX-17 ETFs), Hong Kong, Canada
and Australia only. Sessions, lunch breaks, half days and holidays come from
each exchange's calendar ([markets.md](https://iamsaquib8.github.io/stockwise/markets.html)). `tax` has rules for India,
the US and the UK only, and names the other listings it leaves out. Backtest
costs default to the US model outside India. The bots and the simulator
(`intraday`, `longterm`, `daemon`, `sim start`, `sim replay`, `sim challenge`,
`report`) size positions from the capital you type, so they refuse `-m uk`,
whose prices are in pence; replays on other exchanges use their main index
as the benchmark.
**Commodities** (`-m commodity`): `GOLD`/`SILVER` resolve to COMEX futures, so
`history gold`, `chart silver`, `technical gold` and `alert add gold above
4200` work; `commodity` and `bonds` have their own commands
([below](#commodities--bonds)).
**Hindi** (`--lang hi`): help group titles and the main command descriptions,
`quote`, `markets`, `dashboard`, `portfolio`, `search` and `doctor` labels,
and common errors. Other commands are still English. Indian amounts keep
lakh/crore grouping (`₹16.27 लाख करोड़`).
**Periods** (`-p`): `1d`, `5d`, `1w` (same as `5d`), `1mo`, `3mo`, `6mo`, `ytd`, `1y`, `2y`, `5y`, `10y`, `max`.
Value names (`screen growth`, `tax in`, `-s macd`) are case-insensitive.
**Exit codes**: `0` success, `1` runtime error (unknown symbol, no data,
network, invalid config file), `2` invalid arguments.
**Machine-readable output**: commands marked `[--json]` print JSON, and
`[--json\|--csv]` print JSON or CSV, instead of a table. See
[Scripting](#scripting-with---json-and---csv).
## Start here
> **Guide:** [Quickstart: five minutes](https://iamsaquib8.github.io/stockwise/quickstart.html) walks through the first commands.
`stockwise SYMBOL` with no command prints a one-screen overview, and bare `stockwise`
prints a quick start. Short names: `q` (quote), `d` (dashboard), `a` (analyze), `t` (technical),
`c` (compare), `p` (portfolio), `w` (watch), `ev` (events). The aliases are lowercase: a capital
single letter is a ticker, so `stockwise C -m us` (Citigroup), `T`, `D`, `A` and `W` work as bare
symbols, and `stockwise c TCS INFY` still compares. A longer ticker that spells a command, such as
`ALL` or `PATH`, is a symbol when you give `-m` (`stockwise PATH -m us`). `stockwise quote C -m us`
always works. Common names resolve to NSE symbols
(`SBI` → `SBIN`, `L&T` → `LT`, index names), and `quote` suggests close matches for a mistyped symbol.
| Command | Description |
|---------|-------------|
| `quote [--json\|--csv]` (`q`) | Price, change, day/52-week range, volume, market cap, P/E, EPS, moving averages. Unknown symbols are named in the error |
| `deep [-p PERIOD]` | Everything about one stock: fundamentals, technicals, support/resistance, Fibonacci, risk, gaps, volume, insights. Default `3mo` |
| `markets [--crypto] [--fx] [--global] [--json\|--csv]` | US + India indices (S&P 500, Dow, NASDAQ, Russell, VIX, NIFTY 50, SENSEX, Bank NIFTY, NIFTY IT) and the India 10-year G-sec yield (CCIL, RBI fallback; left out when neither answers); `--crypto` adds the largest coins, `--fx` major pairs (USD/INR, EUR/USD, ...), `--global` one row per exchange's main index (NIFTY 50, S&P 500, FTSE 100, DAX, Nikkei 225, Hang Seng, Straits Times, S&P/TSX, S&P/ASX 200) with whether it is open and its local time. With `-m crypto`, `-m fx`, `-m commodity` or a global exchange that section comes first. `--json` rows carry `region`: `Indian Markets`, `US Markets`, `Crypto`, `FX`, `Commodities`, a country (`United Kingdom`, `Japan`, ...) or `Global` |
| `dashboard` (`d`) | Indices + portfolio P&L + watchlist + triggered alerts; first-run hints when empty |
| `search [--json\|--csv]` | Find a ticker by company name |
| `doctor` | Check the config file, market and language, data directory, Yahoo reachability, the data provider that answered, Redis, AI provider and model (never the key), broker config. Exits 1 if market data is unreachable or the config file is invalid |
## Analysis
> **Guide:** [Check a stock or fund fast](https://iamsaquib8.github.io/stockwise/guide-research.html).
| Command | Description |
|---------|-------------|
| `analyze ` (`a`) | Valuation, profitability, growth, analyst ratings, plain-English outlook |
| `results [--quarters N] [--annual] [--json\|--csv]` | Last 8+ quarters side by side: revenue, operating profit, OPM %, other income, interest, depreciation, PBT, tax %, PAT, diluted EPS, YoY %, QoQ %, OPM change in bps, a TTM column, and EPS against consensus with the 1-day and 5-day move versus the index. Banks add NII and GNPA/NNPA. `--annual` shows fiscal years with CFO/PAT, FCF, ROE, ROCE and 3/5/10-year CAGR. Needs no key; results are kept in `cache.db` for six hours |
| `results --holdings` / `--universe nifty50` `[--sort date\|pat\|margin\|surprise]` | Earnings-season scoreboard: how many companies reported the active quarter, aggregate revenue, operating profit and PAT growth YoY, beat / in-line / miss counts, and the reporters table |
| `events [--days N] [--ai]` (`ev`) | Unified timeline of material corporate events, exchange filings, earnings results, and excess market reaction vs NIFTY/S&P 500 |
| `insights ` | Positive, negative and neutral findings (Growth, Valuation, Margins, Ownership, etc.) with quantified metrics and XBRL shareholding patterns |
| `verdict [--explain] [--json]` | 6-pillar score: valuation (20%), growth & profitability (25%), balance sheet (15%), smart money & ownership (15%), analyst consensus (10%) and technical trend (15%). Each pillar gets 0–100 and a Strong / Moderate / Weak band, or "no data" when its inputs are missing; the composite (0–100) leaves "no data" pillars out and `data_coverage` says how much of the input was present. Crypto, FX and indices get only the technical pillar. Size cut-offs (cash, insider trades; market cap in `longterm`) are written in US dollars and amounts in other currencies are converted at rough fixed rates, so the same size scores the same in rupees and dollars. A description of the data, not a recommendation; `--explain` lists the weights and rules |
| `score [--json] [--explain]` (`scorecard`, `fundamental score`) | Transparent accounting scorecards. **Piotroski F-Score** (2000): nine yes/no signals, most of them this year against last year. A signal whose data is missing shows `n/a`, earns no point and is not counted, so the score reads like `5/7 evaluated`; it is rated only when all nine ran. Net income is estimated from trailing EPS × shares, or margin × revenue, only when no period reports it, and is marked `(est.)`. **Altman Z** (1968): needs all five ratios; with any missing it shows `n/a` and names them, with no zone, and no input is guessed. **Altman Z''** (1995, non-manufacturers) uses book equity over liabilities and is shown with its own factor table whenever X1–X4 exist. In `--json`: `piotroski.evaluated` and `criteria[].evaluated` (`piotroski.rating` is null when not rated), `altman.missing_factors`, `altman.z_double_prime_components` and `altman.z_double_prime_formula_str` |
| `technical [-p] [--json] [--explain]` (`t`) | RSI, MACD, Bollinger Bands, ATR, VWAP, SMA/EMA, braille charts. Default `3mo` |
| `history [-p] [--json\|--csv]` | Braille price chart, volume bars, last 20 bars. `--json`/`--csv` emit every OHLCV bar. Default `1mo` |
| `chart ` | 1W, 1M, 3M and 1Y charts at once |
| `compare [--json\|--csv]` (`c`) | Two or more stocks side by side across 16 metrics |
| `risk [-p] [--json]` | Annualized volatility, Sharpe/Sortino/Calmar, max drawdown, VaR 95/99% — always from daily bars, annualized with 252 bars a year for stocks and indices, 365 for crypto and 260 for FX. Default `1y` |
| `correlate [--json\|--csv]` (`corr`) | Correlation matrix of daily returns (two or more symbols), from the days both traded, so NSE, NYSE and crypto calendars line up; `null`/`N/A` when two symbols share fewer than 20 returns |
| `timeframes ` (`mtf`) | 1M–5Y return, volatility, RSI, SMA/MACD signals |
| `returns [--json\|--csv]` | 1W–5Y returns. The annualized figure (CAGR) uses the calendar time between the first and last bar, and is shown only for spans of a year or more (`null` otherwise) |
| `patterns [-p]` | Candlestick patterns (doji, hammer, engulfing, morning star) |
| `support [-p]` | Pivot points (R1–R3, S1–S3) from the last completed daily session (named on screen), whatever `-p` is, + Fibonacci levels |
| `fibs ` | Fibonacci retracement with a "you are here" marker, for the last 21, 63 and 126 trading days (1, 3 and 6 months of daily bars). Always reads a year of daily bars; `-p` is accepted but ignored |
| `volume [-p]` (`vol`) | OBV, A/D line, volume ratio, volume bars |
| `gaps [-p]` | Filled and unfilled price gaps |
| `stoploss [-e PRICE] [--capital AMT] [--risk-pct PCT] [-p]` | ATR (1.5x/2x/3x), percentage and Chandelier stops, position sizing (shares and position value for risk budget) and R:R targets |
| `forecast [-d DAYS] [-p]` | Linear trend projection with 95% bands — a projection, not a prediction |
`--explain` on `verdict`, `technical`, `score` and `options oi` adds plain-English notes for beginners.
## Market
> **Guide:** [Check a stock or fund fast](https://iamsaquib8.github.io/stockwise/guide-research.html#2-get-the-price).
| Command | Description |
|---------|-------------|
| `movers [--universe INDEX] [--cap TIER] [--json\|--csv]` | Top gainers and losers among 30 large caps, or an NSE index's members (see below), with a cap-tier column |
| `sectors [--json\|--csv]` | Sector ETF/index performance with bars |
| `heatmap [--universe INDEX] [--cap TIER]` | Heatmap of the 30-stock universe, or an NSE index's members |
| `sentiment` | Fear & Greed style gauge from VIX, momentum and trend |
| `news ` | Headlines collated from Yahoo Finance and Google News |
| `rotation` | Sector rotation vs 50-day average |
| `sectorcmp ` | Two sectors head to head |
| `peers ` | Same-sector peers on P/E, P/B, market cap |
| `insider ` | Analyst consensus + insider-related news |
| `ipo [SYMBOL] [--board all\|mainboard\|sme] [--capital 2.5L] [--recent N] [--json]` | Indian IPOs from NSE. Without a symbol: the live board of issues open now, upcoming and recently listed (price band, lot, minimum application, QIB/NII/retail subscription, a demand label such as "STRONG DEMAND" or "BOOK BUILDING") and an IPO-market regime. With a symbol or company name: issue structure (fresh vs OFS, anchor, lead managers), subscription by category with the mutual-fund share of QIB demand, demand by price, SEBI allotment odds for retail, sNII and bNII (with `--capital`, the odds at that amount, including for several separate applications), unofficial GMP and broker calls from press headlines, and a 6-pillar score with a **listing demand** label and a **long-term profile** label. In `--json`: `listing_demand`, `long_term_profile` and `timeline` replace `listing_verdict`, `long_term_verdict` and `timing_guidance`; `capital_odds` and `allotment_rules` replace `capital_plan` and `golden_rules`. Not a recommendation |
| `earnings` | Earnings growth for portfolio and watchlist |
| `surprise` | Earnings momentum — accelerating vs decelerating |
| `matrix` | Quality vs growth quadrants |
| `picks` | The highest-scoring large caps on 8 fundamental and technical factors, with the factors that raised each score. A score, not a recommendation |
| `screen [--universe INDEX] [--cap TIER] [--json\|--csv]` | `undervalued` (P/E<15), `growth` (forward EPS > trailing), `dividend` (yield>2%), `momentum` (top gainers), `bluechip` (>$100B / >₹5L Cr) |
| `dividends [--json]` | Yield, income projection, 10-year DRIP growth |
**`--universe`** (Indian market): scan the current members of an NSE index
instead of the built-in 30 large caps — `nifty50`, `niftynext50` (`next50`),
`nifty100`, `banknifty` (`niftybank`), `niftyit`, `nifty500`, `midcap150` or
`smallcap250`. NSE's website lists the members and prices them in one bulk
request (all 500 of `nifty500`), whichever data provider you use. `movers` and
`heatmap` rank from that snapshot alone, so they work when Yahoo Finance is
unreachable; `screen` needs P/E and market cap too, so it asks Yahoo for them
and falls back to the snapshot (no P/E or market cap) when Yahoo fails. Every
scan also saves the prices into `cache.db` (`market_snapshots`) and the index
members for the cap tiers below. When NSE can't be reached or
refuses, StockWise says so on stderr and scans a built-in list instead (the 30
large caps for `nifty50` / `nifty100`, a built-in member list for `banknifty`
and `niftyit`); `niftynext50` has no built-in list and fails with the reason.
**Cap tiers** (`mega`, `large`, `mid`, `small`, `micro`): a badge such as
`[Large]` next to the market cap in `quote`, `deep`, `analyze` and `compare`
(and a `Cap` column in `movers`, `screen` and `portfolio`), and a `cap_tier`
field in their `--json` / `--csv` (`unknown` when there is no market cap, or
no exchange rate for the currency). Indian stocks follow the SEBI/AMFI ranks
when the index members are known: NIFTY 100 is large (the top 10, or above
₹5,00,000 Cr, is mega), NIFTY MIDCAP 150 is mid, NIFTY SMALLCAP 250 is small
and anything outside NIFTY 500 is micro. Scanning those indexes with
`--universe` saves their members (`cap_membership.json` in the data
directory); until then, and offline, rupee cut-offs decide (large from
₹1,00,000 Cr, mid from ₹33,000 Cr, small from ₹10,000 Cr). Every other market
converts the market cap to dollars at the built-in approximate rates (London
pence `GBp` and Japanese yen `JPY` included): mega from $200B, large from
$10B, mid from $2B, small from $300M. A display and filtering aid, not a
regulatory classification.
**`--cap all|mega|large|mid|small|micro`** keeps one tier in `movers`,
`heatmap` and `screen`. With a tier, `-m in` and `-m us` scans read fresh
(under 30 minutes old) `market_snapshots` rows from `cache.db` when `cache
warm` or the worker has stored most of the market's list, with no request at
all; otherwise they scan live. There is no per-sector drilldown command yet,
so `--cap` has nothing to filter there.
## Funds & F&O
> **Guide:** [Check a stock or fund fast](https://iamsaquib8.github.io/stockwise/guide-research.html#6-check-a-mutual-fund) covers funds and SIP returns.
| Command | Description |
|---------|-------------|
| `mf search [-n N] [--json]` | Find Indian mutual-fund schemes by words in the name, fund house, category or ISIN; add `direct growth` to narrow. Shows the AMFI scheme code the other `mf` commands take |
| `mf nav [--json]` | Latest NAV and date, fund house, category, ISINs, and the plan's expense ratio (TER) with the other plan's for comparison |
| `mf history [-p PERIOD] [--json]` | NAV chart for the period (default `1y`), expense ratio (TER), trailing 1M–10Y returns (annualized from 1Y), since-inception CAGR, max drawdown. `--json` includes every NAV in the period |
| `mf sip [-p PERIOD] [--json]` | Monthly SIP from NAV history (default `5y`): value-vs-invested chart, gain, units, and XIRR on the actual purchase dates |
| `mf screen [CATEGORY...] [--plan direct\|regular\|all] [-s 1y\|3y\|5y\|ter] [--max-ter PERCENT] [-n N] [--max N] [--json]` | Bare: list categories with their active scheme counts. With words (`flexi cap`, `elss`, `liquid`, `equity etf`): rank the category's growth-option schemes by return (default 3Y CAGR, direct plans) or by expense ratio (`-s ter`, lowest first — with more matches than `--max`, the cheapest are screened), with a TER column. `--max-ter 1.0` keeps only funds whose disclosed TER is at most 1% |
| `mf bleed [--return PERCENT] [--json]` | Held Regular-plan funds vs their Direct growth twin: TER gap (AMFI disclosure), yearly rupee cost, 10-year compounded cost at a stated gross return, units that redeem tax-free inside the unused Sec. 112A exemption, and breakeven months for younger units |
| `mf overlap [--json]` | Shared top holdings, holdings and sector overlap, and expense ratios of two funds or ETFs, e.g. `mf overlap SPY QQQ -m us` |
| `options chain [-e DATE] [-n STRIKES] [--rate %] [--div-yield %] [--json]` | Calls and puts around the money: last price, open interest (and change on NSE/BSE), volume, IV, delta, gamma, theta, vega, rho, and the lot size. Default: nearest expiry, 10 strikes each side (`-n 0` for all) |
| `options pcr [-e DATE] [--json]` | Put/call ratio by open interest and by volume, and the three strikes with the most call and put open interest |
| `options oi [-e DATE] [-n N] [--json] [--explain]` (`open-interest`, `fno oi`) | Open-interest analytics: PCR, Max Pain, top call resistance and put support walls with institutional build-up classification (`LB`, `SC`, `SB`, `LU`), and aggregate volume breakdown. Sentiment is read for the underlying: put buying (`LB` on a put) is bearish and put writing (`SB`) is bullish; `--json` carries `calls_sentiment` and `puts_sentiment` |
| `options maxpain [-e DATE] [--json]` (`max-pain`) | The settlement strike at which open options pay their holders least, with the payout curve around it |
| `options payoff [--leg LEG]... [-s PRESET] [-w WIDTH] [-e DATE] [-l LOTS] [--lot-size N] [--rate %] [--div-yield %] [--iv %] [--today] [--json]` | A multi-leg position priced from the live chain: legs with price source, IV and delta; net debit/credit; max profit and loss (or "unlimited"); breakevens; probability of profit; position greeks; and a braille P/L chart at expiry (green/red), plus today's curve with `--today`. Analysis only — no orders |
| `options [-p]` / `options moves [-p]` | Expected 1/5/10/30-day moves and OTM strike ideas from realized volatility |
MF periods (`-p`) are `mo`, `y` or `max`, e.g. `6mo`, `3y`, `10y`. Expiries
(`-e`) are `YYYY-MM-DD`.
**`options payoff` legs and presets**
- A leg is `buy|sell [QTY] call|put STRIKE [@PRICE]` or `buy|sell [QTY] stock [@PRICE]`:
`--leg "buy call 24500" --leg "sell call 24800"`, `--leg "sell 2 put atm-1"`,
`--leg "buy stock @2450"`. `STRIKE` is a listed strike, or `atm`, `atm+N`,
`atm-N` (N listed strikes from the money). An unlisted strike is an error that
names the nearest listed ones.
- Prices: the bid/ask mid where both are quoted, else the last trade (flagged
`ltp` — it can be stale), else the leg needs `@PRICE`. A stock leg is priced at
the current underlying value (for an index that stands in for the future).
- Presets around the at-the-money strike, `W` = `--width` (default two strike
steps; a strike that isn't listed snaps to the nearest one, with a note):
`bull-call` (buy ATM call, sell ATM+W), `bear-put` (buy ATM put, sell ATM−W),
`straddle` (buy ATM call and put), `strangle` (buy ATM−W put and ATM+W call),
`iron-condor` (sell ATM±W, buy ATM±2W wings — a credit), `butterfly` (buy
ATM±W calls, sell two ATM calls), `covered-call` (stock at spot, sell ATM+W
call). `--strategy` and `--leg` combine.
- Sizes: every amount is per position — per-unit P/L × lot size × `--lots`. Lot
sizes come from the chain (NSE/BSE order-book quantities are whole lots; US
contracts are 100 shares). After hours BSE shows no order book: pass
`--lot-size`, otherwise amounts are per unit and the output says so.
- Probability of profit: the chance the expiry price lands where P/L > 0 if the
price is lognormal with the at-the-money IV (or `--iv`) and risk-neutral drift.
It is a model, not a forecast. Costs (brokerage, STT, exchange charges) are not
included. StockWise never places orders from this command.
**Where the data comes from, and what has none**
- **NAVs**: AMFI's public [NAVAll.txt](https://portal.amfiindia.com/spages/NAVAll.txt),
every Indian scheme, cached in the data directory (`cache/`) and re-downloaded
after 12 hours. NAV history: [api.mfapi.in](https://www.mfapi.in), a free
community mirror of AMFI data, cached the same way. If a download fails, a
stale cached copy is used with a warning on stderr.
- **Returns** are point-to-point NAV returns of the growth option, before tax
and exit load. `mf sip` buys at the first NAV on or after each monthly due date.
- **Expense ratios (TER)**: AMFI's monthly TER disclosure
([amfiindia.com/ter-of-mf-schemes](https://www.amfiindia.com/ter-of-mf-schemes)),
fetched from the API behind that page as one spreadsheet export for the month
(`/api/populate-te-rdata-revised?MF_ID=All&Month=MM-YYYY&strCat=-1&strType=-1&excel=true`,
~3 MB; the previous month if the current one isn't published yet). It lists
each scheme's TER per day for the regular and direct plan — from April 2026
split into base expense ratio, brokerage, transaction costs and statutory
levies. StockWise keeps each scheme's latest day, caches the result in
`cache/amfi_ter.json` for a day, and uses a stale copy with a warning if AMFI
is unreachable. The disclosure has no AMFI scheme code, so schemes are joined
on their name (case and punctuation ignored); a plan the scheme doesn't offer
(AMFI writes 0) has no TER. When TERs can't be loaded, `mf nav`/`history`/`screen`
still print NAVs and returns with a note; `--sort ter` and `--max-ter` fail
instead. In JSON, `ter` holds `plan`, `total`, the breakdown, both plans'
totals and the disclosure `date`, as fractions (`null` when not disclosed).
`mf overlap` shows Yahoo's expense ratio for the funds it covers.
- **Holdings** (`mf overlap`) come from Yahoo Finance, which publishes the top 10
holdings and sector weights for US ETFs and mutual funds (SPY, QQQ, VTI,
VFIAX, ... with `-m us`) and for some funds under Yahoo's `0P…` IDs. NSE-listed
ETFs such as NIFTYBEES and AMFI scheme codes have no holdings data — StockWise
says so instead of guessing. With only the top 10 known, the holdings overlap
is a lower bound; sector weights cover the whole fund.
- **Option chains**: in the Indian market (the default), NSE's public
option-chain API — index options on NIFTY, BANKNIFTY, FINNIFTY, MIDCPNIFTY and
NIFTYNXT50 (`^NSEI` and `^NSEBANK` work too) and NSE F&O stocks such as
RELIANCE. NSE serves browser sessions only: StockWise loads its option-chain
page once for cookies, makes two API calls, and stops with a clear error if NSE
refuses — wait a minute and retry. `SENSEX`, `BANKEX` and `SENSEX50` (also
`^BSESN`, `BSE-BANK.BO`) and BSE stock options as `SYMBOL.BO` come from BSE's
derivatives API, the one behind bseindia.com's option-chain page: one session,
calls 400 ms apart, answers cached for a minute in `cache/`, no retries. BSE's
site sometimes refuses scripted requests (HTTP 403) — StockWise then says so;
wait and retry, or use the website. BSE stock options are rarely traded. With
`-m us`, Yahoo Finance (delayed quotes) for US stocks and ETFs.
- **Lot sizes**: NSE and BSE quote order-book quantities in whole lots, so the
lot is their greatest common divisor across the chain (with at least five
quotes; a divisor of 1 counts as unknown); US contracts are 100 shares. `options chain` shows it and `--json`
has `lot_size` (`null` when unknown).
- **IV and greeks**: Black-Scholes-Merton. IV is solved from the bid/ask mid
(else the last trade) at the data timestamp; when that fails, the source's IV
is shown (`iv_from` in JSON). Risk-free rate defaults to 6.5% (India) / 4.0%
(US) — pass `--rate` with the current T-bill yield. Theta is per calendar day,
vega and rho per percentage point. NSE options are European; US equity options
are American, so their greeks are the usual European approximation.
- **PCR and max pain** describe positioning for one expiry; they are not
forecasts.
## Commodities & bonds
| Command | Description |
|---------|-------------|
| `commodity [--json\|--csv]` / `commodity spot` | Gold and silver in USD per troy ounce and in ₹ per gram, per 10 g (gold) and per kg (silver), with the day's change. ₹ prices are international prices converted at USD/INR — before Indian import duty and GST |
| `commodity futures [--json\|--csv]` | COMEX gold and silver futures (GC=F, MGC=F, SI=F, SIL=F) with the front contract month and volume |
| `commodity etfs [--json\|--csv]` | Gold and silver ETFs on NSE: GOLDBEES, SETFGOLD, HDFCGOLD, GOLDIETF, GOLD1, GOLDCASE, SILVERBEES, SILVERIETF, HDFCSILVER, SILVER1 |
| `commodity sgb [--json\|--csv]` | Sovereign Gold Bonds trading on NSE: price per unit (1 g), premium or discount to 1 g of gold at the international spot price, current yield of the 2.5% interest, maturity and volume |
| `bonds [--json\|--csv]` / `bonds yields` | Benchmark yields by tenor — 91/182/364-day T-bills, G-secs from 1–2 to 28–30 years (India 10Y = the 9Y–10Y bucket) and state loans (SDLs) |
| `bonds list [QUERY] [--json\|--csv]` | Government of India securities outstanding: ISIN, name, maturity, NSE symbol, outstanding stock. `QUERY` is a maturity year (`2033`), part of a name (`GS 2036`), an ISIN or an NSE symbol |
| `bonds calc [SECURITY] (--price P \| --ytm Y) [--coupon C --maturity DATE] [--settle DATE] [--dirty] [--tbill] [--json]` | Price ↔ yield to maturity, accrued interest, dirty price, current yield, Macaulay and modified duration, next coupon. `SECURITY` (`718GS2033`, `"7.18% GS 2033"`, `IN0020230085`) is looked up in RBI's list for its coupon and maturity; or give `--coupon` and `--maturity`. `--dirty`: the price includes accrued interest (NSE quotes G-secs that way). `--tbill`: a Treasury bill, priced on its discount yield. Settlement defaults to today in India |
**Where the data comes from, and what has none**
- **Gold and silver spot**: Yahoo Finance has no free spot quote, so spot is
the COMEX front-month future (`GC=F`, `SI=F`, USD per troy ounce) converted at
`USDINR=X` (1 troy ounce = 31.1035 g). It excludes Indian import duty and GST,
so jewellers' rates, IBJA rates and MCX prices are higher.
- **MCX futures** have no free public feed (MCX's site is bot-protected and its
data is licensed), so StockWise shows COMEX contracts and labels them.
- **ETFs** are NSE listings via Yahoo, in ₹ per unit; each ETF holds a
different amount of metal per unit, so compare % moves, not prices.
- **SGB prices** come from NSE's Sovereign Gold Bond list
(`/api/sovereign-gold-bonds`) through the same polite NSE session as the
[NSE provider](https://iamsaquib8.github.io/stockwise/data-providers.html#nse-and-bse), cached for 5 minutes and kept
on disk. When NSE refuses (it often blocks scripts), the last saved list is
shown with its date; with none, every outstanding tranche is listed **without
a price** — never a guessed one. Tranche details (series, issue date, issue
price, maturity = issue + 8 years, early redemption from year 5) are bundled
from public tranche tables; 33 of the 44 issue dates were checked against
RBI press releases, and RBI moves a maturity a day or two earlier when it
falls on a holiday.
Their NSE symbols come from a public list and are only shown when NSE's own
list is unavailable. No new SGBs have been issued since February 2024.
SGBs you hold are valued from the same list in `portfolio` and the other
holdings commands (see [Mutual funds and SGBs](#mutual-funds-and-sgbs)).
- **Yields**: CCIL's [tenor-wise indicative yields](https://www.ccilindia.com/tenorwise-indicative-yields),
end of day, cached for 6 hours; when CCIL can't be reached, the five
benchmark G-sec yields and T-bill cut-offs on the [RBI home page](https://www.rbi.org.in/).
CCIL and RBI data are for personal, non-commercial use.
- **Outstanding G-secs**: RBI's [list of Government of India securities](https://www.rbi.org.in/scripts/bs_viewcontent.aspx?Id=1956),
cached for a day.
- **Prices of individual G-secs and T-bills** are not shown: neither CCIL nor
RBI publishes them as a plain page, and NSE's live debt quotes have no
documented API. Enter the price your broker shows into `bonds calc`.
- **Bond math**: half-yearly coupons on the maturity date's day, 30/360
(European) day count, yield compounded half-yearly, simple interest in the
final coupon period — the G-sec convention. T-bill yield =
`(100 − price) / price × 365 / days`. Floating-rate bonds (FRBs) are not
supported.
## Strategies & bots
> **Guide:** [Backtest and practise on past markets](https://iamsaquib8.github.io/stockwise/guide-practice.html) and [Set up alerts and the daemon](https://iamsaquib8.github.io/stockwise/guide-alerts.html).
| Command | Description |
|---------|-------------|
| `backtest [-s STRATEGY] [-P NAME=VALUE] [-p] [--json\|--csv]` | Backtest one stock after costs: net and gross return, buy & hold, CAGR, drawdown, Sharpe/Sortino, exposure, trades. Fills at the next bar's open. Strategies: `rsi` (default), `macd`, `sma`, `bb`, `vwap`, `mr`, or your own TOML file. Default `2y` |
| `backtest portfolio [-s] [--weights equal\|inverse-vol\|0.5,0.3,...] [--rebalance none\|monthly\|quarterly] [--json\|--csv]` | One strategy across several stocks sharing the capital; combined equity curve and per-symbol contribution |
| `backtest walkforward [-s] [--train 1y] [--test 3mo] [--json\|--csv]` (`wf`) | Tunes the strategy's `[grid]` on rolling train windows and reports only the unseen test windows, with an overfitting verdict. Default `5y` |
| `backtest strategies [FILES...] [--json]` | List built-in and your own strategies, or check strategy files (exit 1 on errors) |
| `sip [-p] [--json]` | Monthly SIP/DCA simulation with value-vs-invested chart and XIRR. Default `5y` |
| `whatif [--json]` | What a lump sum on a past date is worth today |
| `intraday [-s STRATEGY]` (`bot`) | 9-strategy intraday signal scanner: sector heat, trend breadth, a table of regime, score and strategy agreement, and the strategies behind the strongest signals. No position sizes, entries, stops or targets. `-s` adds a strategy file's rules as a tenth signal. Old `CAPITAL TARGET%` arguments are accepted and ignored |
| `longterm [MONTHLY_BUDGET]` | 6-pillar scores (0–100) for the market's stock universe, with the facts that added or took away points ("P/E 9.8 (below 12)", "Analyst mean rating 1.4/5 (1 = most positive)"), a data-coverage figure, a risk level (Lower / Moderate / Higher) and rule-based moat signals. A pillar with no inputs shows a dash and the composite shows `n/a` when under half of the weight had data. Also an illustration at an *assumed* annual return set from the score (5- and 10-year growth, the monthly SIP that would reach 10 lakh / 1 million, your `MONTHLY_BUDGET` compounded) and a simulated 5-year range (10th, median, 90th percentile). It describes data; it is not a recommendation and gives no entry, stop or target. No `--json` yet. Wording changes in 0.8.1: the analyst lines no longer say "buy consensus" / "sell consensus" / "upside", the risk tier names `Conservative` / `Aggressive` became `Lower` / `Higher`, and "Best case" / "Worst case" became the 90th / 10th percentile |
| `daemon intraday [CAPITAL] [--once]` | Paper-trading loop on the NSE clock (IST, holidays included; `-m us` uses NYSE hours in ET): scan, enter at the open, trail stops every 60s, book half at T1, square off 15 minutes before the close, settle into `sim` history, then wait for the next trading day (`--once` exits after one session). Survives network blips, resumes the day after a restart, and squares off on Ctrl+C. **Never sends broker orders** |
| `daemon longterm` | Post-market scan: the highest long-term scores, portfolio value and wealth snapshot, triggered alerts, holdings priced below their average cost, and optional AI research notes |
| `sim start [CAPITAL] [TARGET%]` | Start a paper session with the bot's picks (default ₹25,000, 2%) on a trading day before the close |
| `sim status` / `history [--json]` | Live P&L / record + confidence rating |
| `sim settle [--now]` | Settle open sessions from that day's 5-minute bars: stop or target first, else exit at the 15:15 bar. Today's session waits for the close unless `--now` |
| `sim reset [--yes]` | Delete simulator history after confirmation (a timestamped backup is kept) |
| `sim replay list [--json]` (`scenarios`) | Built-in historical replays: `covid-crash-2020`, `gfc-2008`, `adani-2023`, `dotcom-2000`, `bull-run-2021`, `rate-hikes-2022`. See [simulator.md](https://iamsaquib8.github.io/stockwise/simulator.html) |
| `sim replay start SCENARIO\|file:PATH.toml [--capital] [--costs] [--json]` | Start a replay: daily history is fetched once, then you trade day by day and only ever see bars up to the replay date |
| `sim replay start --from DATE --to DATE --symbols A,B,... [--benchmark SYM]` | Your own replay window in the active market (`-m in`/`-m us`); benchmark NIFTY 50 / S&P 500 by default |
| `sim replay buy SYMBOL QTY [--stop P] [--target P]` / `sell SYMBOL [QTY]` | Queue an order for the next day's open (backtest cost models; long only). `sell` without a quantity sells the whole position |
| `sim replay next [N]` / `status` / `chart SYMBOL\|index [--days]` | Move N trading days (stops and targets on each day's range); account, positions, orders and the market on the replay date; a chart up to it |
| `sim replay cancel [ID]` / `set SYMBOL [--stop P] [--target P]` | Cancel pending orders; move a position's stop or target |
| `sim replay end [--json]` | Finish: 0–100 score and grade, badges, streaks, and what actually happened |
| `sim challenge list\|start NAME\|status\|end [--json]` | Goal-based challenges with hard and soft rules over the running replay or your live paper sessions: `beat-the-index`, `steady-hands`, `position-limit`, `stop-loss`, `sip-discipline`, `crash-survivor`, or `file:PATH.toml` |
| `sim profile [--json]` | Badges (earned and how to earn the rest), streaks, best score per scenario, recent runs |
| `sim card [--svg FILE] [--text]` | Your last run as a text card, or a 640 × 360 SVG image — with the paper-trading disclaimer |
| `report ` | Educational AI notes (signals, risks, what to watch) via local Ollama or an opt-in cloud model — see [ai.md](https://iamsaquib8.github.io/stockwise/ai.html) |
Every `backtest` form also takes:
| Option | Default | |
|--------|---------|---|
| `-s, --strategy` | `rsi` | A built-in, a file in your strategies folder, `file:PATH.toml`, or `wasm:PATH.wasm` (a [WASM plugin](https://iamsaquib8.github.io/stockwise/backtesting.html#wasm-plugins); `--features wasm-plugins` builds) |
| `-P, --param NAME=VALUE` | | Override a strategy parameter (repeatable) |
| `--costs` | `auto` | `auto`, `in-delivery`, `in-intraday`, `us`, `crypto`, `fx`, `none`. `auto` = crypto/FX models for coins and pairs, India delivery for INR stocks, US otherwise |
| `--brokerage` | preset | Per order: `20` (flat), `0.03%`, or `20,0.03%` (whichever is lower) |
| `--slippage-bps` | `5` | Slippage per fill, in basis points |
| `--fill` | `next-open` | `next-open` or `close` (fills at the signal bar's close — look-ahead) |
| `--capital` | `100000` | Starting capital in the stock's currency |
| `--json` / `--csv` | | Print the whole result as JSON, or the trades as CSV |
Costs, fills, the metrics, walk-forward and the strategy file format are in
[backtesting.md](https://iamsaquib8.github.io/stockwise/backtesting.html).
## Portfolio
> **Guide:** [Import your trades and see true returns](https://iamsaquib8.github.io/stockwise/guide-portfolio.html) and [Rebalance to target weights](https://iamsaquib8.github.io/stockwise/guide-rebalance.html).
Data lives in the directory shown by `stockwise doctor`.
| Command | Description |
|---------|-------------|
| `portfolio [show] [--base inr\|usd] [--json\|--csv]` (`p`) | Holdings with live P&L, XIRR and allocation; mutual funds at AMFI's NAV and SGBs from NSE ([below](#mutual-funds-and-sgbs)); holdings without a price show N/A. Mixed INR/USD holdings are totalled in `--base` (default: the market's currency) |
| `portfolio add [--date YYYY-MM-DD] [--force]` | Add a purchase as a new lot (default date: today). A ticker Yahoo does not know is refused with "did you mean" names; `--force` saves it anyway (also on `watch add` and `alert add`). A mutual fund is `MF:` or `MF:` (units and NAV per unit), kept as typed in every market |
| `portfolio sell [PRICE] [--date YYYY-MM-DD]` | Record a sale, oldest lots first (FIFO). Price defaults to the live price (a fund's latest NAV); a past `--date` needs a price. Refused while a split or bonus before the sale isn't applied to your lots |
| `portfolio lots [--lot N --date YYYY-MM-DD]` | Lots, sales, dividends and XIRR of one holding; `--lot N --date` corrects a purchase date |
| `portfolio actions [SYMBOL] [--apply]` | Splits, bonuses and dividends since your purchases, including positions you have sold (from Yahoo). Shows them; `--apply` adjusts lots and records dividends, and is safe to repeat |
| `portfolio list` | Your portfolios: the default one and every named one |
| `portfolio benchmark [NIFTY\|SENSEX\|SP500\|...] [-p 1y] [--base inr\|usd] [--json\|--csv]` | Time-weighted return against an index over the period, from the lots held each day: alpha, beta, tracking error, and attribution by holding and sector (see [below](#benchmark-and-attribution)) |
| `portfolio pme [NIFTY\|SPY\|VOO\|VWCE\|NIFTYBEES\|...] [--base inr\|usd] [--json\|--csv]` | Public Market Equivalent (Index Twin): mirror exact tradebook cash flows into a passive benchmark; computes Kaplan-Schoar PME, Direct Alpha, twin value, and net currency value added vs the index (see [below](#public-market-equivalent-index-twin)) |
| `portfolio remove ` | Remove a holding and its lots without recording a sale |
| `portfolio target [show]` / `portfolio target set ` / `portfolio target remove ` / `portfolio target drift ` | Custom target weights by symbol or sector and configurable drift tolerance band |
| `portfolio xray [--json]` (or `portfolio x-ray`) | Portfolio X-Ray: look-through overlap across direct stocks, ETFs and mutual funds, combined effective company exposures, 10% concentration risk alerts, broad asset-class split, market-cap breakdown, and sector concentration |
| `watch [show] [--json\|--csv]` / `watch add [--force]` / `watch remove ` (`w`) | Watchlist |
| `alert [list] [--json\|--csv]` / `alert add [--period N] [--force]` / `alert check [--json\|--csv]` / `alert remove ` | Price and technical alerts — conditions below; `alert list` shows each condition in words |
| `alert watch [--interval 60] [--once]` | Check alerts while their market is open and notify each trigger once (see [notifications](https://iamsaquib8.github.io/stockwise/bots.html#notifications)). `--once` checks every alert once and exits, for cron |
| `calendar [SYMBOLS...] [--days 30] [--holdings\|--watchlist] [--json\|--csv]` | Upcoming results, ex-dividend and dividend payment dates, splits and bonus issues for your holdings and watchlist (or the named symbols), sorted by date, with today and the next 7 days marked. See [the calendar](#results-and-corporate-calendar) |
| `import [--replace] [--sheet NAME]` | Import broker holdings, a lot list, a tradebook (CSV or `.xlsx`: one sheet is read, the first whose header fits, or `--sheet NAME`; Excel date cells included) or a CAS (casparser JSON/CSV); re-importing updates instead of doubling (see below and [brokers.md](https://iamsaquib8.github.io/stockwise/brokers.html#importing-holdings)) |
| `export ` | Write a CSV to the current directory |
| `wealth` | Save today's portfolio value and show growth (run daily) |
| `rebalance [--inflow N] [--drift N] [--json]` (or `portfolio rebalance`) | Trades to rebalance to targets (or equal weight); `--inflow` buys underweight assets with new cash without selling; `--drift` ignores minor deviations |
| `harvest [--json\|--csv]` | Tax-loss and LTCG-exemption harvesting candidates, by FIFO sale (see [below](#harvest)) |
| `tax [--json\|--csv]` | Capital-gains estimate for the current tax year: realized sales and unrealized lots, each with its own holding period (see below) |
| `tax export [in\|us] [--fy 2025-26] [--out FILE] [--json]` | Capital-gains schedule of a tax year's sales: an ITR Schedule CG / 112A CSV for India, a Form 8949 worksheet for the US (see [below](#tax-export)) |
| `tax advance [--fy 2025-26] [--paid [DATE:]AMOUNT]... [--json]` | Advance tax quarterly schedule (15 Jun, 15 Sep, 15 Dec, 15 Mar) under Indian Income-tax Act Section 208/211 with Section 234C(1) capital-gains proviso and interest estimator (see [below](#advance-tax-india)) |
| `divcal` | Dividend ranking and income for portfolio + watchlist |
| `income [--months 12] [--by-holding] [--json\|--csv]` (or `portfolio income` / `portfolio dividends`) | 12-month trailing and projected dividend, coupon and passive-income calendar, Yield-on-Cost (YoC), and dividend payout safety checks (see [below](#passive-income-and-dividend-calendar)) |
| `income upcoming [--days 60] [--json\|--csv]` | Radar of upcoming ex-dividend and payment dates for held and watchlisted shares (see [below](#passive-income-and-dividend-calendar)) |
| `pcorr` | Correlation between holdings from the days both traded, diversification warnings (`N/A` when two holdings share too few days) |
### Lots, sales and tax
> **Guide:** [File your Indian capital-gains tax](https://iamsaquib8.github.io/stockwise/guide-tax.html).
Every purchase is a lot: quantity, price, date and where it came from
(`manual`, `import:kite`, `broker:angel`, ...). `portfolio sell` takes shares
from the oldest lots first — FIFO, as Indian demat accounts and the IRS
default work — and keeps each slice as a realized sale.
`tax` classifies every lot by its own purchase date (long-term after more than
12 months for India and the US) and shows realized gains (sales this tax year)
separately from unrealized ones (if you sold everything today). For India it
applies the set-off rules: gains and losses of the same kind net first; a
short-term loss is set off against short-term gains, then long-term gains; a
long-term loss only against long-term gains; then ₹1.25 lakh of LTCG is exempt
and STCG is taxed at 20%, LTCG at 12.5%. Losses left over are shown as carry
forward. Holdings in another currency (e.g. US shares in `tax in`) are listed
but not included. It is an estimate — it includes the 4% cess but not surcharge or
brought-forward losses.
Grandfathering (India): for a long-term lot bought on or before 31 Jan 2018, the
cost is the higher of the actual cost and the lower of the 31 Jan 2018 price and
the sale price (Section 112A). `tax in`, `tax export in`, `tax advance` and
`harvest` all use it. The price is the day's highest for a listed share (Yahoo)
and the NAV for a mutual fund with an AMFI code; it is looked up once and cached.
A lot whose price cannot be found keeps its actual cost, and `tax export in`
says so in its notes. Prices are split-adjusted, so a lot recorded before a split
or bonus must carry its adjusted cost.
Same-day trades (India): a buy and sell of one share on the same day net
against each other first, in a tradebook import and in `trade sync` fills. The
matched quantity is recorded as an intraday sale (priced at that day's average
buy and sell) and the older lots are left alone. `tax in`, `tax export in` and
`tax advance` keep intraday results out of Sec. 111A/112A and show them
separately as speculative business income at your slab rate (losses can only
be set off against speculative gains). Without a date-time column a delivery
buy and sell of held shares on one day cannot be told apart from intraday.
Mutual funds and SGBs in `tax in`: equity-oriented funds — by their AMFI
category: equity schemes (ELSS included), arbitrage, aggressive hybrid and
equity savings funds, and Nifty/Sensex index funds — are counted like listed
shares and named under the estimate. Debt and other funds (taxed at your slab
rate for units bought from 1 Apr 2023, at 12.5% after 24 months for older
units), funds not found in AMFI's file, and SGBs (tax-free when RBI redeems
them; sold on the exchange, 12.5% after 12 months without the ₹1.25 lakh
exemption) are left out and listed with the rule that applies (`other_rules`
in `--json`). So are exchange-traded gold, silver and international ETFs (12.5%
after 12 months with no exemption, slab rate before) and liquid and debt ETFs
(slab rate at any holding period). StockWise knows them from a built-in list of
NSE symbols (`GOLDBEES`, `SILVERBEES`, `LIQUIDBEES`, `MON100`, Bharat Bond
`EBBETF…`, ...) and from `…BEES` names that say gold, silver, liquid, gilt,
Nasdaq or Hang Seng; an ETF it does not know is counted like a listed share, as
are equity ETFs such as `NIFTYBEES`. `harvest` skips the same holdings (they
are listed under "Not checked") and `tax export in` and `tax advance` leave
their sales out.
`tax in`, `harvest`, `tax export in` and `tax advance` share one rule for what
counts as a listed share or an equity-oriented fund (Sec. 111A/112A); the other
three load AMFI's file to read the category of any fund you sold. Crypto (such
as `BTC-INR`) is never in 111A/112A: it is taxed at a flat 30% under Sec.
115BBH with no set-off of losses, `tax export in` lists it under
`virtual_digital_assets`, and the other commands name it among the holdings
they leave out.
### Mutual funds and SGBs
> **Guide:** [Import your trades](https://iamsaquib8.github.io/stockwise/guide-portfolio.html#3-import-mutual-funds-from-a-cas) shows how to load a CAS.
Holdings that Yahoo Finance doesn't quote are priced by StockWise itself, the
same way in `portfolio`, `dashboard`, `tui`, `serve`, `wealth`, `rebalance`,
`harvest`, `tax`, `portfolio sell` and `report portfolio`:
- **Mutual funds** — `MF:` (`portfolio add MF:120503 10 50`) or
`MF:` (the growth/payout or IDCW-reinvestment ISIN, as `import cas` and
Groww imports store them) — at the latest NAV in AMFI's
[NAVAll.txt](https://portal.amfiindia.com/spages/NAVAll.txt) (cached 12
hours). NAVs are end of day: the table says "NAV as of <date>" for each fund,
and `--json` rows carry `name`, `price_source: "amfi_nav"` and `nav_date`. A
code or ISIN that isn't in AMFI's file, or a scheme AMFI lists without a NAV,
stays N/A with a note (find codes with `mf search`) — never 0. A scheme whose
last NAV is over a month older than the file is flagged as possibly matured.
`MF:` symbols are never sent to Yahoo, and `watch`/`alert` see the NAV too.
- **Sovereign Gold Bonds** — the NSE symbol (`SGBNOV26`, stored as
`SGBNOV26.NS`) or the tranche's ISIN — at the last price in NSE's SGB list
(`price_source: "nse_sgb"`). When NSE refuses or shows no trade, the value is
one gram of gold at the international spot price per unit, labelled
"estimated from gold spot" (`gold_estimate`). Values leave out the 2.5% yearly
interest, which is paid to your bank every six months.
Older versions stored `portfolio add MF:120503` as `MF:120503.NS` (and
`watch add` / `alert add` likewise); those symbols are repaired when the files
load.
### Passive income and dividend calendar
`stockwise income` (or `stockwise portfolio income`) is a 12-month trailing and
projected dividend, coupon and passive-income engine for dividend-growth
investors, retirees, and bond/SGB holders:
- **Monthly Cashflow Schedule:** Shows month-by-month cash flows across trailing
12 months (received dividends recorded in `portfolio.dividends`) and forward N
months (default 12), with a visual terminal bar chart and `--json`/`--csv`
serialization.
- **Yield on Cost (YoC):** Calculates per-holding and per-lot Yield-on-Cost
(`YoC = Annual Dividend / Original Lot Cost`), comparing it against current
market yield to reveal dividend-growth compounding over time.
- **TDS and Withholding Tax:** Automatically computes withholding tax (flat 10% TDS
for Indian dividends and 25% WHT for US foreign dividends; 0% assumed for other
currencies) to report net spendable income.
- **Sovereign Gold Bonds & Fixed Income:** Models half-yearly 2.5% p.a. SGB
coupons on issue nominal values across maturity cycles.
- **Payout Safety & Sustainability:** Evaluates earnings payout ratios,
diagnosing distributions as `SAFE` (< 60%), `CAUTION` (60–85%), or `WARNING`
(> 85% or negative earnings).
- **Upcoming Radar (`stockwise income upcoming`):** Checks upcoming ex-dividend
and payment dates for holdings and watchlists, showing held share counts and
calendar details.
### Harvest
> **Guide:** [File your Indian capital-gains tax](https://iamsaquib8.github.io/stockwise/guide-tax.html#2-look-at-harvest-candidates-before-31-march).
`harvest` lists what you could sell to book a loss, and which long-term winners
you could sell to use this year's unused ₹1.25 lakh LTCG exemption. For India it
follows FIFO, as a demat account does: to reach a losing lot you must sell the
older lots first, so the row shows that whole sale (`shares`, and the net `st_pnl`
and `lt_pnl` it books). If selling up to a losing lot books a net gain in all, the
holding is not offered as a loss; `fifo_blocked` (and a note on screen) says how
many shares you would sell and what the sale books. For the US you can choose lots,
so only the losing lots are offered.
`estimated_tax_saved` is how much the tax estimate falls when the loss is booked
on top of this year's realized gains, so set-off order, the exemption and cess are
counted; a loss with nothing to offset saves 0 and is carried forward. The
remaining exemption is shared: each winner takes its part of it, biggest first,
and the saving counts only that part. Holdings in another currency (US shares for
`in`) are listed under `other_currency` and not counted; holdings taxed under
other rules (crypto, debt funds, SGBs, gold and similar ETFs) under `other_rules`.
### Tax export
> **Guide:** [File your Indian capital-gains tax](https://iamsaquib8.github.io/stockwise/guide-tax.html#4-export-the-schedule-for-your-itr).
`tax export in` writes the realized sales of an Indian financial year as a
spreadsheet laid out for ITR Schedule CG and Schedule 112A: one row per lot
sold with section (111A short-term, 112A long-term), ISIN, scrip, quantity,
sale date and value, purchase date and cost, days held and the gain, then a
summary block — gains and losses of each kind, the short-term loss set off
against LTCG, the ₹1.25 lakh LTCG exemption, taxable STCG/LTCG, the estimated
tax and losses to carry forward. ISINs come from NSE's equity list (shares)
and AMFI's NAV file (funds imported from a CAS), both cached for 12 hours;
one that can't be found is left blank and named. `tax export us` writes a
Form 8949 worksheet instead: Part I (held one year or less) and Part II rows
with description, dates (MM/DD/YYYY), proceeds, cost basis and gain, then the
Schedule D totals, the $3,000 loss deduction and the carryover.
```bash
stockwise tax export in --fy 2025-26 --out cg-2025-26.csv # file + summary on screen
stockwise tax export in --fy AY2026-27 > cg.csv # same year, by assessment year
stockwise tax export us --fy 2025 --json | jq '.summary'
```
`--fy` takes `2025-26`, `2025-2026` or `AY2026-27` (India) and `2025` (US);
the default is the current tax year. Without `--out` the CSV (or `--json`)
goes to stdout. It is an **estimate — verify it with your broker's tax P&L**:
it only knows sales StockWise recorded (`portfolio sell`, tradebook and CAS
imports) at their recorded prices, without brokerage or STT. Shares bought on
or before 31 Jan 2018 are flagged — Schedule 112A lets you use that day's
fair market value as the cost, which StockWise doesn't apply; sales in
another currency (US shares for `in`) and crypto (taxed at 30% under section
115BBH, Schedule VDA) are listed but left out, and so are debt and other funds
and SGBs.
The rates follow the **sale date**: sales before 23 Jul 2024 are taxed at 15%
(STCG) and 10% (LTCG), later ones at 20% and 12.5%. The LTCG exemption is ₹1 lakh
up to FY 2023-24 and ₹1.25 lakh from FY 2024-25, for the whole of that year. In a
mixed FY 2024-25, losses and the exemption are used against the higher-rate gains
first (the lowest tax; the Act does not prescribe an order), and the summary
shows how much of the taxable gain came from before 23 Jul 2024.
### Advance tax (India)
> **Guide:** [File your Indian capital-gains tax](https://iamsaquib8.github.io/stockwise/guide-tax.html#3-check-advance-tax).
Under Section 208 and 211 of the Indian Income-tax Act, any taxpayer whose
estimated tax liability for the financial year is ₹10,000 or more must pay
advance tax in four quarterly instalments:
- **By 15 June**: At least 15% of estimated tax
- **By 15 September**: At least 45% of estimated tax
- **By 15 December**: At least 75% of estimated tax
- **By 15 March**: 100% of estimated tax (capital gains realized between 16 and 31 March are payable by 31 March)
Because capital gains cannot always be predicted in advance, the **proviso to
Section 234C(1)** specifies that deferment interest is not levied if the shortfall
is due to capital gains realized after an instalment date, provided the remaining
tax is paid in subsequent instalments. `stockwise tax advance` groups realized sales
into the four statutory quarterly cut-offs and calculates cumulative set-offs under
Indian equity rules, by sale date: from 23 July 2024 STCG @ 20% and LTCG @ 12.5%, before it 15% and 10%; the LTCG exemption is ₹1.25L (₹1L up to FY 2023-24):
```bash
stockwise tax advance # current FY advance tax schedule
stockwise tax advance --fy 2025-26 # specific financial year
stockwise tax advance --paid 2025-06-14:3000 --paid 2026-01-10:17800 # dated payments: exact Section 234C interest
stockwise tax advance --paid 25000 # a total with no date: assumed paid on time
stockwise tax advance --json # programmatic JSON output
```
- **Section 208 Check**: Automatically flags whether advance tax is mandatory (total liability ≥ ₹10,000) or exempt.
- **Status**: A quarter is `Paid` only when payments dated on or before its due date cover the minimum. With no `--paid`, a quarter past its due date is `Overdue`, because StockWise has no record of any payment.
- **Section 234C Deferment Interest**: Section 234C looks at what was paid by each due date, so pass each payment with its date (`--paid 2025-06-14:3000`, repeated). It then charges 1% a month (3% for Q1–Q3, 1% for Q4) on each shortfall, rounded down to ₹100, when the payments by that date are below the safe-harbour level (12% in Q1, 36% in Q2, 75% in Q3, 100% in Q4). For Q4 the requirement on 15 March is the tax on gains up to 15 March; tax on gains after it carries no interest when paid by 31 March (the proviso). A payment dated before the financial year began is ignored (it was made for an earlier year) and the output says so. A payment without a date is assumed to have been made before every due date, which can understate the interest.
- **Cess**: The tax and the instalments include the 4% health and education cess; surcharge is left out.
- **Section 234B Note**: Flags potential Section 234B liability if assessed after 31 March and cumulative advance tax paid is below 90% of total liability.
### Benchmark and attribution
> **Guide:** [Import your trades and see true returns](https://iamsaquib8.github.io/stockwise/guide-portfolio.html#6-compare-with-the-index-and-look-through-your-funds).
`portfolio benchmark` rebuilds what you held at each close of the period
from your lots and sales (in today's share count, so splits don't break the
history), and compares the portfolio's time-weighted return with an index
over the same days.
```bash
stockwise portfolio benchmark # vs NIFTY 50 over 1 year (S&P 500 with -m us)
stockwise portfolio benchmark SENSEX -p 2y
stockwise portfolio benchmark SP500 --base inr # S&P 500 converted to rupees day by day
stockwise portfolio benchmark NIFTYBEES -p 6mo --csv
```
- Benchmarks by name: `NIFTY`, `SENSEX`, `BANKNIFTY`, `NIFTY500`, `SP500`,
`NASDAQ`, `NASDAQ100`, `DOW`; or an index symbol (`^NSEI`) or any ticker.
Periods: `1mo`, `3mo`, `6mo`, `ytd`, `1y` (default), `2y`, `5y`, `10y`, `max`.
- The period starts at the first close where something was held. Returns come
from daily closes: a purchase or sale counts at that day's close, so the
figure measures the holdings, not your fill prices. Dividends are left out
on both sides (NIFTY 50 and the S&P 500 on Yahoo are price indices).
- Alpha (annualized, risk-free rate 0), beta, tracking error, information
ratio and correlation come from the daily returns and need 20 of them.
- Attribution: each holding's average weight, its return while held, its
contribution to the portfolio return and to the return over the index, in
percentage points (linked with Carino's method, so they add up exactly to
the totals); the same by sector, from Yahoo's asset profile ("Unknown" when
Yahoo has none, "Mutual fund" for funds imported from a CAS, valued from
their NAV history).
- Holdings in another currency are converted into the base currency (`--base`,
else the holdings' one currency, else the market's) at each day's exchange
rate; an index in another currency is converted too.
- Flagged under *Partial history*: holdings with no price history (left out),
days held before a holding's price history starts (left out of those days),
and purchases inside the period with an estimated date (the shares may have
been held earlier than the history shows).
### Public Market Equivalent (Index Twin)
`stockwise portfolio pme` mirrors your exact tradebook cash flows into a passive benchmark index or ETF (e.g. `SPY`, `VOO`, `VWCE`, `NIFTY`, `NIFTYBEES`) on their matching trade dates:
- Every buy of \$X on date D buys \$X worth of the benchmark twin.
- Every sell on date D distributes the equivalent proceeds from the benchmark twin.
- Recorded dividends are distributed from the twin on matching dates.
It answers: *"Did my active trading and stock picking actually beat investing in a passive index on the exact same dates?"*
Metrics reported:
- **Index Twin Value**: Current value of the synthetic benchmark portfolio.
- **Value Added / Lost**: Terminal portfolio value minus Index Twin value (in exact currency units: +\$ / +₹).
- **Kaplan-Schoar PME (KS-PME)**: Compounded future value of distributions + terminal portfolio value over future value of investments (> 1.000 indicates active outperformance; 1.000 is parity).
- **Direct Alpha (Gredil, Griffiths, Stucke 2014)**: Annualized IRR of benchmark-discounted cash flows, yielding 0.00% on parity.
- **Holding Breakdown**: Direct Alpha and Value Added breakdown for each held asset.
XIRR (in `portfolio` and `portfolio lots`) is the money-weighted annual return
from each lot's purchase, each sale, recorded dividends and today's value. It
shows `—` with under 30 days of history. The portfolio figure includes closed
positions and converts other currencies at today's rate.
Lots marked `*` have an estimated date: holdings from before lot tracking
(dated when they were added to StockWise) or imported from a file without
dates. Fix one with `portfolio lots SYMBOL --lot N --date YYYY-MM-DD`.
**Corporate actions.** `portfolio actions` lists splits, bonus issues (Yahoo
reports a 1:1 bonus as a 2:1 split) and dividends since each holding's first
purchase. Nothing changes until `--apply`, which multiplies the quantity and
divides the price of lots bought before the split (cost and date unchanged)
and records each dividend on the shares held at its ex-date. Each lot
remembers the splits applied to it, so running it again is a no-op. Indian tax
treats bonus shares as bought at zero cost on the allotment date — check bonus
lots against your tax statement.
**Imports.** A holdings export (Kite, INDmoney) has no purchase dates: existing
lots are kept and only the quantity is matched — more shares add a lot dated
today, fewer shares come out of the oldest lots (not recorded as a sale).
`trade holdings` syncs the same way. When the snapshot has more shares and a
split or bonus isn't applied to your lots yet, that holding is skipped until
you run `portfolio actions --apply` (otherwise the bonus shares would be
counted twice). A file with a date column (`Date`, `Purchase Date`,
`Buy Date`, `trade_date`, ...) is a list of lots: each symbol's lots are
rebuilt from it. With a buy/sell column (`trade_type`, `Type`, `Side`) it is a
tradebook — such as Zerodha Console's — sells come out FIFO as realized sales,
and splits and bonuses are applied as they happened (this needs a network
connection). StockWise keeps the trades of each tradebook import and merges the
next file with them, so several yearly tradebooks (Console allows 365 days per
download) can be imported one after another, in any order, and re-importing a
file changes nothing. A symbol is left unchanged when its sells have no earlier
buy yet (the trades are kept; import the earlier tradebook to finish), or when
it already has lots or sales from elsewhere (manual entries, another import,
or a tradebook imported before StockWise kept trades) — `--replace` rebuilds it
from this file alone anyway. A file of dated buys only (no buy/sell column) is
the symbol's whole list of lots and replaces them. Dates may be
`YYYY-MM-DD`, `DD-MM-YYYY`, `DD/MM/YYYY` (`MM/DD/YYYY` with `-m us`) or
`15-Mar-2024`; a file with an unrecognized date is refused.
**Mergers.** A dated row (or `portfolio add --date`) for a ticker that was
merged away is restated in the acquirer's shares, keeping the date and the
total cost: HDFC up to the 13 Jul 2023 record date becomes HDFCBANK at 42 shares
for every 25, and Mindtree up to 24 Nov 2022 becomes LTIM at 73 for every 100
(quantity × ratio, price ÷ ratio). Rows without a date, and plain renames such
as ZOMATO → ETERNAL, are mapped to the new name only. Fractional shares from
the swap are kept as a fraction (the cash paid for them is not recorded).
Existing lots already imported under the new name are not corrected — re-import
the tradebook with `--replace`.
**Accounts.** Each lot and sale carries the demat account it came from, named
after the broker: `import kite` and `trade … --broker zerodha` are `zerodha`,
`import groww` is `groww`, and so on. A holdings file or tradebook only
matches, rebuilds or sells the lots of its own account, so the same stock held
at two brokers is two sets of lots in one holding (`portfolio lots` shows the
account). Lots entered by hand or saved by an older version, with no account of
their own, count as the account of their `source`, or — for manual lots — join
the first broker that syncs the symbol. Totals and tax span all accounts.
`portfolio sell` works on the whole holding, oldest lot first.
**Groww, Upstox, CAS.** Groww and Upstox exports are snapshots (quantity
sync, like Kite). A CAS whose transactions cover a fund's whole balance
rebuilds it as dated lots, with redemptions as realized sales; otherwise only
the quantity is synced. See [brokers.md](https://iamsaquib8.github.io/stockwise/brokers.html#importing-holdings).
**Upgrading.** Portfolios saved before lot tracking are upgraded on first
load: each holding becomes one lot dated when it was added, and the original
file is kept once as `portfolio.json.bak`.
## Planning
| Command | Description |
|---------|-------------|
| `goal sip --target AMOUNT (--years N \| --months N \| --horizon 18mo \| --by YYYY-MM[-DD]) [--return PCT] [--inflation PCT] [--tax equity\|arbitrage\|debt\|none] [--slab PCT] [--exit-load PCT] [--net] [--glide-path] [--step-up PCT] [--savings AMOUNT \| --from-portfolio \| --holdings SYM,...] [--json]` | Monthly SIP that reaches the target. Without `--return` the return follows the horizon: capital preservation up to 24 months (6.75% India / 4.25% US, or the cached 364-day T-bill yield), conservative hybrid to 60 months (9% / 6.5%), long-term growth beyond (12% / 9%). Shows tax and exit load at redemption per instalment (equity: 20% STCG under 12 months, 12.5% LTCG above with the ₹1.25 L exemption; debt: slab rate; cess 4%), the net in hand and the SIP that leaves the target after tax (`--net` makes the target itself post-tax), P10 / median / P90 outcome bands with the chance of reaching the target, a ±2% return sensitivity, and a month-by-month (up to 24 months), quarterly (to 36) or yearly schedule. Forcing an equity return on a short goal prints a warning with the simulated downside |
| `goal retire --monthly-expense AMOUNT --years-to-retire N --years-in-retirement N [--inflation 6] [--return-pre 12] [--return-post 7] [--monthly-income AMOUNT] [--buffer-years N] [--legacy AMOUNT] [--step-up PCT] [--savings AMOUNT] [--json]` | Corpus needed on the day you retire to pay today's expenses (grown by inflation, less any pension or rent) for every month of retirement, and the SIP that builds it; the initial withdrawal rate, how long the corpus lasts after a 30% fall on day one, and the year-1 tax of an SWP against FD interest |
| `goal education --cost AMOUNT (--years N \| --months N \| --horizon D \| --by DATE) [--inflation 8] [--return PCT] [tax flags] [--json]` | Today's cost after the horizon of education inflation, and the SIP that pays it, with the same return basis, tax, bands and sensitivity as `goal sip` |
| `goal swp (--corpus AMOUNT \| --from-portfolio \| --holdings SYM,...) --withdrawal AMOUNT [--inflation 6] [--return 10] [--years 30] [--fd-return 7] [--slab 30] [--cost AMOUNT] [--json]` | How long the corpus lasts under monthly withdrawals that rise with inflation: an equity SWP (tax on the gain share only) against an FD (interest at the slab rate) |
| `goal emergency --monthly-expense AMOUNT [--months-cover 6] [--horizon 12mo] [--savings AMOUNT \| --from-portfolio \| --holdings SYM,...] [--return PCT] [--json]` | Reserve to hold, how much of it your liquid money covers, and the monthly build-up |
| `goal prepay --principal AMOUNT --loan-rate PCT --remaining-months N (--extra AMOUNT \| --lump-sum AMOUNT) [--return PCT] [--tax KIND] [--slab PCT] [--json]` | Exact interest and tenure saved by prepaying, against the after-tax SIP outcome of the same money (P10 / median / P90), the chance the SIP wins and the before-tax return it needs to break even |
| `goal add NAME --target AMOUNT --by YYYY-MM[-DD] [--inflation PCT] [--holdings SYM,...] [--return PCT]` | Save a goal in `goals.json` (a `--inflation` target is in today's money) |
| `goal [list] [--json]` · `goal show NAME [--json]` · `goal remove NAME` | Saved goals with months left, the live value of their linked holdings, the funded share and the SIP still needed; one goal in full; remove one |
`--from-portfolio` and `--holdings` value the portfolio (or the named holdings, such as `MF:122639` or `NIFTYBEES`) at live prices, or at cost when offline, and use that as existing savings; they replace `--savings`. Taxes and exit loads are estimates, not advice; `--tax` defaults to debt up to 60 months and equity beyond, with a 1% exit load on equity lots held under 12 months in India.
```bash
stockwise goal sip --target 1cr --years 15 # ₹19,819 a month at 12%
stockwise goal sip --target 1cr --years 15 --step-up 10 # ₹11,516 a month, rising 10% a year
stockwise goal sip --target 10L --months 9 # short goal: 6.75% baseline, month-by-month table
stockwise goal sip --target 50L --by 2031-06 --inflation 6 --from-portfolio
stockwise goal retire --monthly-expense 80k --years-to-retire 20 --years-in-retirement 30
stockwise goal education --cost 25L --years 10 --savings 3L
stockwise goal add house --target 50L --by 2031-06 --holdings NIFTYBEES
stockwise goal swp --corpus 1cr --withdrawal 50k
stockwise goal prepay --principal 50L --loan-rate 9 --remaining-months 240 --extra 10k
```
Amounts take Indian units — `50k`, `25L` / `25 lakh`, `1cr` / `1.5 crore`,
`2,50,000` — and results print in lakhs and crores (dollars with `-m us`).
The math, as Indian SIP calculators do it: the monthly rate is the annual
return ÷ 12 and SIPs are paid at the start of each month; a step-up raises
the SIP once a year; `--savings` already invested grows at the same return
and lowers the SIP. In retirement the expense rises with inflation once a
year and the corpus earns `--return-post` until it runs out at the end of the
last year. Returns are assumptions, not promises.
## AI assistants
> **Guide:** [Use StockWise from Claude or Cursor](https://iamsaquib8.github.io/stockwise/guide-mcp.html).
Both use the same read-only tools (quotes, history, technicals, risk, scorecards,
insights, events, mutual funds, option chains, IPOs, screens, backtests, your
portfolio, lots, tax estimate, advance tax, harvest and rebalance plans,
calendar, goals, watchlist and alerts). See [ai.md](https://iamsaquib8.github.io/stockwise/ai.html).
| Command | Description |
|---------|-------------|
| `ask [--verbose] [--max-steps 6] [--max-tokens 100000] [--json]` | Ask in plain English; the configured model (local Ollama by default, or the cloud provider in `STOCKWISE_AI_PROVIDER`) calls the tools and answers. Needs a model with tool calling. `--verbose` prints each tool call on stderr; `--json` prints the transcript (answer, tool calls with arguments, token usage). The last request runs without tools, so the model must answer. Educational, not financial advice |
| `mcp [--http ADDR] [--allow-remote] [--allow-orders] [--list-tools]` | [Model Context Protocol](https://modelcontextprotocol.io) server for Claude Desktop, Claude Code, Cursor and other assistants: 35 read-only tools, the portfolio, watchlist and alerts as resources, and three prompts. Stdio by default; `--http 127.0.0.1:8788` serves Streamable HTTP at `/mcp` (other addresses need `--allow-remote` and `STOCKWISE_API_TOKEN`). `--allow-orders` adds a dry-run `order_preview` that returns the `stockwise trade` command for you to run — it never places orders. `--list-tools` prints the tools and their input schemas as JSON. Cargo feature `mcp` (on by default) |
```bash
stockwise ask "how is my portfolio doing?"
claude mcp add stockwise -- stockwise mcp # Claude Code
stockwise mcp --list-tools | jq '.[].name'
```
## Interfaces
Both are read-only and share the data layer with the commands above, so they
show the same numbers. They are on by default and can be left out of a build
with `--no-default-features` (cargo features `tui` and `server`).
| Command | Description |
|---------|-------------|
| `tui [-i SECONDS]` | Full-screen live dashboard: indices, watchlist, portfolio P&L, alerts and a chart of the selected symbol. Refreshes every 30s by default (10-3600). `?` shows the keys. See [tui.md](https://iamsaquib8.github.io/stockwise/tui.html) |
| `serve [--bind ADDR] [--allow-remote] [--open]` | Web dashboard at `/` (`--open` opens it in your browser), JSON API (`/api/quote`, `/api/history/{symbol}`, `/api/markets`, `/api/search`, `/api/portfolio`, `/api/watchlist`, `/api/alerts`), `/healthz` and Prometheus `/metrics`. Listens on `127.0.0.1:8787`; other addresses need `--allow-remote` and `STOCKWISE_API_TOKEN`. See [server.md](https://iamsaquib8.github.io/stockwise/server.html) |
## Live trading
See [brokers.md](https://iamsaquib8.github.io/stockwise/brokers.html). Orders are previewed and need a typed `yes`.
Every `trade` command takes `--broker angel|zerodha|upstox|dhan|fyers`
(default `angel`, or `STOCKWISE_BROKER`); all but `angel` are experimental —
untested against a live account.
| Command | Description |
|---------|-------------|
| `trade setup` / `trade config [--keyring]` | How to connect / create the owner-only credentials file, move secrets into the OS keyring |
| `trade login` | Daily login (Zerodha, Upstox, Fyers) or paste a Dhan token |
| `trade holdings` / `positions` / `orders` | Live holdings (synced into `portfolio`, keeping your lots), open positions, order book |
| `trade buy [--limit P] [--trigger T] [--dry-run] [--yes]` | Buy on NSE: MARKET, LIMIT, SL-M (`--trigger`) or SL (both) |
| `trade sell [--limit P] [--trigger T] [--dry-run] [--yes]` | Sell on NSE, same order types |
| `trade limit [--dry-run] [--yes]` | Limit buy on NSE |
| `trade gtt --trigger T --limit P [--dry-run] [--yes]` | Single-leg GTT (not Upstox) |
| `trade gtt list [--notify] [--json]` / `trade gtt cancel ` | List GTTs with status changes since the last look (triggered, cancelled, expired, gone; `--notify` sends them) / cancel one (asks for confirmation) |
| `trade sync [--since DATE] [--apply] [--notify] [--json]` | Today's executed delivery trades into the portfolio — buys become dated lots, sells come out FIFO — idempotent by broker trade id; previews unless `--apply`. Dhan also reads older fills with `--since`. See [brokers.md](https://iamsaquib8.github.io/stockwise/brokers.html#syncing-trades-and-gtts-into-the-portfolio) |
| `trade selftest [--redact-amounts] [--json]` | Read-only check of the integration against your account (session, profile, holdings, positions, orders, trades, GTTs, instrument lookup, a dry-run order, response shapes); prints a redacted report for a [broker test report](https://iamsaquib8.github.io/stockwise/brokers.html#help-verify-a-broker). Exits 1 if a check failed |
## Setup
| Command | Description |
|---------|-------------|
| `profile [show] [--json]` | Show active investor profile mode (`passive`, `active:low\|med\|high`, `ultra`), effective tax parameters and personal guardrails |
| `profile set [MODE[:RISK]] [--risk low\|med\|high] [--tax-slab PCT] [--tax-regime new\|old] [--horizon-years N] [--max-stock-weight PCT] [--max-smallcap-weight PCT] [--risk-per-trade PCT] [--json]` | Save investor profile mode and personal guardrails to `config.toml` |
| `profile reset` | Reset profile mode and guardrails in `config.toml` to defaults (`active:med`) |
| `config [show] [--json]` | Effective settings and where each comes from (flag, env, config file, default), plus the config file's status |
| `config path` | Print the config file path (it may not exist yet) |
| `config init [--force]` | Write a commented template to the config file path; refuses to replace an existing file without `--force` |
| `notify [status]` / `notify test` | Show the configured notification channels (Telegram, Slack, Discord, email, desktop) / send a test message. See [notifications](https://iamsaquib8.github.io/stockwise/bots.html#notifications) |
| `completions ` | Print a completion script, e.g. `stockwise completions zsh > ~/.zfunc/_stockwise` |
| `man [--dir ]` | Print the `stockwise.1` man page, or write it plus one `stockwise-.1` page per command into `DIR`, e.g. `stockwise man --dir ~/.local/share/man/man1` |
## Scripting with `--json` and `--csv`
> **Guide:** [Script it with JSON, CSV and jq](https://iamsaquib8.github.io/stockwise/guide-scripting.html).
```bash
stockwise portfolio --json | jq '.totals'
stockwise portfolio --json | jq -r '.holdings | sort_by(.pnl_percent) | .[:3][] | "\(.symbol) \(.pnl_percent)%"'
stockwise portfolio --csv > portfolio.csv # open in a spreadsheet
stockwise history TCS -p 1y --csv > tcs.csv # every daily bar
stockwise alert check --json | jq -r '.[] | select(.status == "triggered") | .symbol'
stockwise screen dividend --csv | column -s, -t
stockwise tax in --json | jq '.realized.total_tax'
stockwise tax export in --fy 2025-26 --json | jq '.summary.taxable_ltcg'
stockwise portfolio benchmark --json | jq '{portfolio_return, benchmark_return, beta: .metrics.beta}'
stockwise goal sip --target 1cr --years 15 --json | jq '.monthly_sip'
for s in rsi macd sma bb vwap mr; do
stockwise backtest INFY -s "$s" --json | jq -r '"\(.strategy)\t\(.total_return_percent)"'
done
for s in $(stockwise watch --json | jq -r '.[].symbol'); do
stockwise technical "$s" --json | jq -r '"\(.symbol)\t\(.rsi_14)\t\(.bias.verdict)"'
done
stockwise quote TCS INFY --json | jq '.[] | {symbol, regularMarketPrice}'
stockwise markets --json | jq '.[] | select(.change_percent < -1)'
stockwise mf sip 122639 5000 -p 3y --json | jq '{invested, value, xirr}'
stockwise options chain NIFTY -n 2 --json | jq '.rows[] | {strike, delta: .call.greeks.delta}'
stockwise options payoff NIFTY -s straddle --json | jq '{breakevens, pop, max_loss}'
stockwise mf screen flexi cap -s ter --json | jq '.[] | {name, ter, return_3y}'
```
**Rules**
- stdout holds only the JSON or CSV; errors go to stderr with exit code 1, so
`set -e` and `||` work. `--json` and `--csv` can't be combined.
- Empty states are not errors: an empty portfolio prints `{"holdings": [], ...}`
or a CSV header with no rows.
- Keys are stable `snake_case`. The exceptions are `quote --json` and
`search --json`, which pass Yahoo's own fields through (camelCase, unchanged
since 0.3); their `--csv` uses snake_case columns.
- A value that isn't available is `null` in JSON and an empty cell in CSV —
never `0`. A holding without a live price keeps its row but is left out of
totals and listed in `unpriced`.
- Fields ending in `_percent` are percentages (`12.5` = 12.5%). `dividend_yield`
in `quote`, `compare` and `screen` is a fraction as Yahoo reports it (`0.012`
= 1.2%). Prices are as Yahoo returns them; derived money values are rounded
to 2 decimals, indicators to 4.
- Dates are `YYYY-MM-DD`; `history` bar times are RFC 3339 in UTC.
- CSV follows RFC 4180 quoting (fields with a comma, quote or newline are
quoted, quotes doubled), starts with a header row, and ends lines with `\n`.
CSV columns are the same names as the JSON row keys.
- The `--ai` section is never part of JSON or CSV output.
- `mf` and `options` have their own `--json` (no `--csv`); fractions there are
decimals (`0.1234` = 12.34%), and missing values are `null`, never 0.
- `options payoff --json` is `{symbol, source, underlying, currency, as_of, expiry,
days_to_expiry, strategy, width, lot_size, lot_size_from, lots, multiplier, rate,
div_yield, legs: [{side, qty, instrument, strike, price, price_from, iv, iv_from,
delta}], net_premium, net_premium_total, premium_type, max_profit, max_loss,
breakevens, profit_ranges: [{from, to}], pop, pop_iv, pop_iv_from, greeks: {delta,
gamma, theta, vega, rho}, notes, curve: [{price, expiry, today}]}`. `net_premium`
and leg prices are per unit (positive = debit); money totals, greeks and the
curve are for the whole position (`× multiplier` = lot size × lots).
`max_profit`/`max_loss` are `null` when unlimited; `max_loss` is negative;
`to: null` means "and above". `price_from` is `mid`, `ltp`, `manual` or `spot`.
Money values, prices and breakevens are rounded to 2 decimals.
**Shapes** (`[...]` is an array; `row` is described next to it)
| Command | `--json` | `--csv` — one row per |
|---------|----------|-----------------------|
| `quote` | `[quote]` — Yahoo's quote fields, plus `cap_tier` and `dataSource` when another provider supplied the price, and `deliveryPercent`, `lowerCircuit`, `upperCircuit` when NSE did ([data-providers.md](https://iamsaquib8.github.io/stockwise/data-providers.html#nse-and-bse)) | quote: `symbol, name, currency, price, change, change_percent, open, day_low, day_high, year_low, year_high, volume, avg_volume_3m, market_cap, cap_tier, pe_ttm, eps_ttm, dividend_yield, delivery_percent, lower_circuit, upper_circuit` (the last three empty unless NSE answered) |
| `markets` | `[{region, symbol, name, price, change, change_percent, currency}]` — `region` is `Indian Markets`, `US Markets`, `Crypto`, `FX`, `Commodities`, a country (`United Kingdom`, `Germany`, `Japan`, `Hong Kong`, `Singapore`, `Canada`, `Australia`) or `Global`. The India 10-year yield is the row with `symbol` `IN10Y`: `price` is the yield in percent, `currency` and `change` are null | index |
| `search` | `[hit]` — Yahoo's search fields | hit: `symbol, name, quote_type, exchange` |
| `commodity` / `commodity spot` | `[{metal, unit, price, currency, change_percent, source}]` — `metal` is `gold` or `silver`; `unit` is `USD/oz`, `INR/g`, `INR/10g` or `INR/kg` | row |
| `commodity futures`, `commodity etfs` | `[{symbol, metal, name, contract, price, change, change_percent, volume, currency}]` — `contract` is Yahoo's short name (the front month for futures) | instrument |
| `commodity sgb` | `[{symbol, isin, series, issue_date, issue_price, maturity, early_redemption_from, years_to_maturity, price, change_percent, volume, gold_value, premium_percent, interest_per_year, current_yield_percent}]` — `price` is ₹ per unit (1 g) and null when NSE gave none; `gold_value` is 1 g at the international spot price; `maturity` is nominal (issue + 8 years); NSE's notes go to stderr | tranche |
| `bonds` / `bonds yields` | `[{date, tenor, kind, security, ytm, source}]` — `kind` is `tbill`, `gsec` or `sdl`; `source` is `CCIL` or `RBI`; `ytm` in percent, null when the page shows none | yield |
| `bonds list` | `[{isin, name, coupon, issue_date, maturity, outstanding_cr}]` — `coupon` is null for floating-rate bonds; `outstanding_cr` in ₹ crore | security |
| `bonds calc` | `{security, isin, kind, coupon, maturity, settlement, days_to_maturity, clean_price, accrued_interest, dirty_price, ytm, current_yield, macaulay_duration, modified_duration, next_coupon, coupons_left}` — `kind` is `gsec` or `tbill`; prices per ₹100 face value, yields in percent | — |
| `history` | `{symbol, currency, period, interval, bars: [{time, open, high, low, close, volume}]}` | bar |
| `technical` | `{symbol, currency, period, interval, bars, price, sma_10, sma_20, sma_50, sma_100, sma_200, ema_12, ema_26, rsi_14, rsi_signal, macd: {line, signal, histogram, trend}, bollinger: {upper, middle, lower, position}, atr_14, atr_percent, vwap, bias: {verdict, bullish, bearish}}` — `position` is `above_upper`, `below_lower` or `within`; `verdict` is `bullish`, `bearish` or `neutral` | — |
| `risk` | `{symbol, currency, period, interval, bars, price, return_percent, volatility_percent, risk_level, sharpe, sortino, calmar, max_drawdown: {percent, peak_price, trough_price, trading_days}, var_95_percent, var_99_percent}` — `risk_level` is `low`, `moderate`, `high` or `very_high` | — |
| `compare` | `[{symbol, name, currency, price, change_percent, market_cap, cap_tier, pe_ttm, pe_forward, eps_ttm, price_to_book, dividend_yield, profit_margin, revenue_growth, debt_to_equity, beta, year_high, year_low, sma_50, analyst_mean_rating}]` — the table's transpose | stock |
| `correlate` | `{period, interval, symbols: [..], matrix: [[..]]}` — `matrix[i][j]` pairs `symbols[i]` and `symbols[j]` | symbol: `symbol, , , ...` |
| `returns` | `{symbol, currency, price, returns: [{period, label, start_price, end_price, return_percent, annualized_percent, growth_of_100000}]}` — `annualized_percent` is `null` for spans under a year | period |
| `movers` | `{gainers: [row], losers: [row]}`, row = `{side, rank, symbol, price, change_percent, volume, currency, cap_tier}` | stock, gainers then losers |
| `sectors` | `[{sector, symbol, price, change_percent, currency}]`, best first | sector |
| `screen` | `[{symbol, name, price, change_percent, pe, market_cap, dividend_yield, currency, cap_tier}]` | match |
| `dividends` | `{symbol, currency, price, pays_dividend, dividend_yield_percent, annual_dividend_per_share, income: [{invested, shares, annual_income, monthly_income}], drip: [{year, shares, growth_percent}]}` | — |
| `backtest` | `{symbol, currency, strategy, period, interval, bars, start, end, fill, capital, start_value, end_value, total_return_percent, cagr_percent, max_drawdown_percent, sharpe, sortino, exposure_percent, trade_count, win_rate_percent, avg_win_percent, avg_loss_percent, profit_factor, costs_paid, gross_return_percent, buy_hold_return_percent, alpha_percent, skipped_entries, costs, cost_model, rules, trades: [trade], equity: [{date, value}]}` — returns are after costs except `gross_return_percent`; `portfolio` and `walkforward` shapes are in [backtesting.md](https://iamsaquib8.github.io/stockwise/backtesting.html#json-output). trade = `{symbol, entry_date, exit_date, qty, entry_price, exit_price, gross_pnl, costs, net_pnl, pnl_percent, bars_held, exit_reason}` | trade (every form) |
| `sip` | `{symbol, currency, period, interval, monthly_amount, months, total_invested, final_value, profit, total_return_percent, xirr_percent, total_units, avg_cost, current_price}` | — |
| `whatif` | `{symbol, currency, amount, date, buy_date, buy_price, shares, invested, current_price, value, pnl, pnl_percent, cagr_percent, days, years}` — `buy_date` is the trading day closest to `date` | — |
| `portfolio` | `{holdings: [holding], totals: {cost_basis, value, pnl, pnl_percent, currency}, unpriced: [symbol], unconverted: [symbol], notes: [text]}`, holding = `{symbol, shares, avg_cost, cost_basis, price, value, pnl, pnl_percent, weight_percent, xirr_percent, currency, name, price_source, nav_date}` — a holding's money values are in its own currency; `totals` and `weight_percent` are in the base currency (`--base`); `unconverted` holdings had no exchange rate; `price_source` is `quote`, `amfi_nav`, `nse_sgb`, `gold_estimate` or null (no price) and `nav_date` the date of a fund's NAV; `notes` explain fund and SGB prices (unknown codes, estimates) | holding |
| `watch` | `[{symbol, price, change, change_percent, volume, currency}]`, in watchlist order | symbol |
| `alert list` | `[{index, symbol, condition, target, created_at, period, description}]` — `index` is what `alert remove` takes; `condition` is the CLI name (`above`, `rsi-below`, ...), `period` the RSI or volume-average length (else null), `description` the condition in English words | alert |
| `alert check` | `[{index, symbol, condition, target, price, currency, status, period, description, value, reference}]` — `status` is `triggered`, `waiting`, `no_price` (price alert without a live price) or `no_data` (too little history, or no results date); `value` is what the condition compared (price, RSI, day change %, volume multiple, days to results; the close for crosses and breakouts) and `reference` the moving average, previous high/low or average volume | alert |
| `calendar` | `[{date, days_until, when, symbol, event, detail, source, estimate}]` — `when` is `today`, `tomorrow`, `this_week` (within 7 days) or `later`; `event` is `results`, `board_meeting`, `ex_dividend`, `dividend_paid`, `split`, `bonus`, `rights` or `other`; `source` is `yahoo` or `nse`; `estimate` marks Yahoo's unconfirmed earnings dates. Notes (e.g. NSE refused) go to stderr | event |
| `portfolio benchmark` | `{benchmark: {symbol, name, currency, converted_to_base}, base_currency, period, start, end, trading_days, portfolio_return, benchmark_return, excess_return, portfolio_annualized, benchmark_annualized, metrics: {observations, alpha, beta, tracking_error, information_ratio, correlation}, holdings: [holding], sectors: [{sector, holdings, avg_weight, end_weight, return_pct, contribution, active_contribution}], flagged: [{symbol, reason}], series: [{date, portfolio, benchmark}]}`, holding = `{symbol, sector, avg_weight, end_weight, return_pct, contribution, active_contribution, days_held, days_unpriced, flag}` — returns, weights, `alpha` and `tracking_error` are percentages, contributions are percentage points; annualized returns only for periods over about 13 months; `series` is growth of 100; figures are null when nothing was held | holding |
| `portfolio pme` | `{summary: {benchmark_symbol, benchmark_name, base_currency, start_date, end_date, total_invested, total_distributed, actual_value, twin_value, value_added, kaplan_schoar_pme, direct_alpha_percent, portfolio_xirr_percent, twin_xirr_percent, twin_shares, benchmark_end_price}, holdings: [{symbol, actual_value, twin_value, value_added, pme_ratio, direct_alpha_percent, actual_xirr_percent, twin_xirr_percent, cashflows_count}]}` — Public Market Equivalent (Index Twin) analysis; `kaplan_schoar_pme` is the KS-PME ratio (> 1.0 indicates outperformance); `direct_alpha_percent` is the annualized excess return rate (%) | holding row |
| `portfolio xray` | `{portfolio_name, base_currency, total_value, effective_holdings: [{symbol, name, direct_value, direct_weight_pct, fund_value, fund_weight_pct, total_value, effective_weight_pct, is_concentrated, via_funds: [{fund_symbol, value, weight_in_fund_pct, effective_weight_pct}]}], concentrations_above_10pct: [effective_holding], market_cap_breakdown: {large_cap_value, large_cap_pct, mid_cap_value, mid_cap_pct, small_cap_value, small_cap_pct, unclassified_value, unclassified_pct}, sector_allocation: [{sector, value, weight_pct}], asset_class_split: {equity_value, equity_pct, debt_value, debt_pct, commodity_value, commodity_pct, cash_value, cash_pct}, funds_analyzed: [{symbol, name, look_through_available, holdings_count, coverage_pct}]}` | effective holding |
| `tax export` | `{country, tax_year, period_start, period_end, currency, disclaimer, sales: [sale], summary, other_currency: [symbol], virtual_digital_assets: [symbol], intraday_sales, intraday_gain, other_rules: [symbol], notes: [text]}` (`virtual_digital_assets`, `intraday_sales`, `intraday_gain` and `other_rules` for India only) — India: sale = `{section, term, isin, scrip, symbol, quantity, sale_date, sale_price, sale_value, purchase_date, purchase_price, cost, actual_cost, fmv_31_jan_2018, holding_days, gain, grandfathered, purchase_date_estimated}`, summary = `{stcg_111a_sale_value, stcg_111a_cost, stcg_gains, stcg_losses, ltcg_112a_sale_value, ltcg_112a_cost, ltcg_gains, ltcg_losses, st_loss_set_off_against_ltcg, taxable_stcg, ltcg_after_set_off, ltcg_exemption, taxable_ltcg, taxable_stcg_before_23_jul_2024, taxable_ltcg_before_23_jul_2024, stcg_tax, ltcg_tax, cess, total_tax, st_loss_carried_forward, lt_loss_carried_forward}`; US: sale = `{part, description, symbol, quantity, date_acquired, date_sold, proceeds, cost_basis, gain_or_loss, date_acquired_estimated}`, summary = `{short_term_proceeds, short_term_cost, short_term_gain, long_term_proceeds, long_term_cost, long_term_gain, net_gain, taxable_short_term, taxable_long_term, loss_deductible_this_year, loss_carryover, short_term_tax, long_term_tax, total_tax}` | — (the CSV is the schedule itself, see [Tax export](#tax-export)) |
| `harvest` | `{country, currency, total_st_loss_harvestable, total_lt_loss_harvestable, estimated_loss_tax_saved, ltcg_exemption_limit, ltcg_exemption_remaining, loss_candidates: [row], gain_harvest_candidates: [row], fifo_blocked: [{symbol, shares_to_sell, net_result, lot_loss}], unpriced: [symbol], other_rules: [{symbol, reason}], other_currency: [symbol]}`, row = `{symbol, shares, avg_cost, price, pnl, pnl_pct, st_pnl, lt_pnl, estimated_tax_saved, harvest_type}` — `harvest_type` is `tax_loss` or `ltcg_exemption_step_up`; for India `shares`, `st_pnl` and `lt_pnl` describe the whole FIFO sale that reaches the losing lot; `fifo_blocked` holdings would book a net gain (`net_result` > 0) if sold up to the losing lot, so they are not offered as losses; `other_currency` and `other_rules` holdings are left out | row |
| `rebalance` | `{total_value, projected_value, currency, inflow, drift_tolerance, items: [{symbol, current_value, current_pct, target_pct, projected_value, projected_pct, price, diff_value, diff_shares, action_type, action_display, is_fund}]}` — `action_type` is `buy`, `sell` or `ok`; with `--inflow` nothing is sold | item |
| `verdict` | `{symbol, name, price, currency, overall_score, data_coverage, pillar_bands: {strong, moderate, weak, no_data}, pillars: [{name, score, weight, band, inputs_present, inputs_possible, summary}], note, bull_points: [text], bear_points: [text], next_catalyst, warnings: [text], disclaimer}` — `score` and `overall_score` are 0–100 and `null` when there is no data (then `band` is `no_data` and the pillar is left out of the composite); `band` is `strong` (65+), `moderate` (45–64), `weak` or `no_data`; `weight` is a share of 1; `data_coverage` is the share of input data present, in percent; `note` says why there is no composite | — |
| `score` / `scorecard` | `{symbol, name, currency, price, market_cap, sector, industry, piotroski: {score, max_score, evaluated, rating, rating_label, profitability_score, leverage_score, efficiency_score, criteria: [{id, category, name, formula, formatted_value, passed, points, explanation, evaluated}]}, altman: {z_score, zone, zone_label, formula_str, is_financial, note, missing_factors, components, z_double_prime, z_double_prime_zone, z_double_prime_components, z_double_prime_formula_str}, evaluation_date}` — `evaluated` counts the signals that had data (`score` out of `evaluated`, not always out of 9); `rating` is `null` unless all nine ran; `z_score` is `null` with the missing ratios in `missing_factors` | — |
| `options oi` | `{symbol, source, underlying, currency, expiry, expiries, lot_size, pcr: {call_oi, put_oi, call_volume, put_volume, oi_ratio, volume_ratio}, pcr_sentiment, max_pain, top_calls: [wall], top_puts: [wall], breakdown: {calls: {long_buildup_oi, short_covering_oi, short_buildup_oi, long_unwinding_oi, neutral_oi, long_buildup_count, ...}, puts: {...}}, calls_sentiment, puts_sentiment}`, wall = `{strike, open_interest, share_pct, oi_change, price_change, last_price, buildup}` — `buildup` is `long_buildup`, `short_covering`, `short_buildup`, `long_unwinding` or `null` (on screen `LB`, `SC`, `SB`, `LU`); `calls_sentiment` and `puts_sentiment` read each side for the underlying (put buying is bearish, put writing bullish); `pcr_sentiment`, `calls_sentiment` and `puts_sentiment` are words such as `Bullish` | — |
| `tax advance` | `{fy_label, fy_start, fy_end, as_of, total_stcg, total_ltcg, total_tax_estimated, sec_208_threshold, sec_208_applicable, total_paid, payments: [{date, amount}], assumed_on_time, ignored_before_year, total_sec_234c_interest, sec_234b_applicable, quarters: [{quarter, due_date, cutoff_date, stcg_quarter, ltcg_quarter, stcg_cumulative, ltcg_cumulative, cumulative_tax_estimated, required_cumulative_pct, min_cumulative_tax_required, quarter_tax_payable, paid, shortfall, sec_234c_interest, status}]}` — `paid` is what was paid on or before that due date; `status` is `paid`, `pending`, `upcoming`, `overdue` or `nothingdue` | quarter row |
| `goal sip` | `{target, years, months, annual_return_pct, effective_annual_pct, step_up_pct, savings, savings_future_value, monthly_sip, final_monthly_sip, total_invested, gains, lump_sum_today, schedule: [{year, monthly_sip, invested, value}], monthly_schedule: [{month, monthly_sip, invested, value, lots_long_term}], regime: {kind, name, return_pct, vol_pct, source}, glide, target_today, inflation_pct, real_value_today, real_value_cpi_pct, net_target, tax: {kind, slab_pct, exit_load_pct, short_term_lots, long_term_lots, short_term_gain, long_term_gain, exit_load, tax}, net_after_tax, monthly_sip_for_net_target, percentiles: {p10, p50, p90, success_prob_pct, basis, gross: {p10, p50, p90}, net}, sensitivity: [{return_pct, monthly_sip}], mismatch, tax_compare: [{kind, net_after_tax}], warnings: [text], goal, currency}` — `goal` is `sip`, `retire`, `education`, `swp`, `prepay`, `emergency`, `list` or `show`; `monthly_schedule` only up to 24 months; `glide`, `target_today`, `real_value_today`, `mismatch` and `tax_compare` only when they apply | — |
| `goal retire` | `{monthly_expense_today, years_to_retire, years_in_retirement, inflation_pct, return_pre_pct, return_post_pct, monthly_expense_at_retirement, monthly_income_today, monthly_need_at_retirement, buffer_years, legacy, corpus, initial_withdrawal_rate_pct, survival_years, crash_survival_years, swp_drag: {annual_withdrawal, gain_fraction, swp_tax, fd_tax}, sip: plan, goal, currency}` — `plan` is the first part of the `goal sip` object (up to `schedule`) | — |
| `goal education` | `{cost_today, years, months, inflation_pct, future_cost, sip: report, goal, currency}` — `report` is the full `goal sip` object | — |
| `goal swp` | `{corpus, cost_basis, monthly_withdrawal, inflation_pct, return_pct, years, fd_return_pct, slab_pct, initial_withdrawal_rate_pct, swp: path, fd: path, goal, currency}` — path = `{runway_months, lasts, final_value, total_withdrawn, total_tax}` | — |
| `goal prepay` | `{principal, loan_rate_pct, remaining_months, emi, extra_monthly, lump_sum, new_tenure_months, tenure_saved_months, interest_without_prepay, interest_saved, sip_return_pct, sip_tax, sip_net, prepay_equivalent, sip_percentiles, breakeven_return_pct, goal, currency}` | — |
| `goal emergency` | `{monthly_expense, months_cover, target, liquid, funded_pct, horizon_months, build: plan, goal, currency}` | — |
| `goal list` | `{goal, currency, goals: [{name, target, target_future, by, months_left, holdings, linked_value, funded_pct, monthly_sip, status}]}` — `status` is `funded`, `needs_sip` or `overdue` | — |
| `tax` | `{country, tax_year, currency, sales: [{symbol, qty, bought, bought_estimated, sold, gain, term}], holdings: [{symbol, cost, value, short_term_gain, long_term_gain, estimated_dates}], realized: estimate, if_sold_today: estimate, ltcg_exemption_left, unpriced: [symbol], other_currency: [symbol], equity_funds: [symbol], other_rules: [{symbol, reason}], intraday_sales: [{symbol, qty, bought, bought_estimated, sold, gain, term}], intraday_gain}`, estimate = `{short_term_gains, short_term_losses, long_term_gains, long_term_losses, taxable_short_term, taxable_long_term, exemption_used, short_term_tax, long_term_tax, cess, total_tax, loss_carried_forward}` — `term` is `short`, `long`, or null (UK); `ltcg_exemption_left` is India only; `equity_funds` (counted at listed-share rates), `other_rules` (left out: debt/other funds, SGBs) and `intraday_sales` (bought and sold on one day: slab-rate speculative income, left out of every estimate) are filled for India | open holding |
| `sim history` | `{stats: {days, winning_days, day_win_rate_percent, total_pnl, avg_daily_pnl, best_day, worst_day, trades, winning_trades, losing_trades, trade_win_rate_percent, sharpe, max_win_streak, max_loss_streak}, confidence, sessions: [{date, market, capital, target_percent, settled, trades, pnl, pnl_percent, wins, losses}]}` — `confidence` is `high`, `moderate`, `low`, or null before 5 settled days | — |
| `trade selftest` | `{stockwise, os, date, market, broker, broker_name, experimental, account, secrets, orders_enabled, max_order_value_set, amounts_redacted, read_only, checks: [{name, status, detail}], shapes: [{endpoint, status, rows, missing, unknown, error}], summary: {pass, warn, fail, skip}}` — `status` is `pass`, `warn`, `fail` or `skip`; `account` is the last 2 characters only | — |
| `trade sync` | `{broker, experimental, portfolio, since, applied, orders: [{symbol, order_id, side, date, time, qty, price, fills, result, sales?, held?}], already_synced, skipped: [{order_id, symbol, reason}], open_orders: [{order_id, symbol, side, status, qty, filled_qty}], quantity_check: [{symbol, portfolio_qty, broker_qty}], gtt_changes: [change], notes}` — `result` is `lot`, `sold` (with `sales`) or `unmatched` (with `held`); `applied` is true only when `--apply` wrote something | — |
| `trade gtt list` | `{broker, experimental, gtts: [{id, symbol, side, qty, trigger, limit, status}], changes: [change], first_check, last_checked}`, change = `{kind, id, symbol, side, qty, trigger, from, to, state}` — `kind` is `new`, `status` or `gone`; `state` is `active`, `triggered`, `cancelled`, `expired`, `rejected` or `other` | — |
| `sim replay list` | `[{name, title, market, from, to, benchmark, benchmark_name, capital, stocks, description}]` | — |
| `sim replay status` | `{scenario, title, market, date, day, days, last_day, capital, cash, invested, equity, return_percent, max_drawdown_percent, costs_paid, cost_model, benchmark: {symbol, name, close, return_percent}, positions: [{symbol, sector, qty, avg_price, close, value, pnl, pnl_percent, weight_percent, stop, target, opened}], orders: [order], universe: [{symbol, sector, close, change_percent, change_20d_percent}], challenge, finished_challenges: [outcome], disclaimer}` — only data known at the replay date's close; `sim replay start` adds `description` and `skipped`. order = `{id, side, symbol, qty, stop, target, placed}`; challenge = `{name, title, description, day, days, started, rules: [{rule, goal, hard}], violations: [{date, rule, detail, hard}]}`; outcome = `{name, title, passed, reason, from, to, goals: [{goal, met, detail}], violations, sip}` | — |
| `sim replay next` | `{days: [{date, events: [event]}], new_badges, status}` — `event` is `fill` (`{date, symbol, side, qty, price, charges, reason, with_stop, weight_percent, pnl}`, `reason` = `order`, `stop-loss` or `target`), `contribution` (`{amount}`), `rejected` (`{symbol, side, qty, reason}`), `waiting` (`{symbol, side}`), `rule_broken` (a violation), `challenge_over` (an outcome) or `last_day` | — |
| `sim replay buy` / `sell` | `{order, warnings: [violation]}` — warnings are soft challenge rules the order breaks | — |
| `sim replay end` | `{kind, name, title, market, benchmark_name, from, to, finished, scorecard: {days, score, grade, return_percent, benchmark_return_percent, max_drawdown_percent, benchmark_max_drawdown_percent, sharpe, benchmark_sharpe, sortino, benchmark_sortino, exposure_percent, parts: {risk_adjusted, drawdown, discipline, diversification}}, badges, new_badges, disciplined, challenges: [{name, title, passed, reason}], curve, benchmark_curve, debrief, disclaimer}` — `score`/`grade` are null under 5 trading days; curves are rebased to 100 | — |
| `sim challenge list` | `[{name, title, description, days, modes, monthly_contribution, rules: [{kind, ...}]}]` — `days` 0 = until the replay ends | — |
| `sim challenge status` | `{mode, challenge, ...}` — `mode` `replay` adds `finished: [outcome]`; `live` adds `sessions, complete, return_percent, benchmark_return_percent, max_drawdown_percent, violations`; `challenge` is null when none runs | — |
| `sim challenge end` | replay: `{outcome, new_badges}`; live: the `sim replay end` shape plus `outcome` | — |
| `sim profile` | `{badges: [{id, title, how, earned, run}], streaks: {disciplined: {current, best}, challenges: {current, best}}, best: {NAME: {score, grade, date}}, challenges_passed, challenges_failed, runs: [run], disclaimer}` — `earned` is null for badges not earned yet; run = the `sim replay end` shape without `debrief`/`disclaimer` | — |
| `config show` | `{path, status, error, settings: [{key, value, source}]}` — `status` is `loaded`, `missing` or `invalid`; `source` is `flag`, `env`, `file` or `default` | — |
## Alert conditions
> **Guide:** [Set up alerts and the daemon](https://iamsaquib8.github.io/stockwise/guide-alerts.html#2-add-alerts).
`stockwise alert add `:
| Condition | NUMBER | Fires when |
|-----------|--------|------------|
| `above` / `below` (`>` / `<`) | price | the live price is at or beyond the level |
| `rsi-below` / `rsi-above` | RSI level (0–100) | RSI (14-day, or `--period N`) is at or beyond the level |
| `cross-above-sma` / `cross-below-sma` | days (2–400) | the daily close crosses its simple moving average — on the crossing day only |
| `cross-above-ema` / `cross-below-ema` | days (2–400) | the same with an exponential moving average |
| `breakout-high` / `breakdown-low` | days (2–1000) | the close is above the highest high (below the lowest low) of the previous N sessions |
| `change-above` / `change-below` | percent | the day's change is +N% or more / −N% or less (`change-below 3` and `change-below -3` are the same) |
| `volume-spike` | multiple (1–100) | today's volume is at least N times its 20-day (`--period`) average |
| `results-in` | days (0–90) | the next results date is at most N days away (from [`calendar`](#results-and-corporate-calendar)) |
Technical conditions read daily bars (one chart request per symbol per
check), with the last bar updated to the live quote, so a cross or breakout
fires on the session it happens in. A symbol with too little history reads
`no_data` and never triggers. `alert watch` notifies each alert once until it
re-arms, at most once per trading day.
```bash
stockwise alert add INFY rsi-below 30
stockwise alert add INFY rsi-above 70 --period 9
stockwise alert add RELIANCE cross-above-sma 200
stockwise alert add AAPL -m us breakout-high 52
stockwise alert add HDFCBANK change-below 3
stockwise alert add TCS volume-spike 2
stockwise alert add TCS results-in 7
```
Alerts saved by earlier versions keep working unchanged.
## Results and corporate calendar
> **Guide:** [Check a stock or fund fast](https://iamsaquib8.github.io/stockwise/guide-research.html#5-see-what-happened-and-what-the-data-says) and [Set up alerts and the daemon](https://iamsaquib8.github.io/stockwise/guide-alerts.html).
`stockwise calendar` lists what is coming up for your holdings and watchlist
(`--holdings` or `--watchlist` for one of them, or name symbols), over the
next 30 days (`--days 1-365`):
| Source | Covers | Events |
|--------|--------|--------|
| Yahoo Finance (`quoteSummary` calendarEvents) | US and Indian stocks | results (earnings) date or expected window with the EPS estimate, ex-dividend date, dividend payment date |
| NSE board meetings | `.NS` / `.BO` stocks (looked up by their NSE symbol) | board meetings; those that consider results are listed as results |
| NSE corporate actions | `.NS` / `.BO` stocks | ex-dates of dividends, splits, bonus and rights issues, other actions |
- For an Indian stock, NSE's filed results date replaces Yahoo's estimate,
and NSE's ex-dividend date replaces Yahoo's.
- Upcoming splits come from NSE only; Yahoo lists splits after they happen,
so US splits don't appear in advance.
- BSE filings are not read; a `.BO` symbol uses the same company's NSE
filings.
- NSE's API serves browser sessions. StockWise opens one session per run,
waits a second between requests and caches every answer for a day in
`calendar_cache.json` (failures for an hour). When NSE refuses, the table
says so and shows Yahoo's dates.
- Crypto, FX and indices have no calendar and are skipped.
```bash
stockwise calendar
stockwise calendar --holdings --days 14
stockwise calendar TCS INFY
stockwise calendar AAPL MSFT -m us --json
```
## Configuration file
Everything works without one. To set your own defaults:
```bash
stockwise config init # write a commented template
stockwise config path # where it is
stockwise config show # every effective setting and where it came from
```
The file is `config.toml` in your platform's config directory —
`~/Library/Application Support/stockwise/` on macOS, `~/.config/stockwise/`
on Linux (`$XDG_CONFIG_HOME`), `%APPDATA%\stockwise\` on Windows — or the
file named by `STOCKWISE_CONFIG`.
```toml
market = "us" # in | us | crypto | fx | commodity | uk | de | jp | hk | sg | ca | au
lang = "hi" # en | hi
quote_currency = "inr" # BTC → BTC-INR with -m crypto
portfolio = "long-term" # a named portfolio
ai = false
ai_provider = "ollama" # ollama | openai | anthropic | gemini
model = "llama3.1:8b"
ollama_host = "http://gpu-box:11434"
data_provider = "yahoo" # yahoo | alphavantage | massive | twelvedata
redis_url = "off"
home = "~/Dropbox/stockwise" # data directory; ~ is your home
[periods] # default -p per command
technical = "6mo"
backtest = "5y"
sip = "10y"
[notify] # STOCKWISE_* notification variables, lower-case, no prefix
telegram_chat_id = "123456789"
smtp_host = "smtp.gmail.com"
email_to = "me@example.com"
```
| Key | Overridden by | Built-in default |
|-----|---------------|------------------|
| `market` | `-m`, `STOCKWISE_MARKET` | `in` |
| `ai` | `--ai` / `--no-ai`, `STOCKWISE_AI` | `false` |
| `model` | `STOCKWISE_MODEL` | `qwen3:14b` |
| `ollama_host` | `OLLAMA_HOST` | `http://localhost:11434` |
| `redis_url` | `STOCKWISE_REDIS_URL` | `redis://127.0.0.1:6379/` |
| `home` | `STOCKWISE_HOME` | platform data directory (see `doctor`) |
| `lang` | `--lang`, `STOCKWISE_LANG` | your locale, else `en` |
| `quote_currency` | `--quote-currency`, `STOCKWISE_QUOTE_CURRENCY` | `usd` |
| `portfolio` | `--portfolio`, `STOCKWISE_PORTFOLIO` | `default` |
| `ai_provider` | `STOCKWISE_AI_PROVIDER` | `ollama` |
| `data_provider` | `STOCKWISE_DATA_PROVIDER` | `yahoo` |
| `[periods] ` | `-p` | per command, see ` --help` |
| `[notify] ` | `STOCKWISE_` | none (see [notifications](https://iamsaquib8.github.io/stockwise/bots.html#notifications)) |
`[periods]` accepts the commands that take `-p`: `deep`, `technical`,
`history`, `risk`, `patterns`, `support`, `fibs`, `volume`, `gaps`,
`stoploss`, `forecast`, `options`, `backtest`, `sip`. `--help` shows the
default from your file, e.g. `[default: 6mo]`.
A file that doesn't parse — an unknown key, a wrong type, an unknown market or
period — stops every command with exit code 1 and an error naming the file and
the key. `config`, `doctor` and `completions` still run, so you can inspect it
or replace it with `stockwise config init --force`.
API keys (`OPENAI_API_KEY`, the data provider's key, ...) and
`STOCKWISE_API_TOKEN` are read from the environment only. Notification tokens
and passwords may go in `[notify]`; if they do, keep the file private
(`chmod 600`) — `config show` and `doctor` warn when other users can read it.
`stockwise serve` answers with the same shapes over HTTP — see
[server.md](https://iamsaquib8.github.io/stockwise/server.html).
---
# Markets
`-m` picks the market bare tickers belong to. India (`in`, the default) and
the US (`us`) have their own lists and rules; crypto (`crypto`), currency pairs
(`fx`) and gold and silver (`commodity`) trade around the clock or 24x5; and
seven more stock exchanges come from one **market table** in `src/market.rs`.
## Global exchanges
| `-m` | Exchange | Suffix | Currency | Main index | Hours (local) | Holiday list bundled |
|------|----------|--------|----------|------------|---------------|----------------------|
| `uk` (`gb`, `lse`) | London Stock Exchange | `.L` | GBP, quoted in pence (GBp) | FTSE 100 | 08:00–16:30; 24/31 Dec close 12:30 | 2026, 2027 |
| `de` (`xetra`) | Deutsche Börse Xetra | `.DE` | EUR | DAX | 09:00–17:30 | 2026, 2027 |
| `jp` (`tse`) | Tokyo Stock Exchange | `.T` | JPY | Nikkei 225 | 09:00–11:30, 12:30–15:30 | 2026, 2027 |
| `hk` (`hkex`) | Hong Kong Exchanges | `.HK` (codes padded: `5` → `0005.HK`) | HKD | Hang Seng | 09:30–12:00, 13:00–16:00; eves close 12:00 | 2026, 2027 |
| `sg` (`sgx`) | Singapore Exchange | `.SI` | SGD | Straits Times | 09:00–12:00, 13:00–17:00; eves close 12:00 | 2026 (2027 provisional) |
| `ca` (`tsx`) | Toronto Stock Exchange | `.TO` | CAD | S&P/TSX Composite | 09:30–16:00 ET; 24 Dec closes 13:00 | 2026 (2027 provisional) |
| `au` (`asx`) | Australian Securities Exchange | `.AX` | AUD | S&P/ASX 200 | 10:00–16:00 Sydney; 24/31 Dec close 14:10 | 2026, 2027 |
```bash
stockwise -m uk quote VOD BARC # VOD.L, BARC.L — prices in pence (118.20p)
stockwise -m jp history 7203 -p 1y # Toyota, 7203.T
stockwise -m hk quote 700 5 # 0700.HK, 0005.HK
stockwise -m au sectors # S&P/ASX 200 sector indices
stockwise markets --global # every exchange's main index, open or closed now
```
What each row holds:
- **Symbols**: the Yahoo Finance suffix bare tickers get. Symbols that already
say where they trade (`TCS.NS`, `^FTSE`) pass through unchanged.
- **Currency**: prices use the local symbol (`£`, `€`, `¥`, `HK$`, `S$`, `C$`,
`A$`). London quotes shares in pence: prices print as `118.20p`, market caps
(which Yahoo reports in pounds) as `£27.38B`, and portfolio totals convert
pence at GBP's rate ÷ 100. Enter London costs in pence too
(`portfolio add VOD.L 100 118.2`). The bots and the simulator size positions
in pounds, so they refuse `-m uk` for now.
- **Benchmarks** for `markets` (`-m uk markets` leads with the FTSE 100, FTSE
250 and FTSE All-Share); the first one is the `markets --global` row.
- **Sectors** where Yahoo has a usable set: TOPIX-17 sector ETFs (Japan), Hang
Seng sub-indices (Hong Kong), iShares S&P/TSX sector ETFs (Canada), S&P/ASX
200 sector indices (Australia). The UK, Germany and Singapore have none, so
`sectors`, `rotation` and `sectorcmp` say so.
- **Universe**: 14–15 large caps scanned by `movers`, `screen` and the bots.
- **Session and time zone**: European and Australian summer time are computed
(no time-zone database needed); Tokyo, Hong Kong and Singapore have lunch
breaks, during which `is_open` is false and alerts wait.
- **Holidays** (`src/clock.rs`): weekday closures and half days from each
exchange's own calendar — LSE business days, Deutsche Börse's trading
calendar, JPX's holiday page, SEHK circulars CT/075/25 and CT/077/26, ASX's
trading calendar, TMX holiday notices, and for SGX the Ministry of Manpower
public holidays it follows (2026 cross-checked against SGX's monthly
trading-day counts; 2027 provisional for SGX and TSX until they publish). Years outside a
table fall back to "every weekday trades", and `markets --global` warns
about it.
- **Tax**: `stockwise tax` has rules for India, the US and the UK only. Other
listings are left out of an estimate and named.
## Commodities
`-m commodity` resolves `GOLD` (or `XAU`) to COMEX gold futures `GC=F` and
`SILVER` (`XAG`) to `SI=F`; other bare tickers are NSE listings, for gold and
silver ETFs (`GOLDBEES`) and Sovereign Gold Bonds. So `history gold`, `chart
silver`, `technical gold` and alerts work on them. `stockwise commodity` shows
spot in ₹/g, ₹/10 g and ₹/kg; see [commands.md](https://iamsaquib8.github.io/stockwise/commands.html#commodities--bonds)
for where every number comes from — and what has no free source (MCX).
## Indian government bonds
`stockwise bonds` reads CCIL's tenor-wise indicative yields (RBI's benchmarks
as the fallback) and RBI's list of outstanding G-secs, and prices bonds with
`bonds calc`. `markets` shows the India 10-year yield. Details and limits are
in [commands.md](https://iamsaquib8.github.io/stockwise/commands.html#commodities--bonds).
## Adding an exchange
1. Add a variant to `Exchange` and a `MarketInfo` row to `GLOBAL_MARKETS` in
`src/market.rs`, in the same order (a test checks the order). Check every
symbol against Yahoo Finance first.
2. Add its holiday table to `src/clock.rs` (weekday closures and half days,
from the exchange's own calendar) and list the years in `official_years`.
If its time zone has summer time not covered by a `TzRule`, add the rule
with a test of the changeover instants.
3. Add its title to the `messages!` table in `src/i18n.rs` (`MarketXx`).
4. If the exchange quotes in a new currency, add its symbol to
`currency_symbol` in `src/market.rs`.
5. `cargo test market clock` checks suffixes, universes, hours, that every
holiday is a weekday in a covered year and that half days trade.
## Updating holiday tables
Exchanges publish next year's list between June (HKEX) and December (NSE,
TMX). Add the rows, move the year into `official_years`, and run `cargo test
clock`.
---
# Data providers
StockWise gets market data from **Yahoo Finance** by default — no account or
key needed. You can opt in to another provider for quotes, daily history and
symbol search. Yahoo stays in the loop either way: it fills every field the
other provider lacks and answers whatever the provider can't.
```bash
export STOCKWISE_DATA_PROVIDER=twelvedata
export TWELVEDATA_API_KEY=...
stockwise doctor # "Twelve Data answered for AAPL (180 ms)"
stockwise quote AAPL -m us # ends with "Data: Twelve Data"
```
## Providers
| Provider | `STOCKWISE_DATA_PROVIDER` | Key variable | Free tier | Covers on the free tier |
|----------|---------------------------|--------------|-----------|-------------------------|
| Yahoo Finance | unset or `yahoo` | — | unofficial, no key | Everything StockWise does |
| Alpha Vantage | `alphavantage` | `ALPHAVANTAGE_API_KEY` | 25 requests/day; end-of-day; last 100 daily bars | US stocks, BSE (`.BO`), LSE, TSX, XETRA, Shanghai/Shenzhen, FX, crypto |
| Massive (formerly Polygon.io) | `massive` or `polygon` | `MASSIVE_API_KEY` (or `POLYGON_API_KEY`) | 5 requests/min; end-of-day; 2 years of history | US stocks, FX, crypto |
| Twelve Data | `twelvedata` | `TWELVEDATA_API_KEY` | 8 credits/min, 800/day | US stocks, FX, crypto (NSE/BSE need the paid Grow plan) |
| NSE website | `nse` | — | unofficial; ≤3 requests/s | NSE stocks (`.NS`) and NSE indices (`^NSEI`, `^NSEBANK`, ...) — see [NSE and BSE](#nse-and-bse) |
| BSE website | `bse` | — | unofficial; ≤3 requests/s | BSE stocks (`RELIANCE.BO`, `500325.BO`) |
| NSE + BSE | `exchange` | — | as above | `.NS` and NSE indices from NSE, `.BO` from BSE, everything else from Yahoo |
| Finnhub | `finnhub` | `FINNHUB_API_KEY` | 60 calls/min | US quotes, company profile (market cap, industry), analyst recommendation trends, earnings surprises — see [Backup providers](#backup-providers-finnhub-and-fmp) |
| Financial Modeling Prep | `fmp` | `FMP_API_KEY` | 250 calls/day; mostly US listings | Quotes (one request for a whole list), company profile, quarterly and annual income / balance-sheet / cash-flow statements, company screener |
Free-tier limits are the providers' own and change over time; check their
pricing pages. Twelve Data's free plan is licensed for internal, non-display
use only.
**Indian stocks:** none of the keyed free tiers carries NSE, and only Alpha
Vantage carries BSE. With those providers, `TCS` (`TCS.NS`) keeps coming from
Yahoo; `RELIANCE.BO` goes to Alpha Vantage. For prices straight from the
exchanges, use `nse`, `bse` or `exchange` below. SENSEX (`^BSESN`) and
non-Indian indices always come from Yahoo.
## What goes where
| Data | With a provider configured |
|------|----------------------------|
| Quotes (`quote`, `dashboard`, `portfolio`, ...) | Provider for price, day range, volume and change; Yahoo for name, 52-week range, fundamentals and anything else missing. `quote` says which provider answered |
| Bulk scans (`movers`, `screen`, `heatmap`, ...) | Yahoo, when the scan has more symbols than the provider's per-run budget, so one table never mixes sources |
| Daily, weekly, monthly history | Provider, if its plan reaches back over the whole period (weekly and monthly bars are built from daily ones); otherwise Yahoo. Ranges beyond the free plan — over ~140 days on Alpha Vantage, over 2 years on Massive, and `-p max` on both — go straight to Yahoo without spending a request |
| Intraday bars (`-p 1d`, `-p 5d`) | Yahoo (not on any free tier) |
| `search` | Provider's matches, mapped back to Yahoo symbols (`RELIANCE.BSE` → `RELIANCE.BO`, `X:BTCUSD` → `BTC-USD`); Yahoo's when it finds none |
| Fundamentals, news, insider, earnings | Yahoo |
## NSE and BSE
```bash
export STOCKWISE_DATA_PROVIDER=exchange # or nse, or bse; or data_provider = "exchange" in config.toml
stockwise doctor # "NSE answered for RELIANCE.NS (640 ms)", "BSE answered for 500325.BO (...)"
stockwise quote RELIANCE # ... Circuit Limits ₹1,307.20 — ₹1,597.60, Delivery 52.35%, "Data: NSE"
stockwise quote 500325 # a bare 6-digit number is a BSE scrip code (500325.BO)
stockwise movers --universe nifty50
```
**What these are.** NSE and BSE have no free public market-data API. Their
websites load prices from JSON endpoints meant for the site's own pages, and
these providers read the same endpoints. They are **unofficial and
undocumented**: the exchanges can change, rate-limit or block them at any time,
and their terms of use restrict automated access and redistribution of their
data. Use them for your own analysis; don't republish what they return, and
check NSE's and BSE's terms of use yourself. Licensed real-time feeds come from
the exchanges' data vendors (NSE Data & Analytics, BSE's data products) and
from brokers' market-data APIs.
**What StockWise reads**
| | NSE (`nse`) | BSE (`bse`) |
|---|---|---|
| Quote | LTP, change, open, day high/low, previous close, 52-week range, upper/lower circuit; for up to three symbols at a time also volume, market cap and **delivery %** | LTP, change, open, day high/low, previous close |
| Indices | `^NSEI` (NIFTY 50), `^NSEBANK`, `^CNXIT`, `^NSMIDCP` (NEXT 50), `^CNX100`, `^CNX200`, `^CRSLDX` (500), `^NSEMDCP50`, sector indices (`^CNXPHARMA`, `^CNXAUTO`, ...), `^INDIAVIX` — all in one request | — (SENSEX stays on Yahoo) |
| Intraday bars (`-p 1d`) | Today's session, 1m–90m bars built from NSE's price chart, aligned to 09:15 IST; **no volume** (NSE's chart has prices only) | Today's session from BSE's price chart; no volume |
| Daily / weekly / monthly bars | Up to 5 years (one request per year of history) | Up to 5 years (one CSV download) |
| Search | NSE's autocomplete; NSE's listed-equity file (`EQUITY_L.csv`, kept a day) if that fails | BSE's scrip list (kept on disk for a day): ids, scrip codes and company names |
| Symbols | `RELIANCE.NS`, `M&M.NS`, NSE indices | `RELIANCE.BO` (looked up in the scrip list) or `500325.BO` |
Everything else — names when the exchange gives none, fundamentals,
multi-day intraday bars (`-p 5d`), history beyond 5 years or `-p max`,
dividends and splits — comes from Yahoo, exactly as with the other providers.
`--json` quotes from NSE add `deliveryPercent`, `lowerCircuit` and
`upperCircuit` (and `dataSource: "NSE"`); `--csv` has `delivery_percent`,
`lower_circuit` and `upper_circuit` columns, empty for other sources.
**Being a polite client**
- One session per site per run: NSE's home page is loaded once for the
cookies its API expects; an expired session (HTTP 401) is renewed once.
- At most three requests a second per site, with random jitter on top, across
everything a command does.
- Timeouts, HTTP 429 (honouring `Retry-After`, capped at 10 s) and 5xx are
retried twice with exponential backoff.
- **A refusal is final.** HTTP 403 or an "Access Denied" page means the site
doesn't want automated requests right now: StockWise prints one `ⓘ` notice
on stderr, stops asking that site for 15 minutes (the rest of a normal run),
and Yahoo answers. It never retries a refusal with different headers.
- If an endpoint disappears (404) or answers in a shape StockWise doesn't
know, it says *"NSE changed its response format (...)"* once and Yahoo
answers. `doctor` shows the same reason.
- Answers share the Redis / in-memory cache (quotes 30 s, intraday 1 minute,
daily history 10 minutes, search 1 hour); the BSE scrip list and NSE's
equity file are kept in the data directory's `cache/` for a day.
- Big scans stay on Yahoo: a quote request for more than 12 uncached NSE
symbols (8 on BSE) goes to Yahoo in one request rather than waiting on the
rate limit. Members of an index fetched with `--universe` are already priced
by that one NSE request, so `movers --universe nifty500` costs one NSE
request and no Yahoo request, and still works when Yahoo is unreachable;
`screen --universe` also asks Yahoo for P/E and market cap.
**Mixed mode** (`exchange`): `.NS` symbols and NSE indices go to NSE, `.BO`
symbols to BSE, and US stocks, crypto, FX and SENSEX to Yahoo — so a
portfolio holding both `TCS` and `AAPL` gets each price from its own market.
`search` lists NSE matches first, then BSE's.
**Index members** (`--universe` on `movers`, `heatmap` and `screen`) come from
NSE whichever provider is set; see [commands.md](https://iamsaquib8.github.io/stockwise/commands.html#market). Offline,
the built-in lists stand in.
The fields StockWise reads from each endpoint, and how to record a fresh
response for a test fixture, are in
[exchange-responses.md](https://iamsaquib8.github.io/stockwise/exchange-responses.html).
## Backup providers (Finnhub and FMP)
Set a key and the provider joins a **router** that backs Yahoo up — no
`STOCKWISE_DATA_PROVIDER` needed (name one there and it becomes the primary
provider instead):
```bash
export FINNHUB_API_KEY=... # US quotes, profile, analyst trend, earnings surprises
export FMP_API_KEY=... # quotes, statements, screener
stockwise doctor # "Backup providers: Finnhub answered for AAPL (210 ms)"
stockwise config show # finnhub_api_key (set) — the value is never shown
```
Keys stay in the environment. They are sent in a header (Finnhub) or added to
the request when it is sent (FMP), and never reach a cache key, a log line or
an error message.
Each capability goes to the best configured provider that covers the symbol
and still has calls left in its free-tier window, then down the list:
| Capability | Order | Used by |
|---|---|---|
| Quote | Finnhub, then FMP for one to five symbols; FMP first for longer lists (one batch request) | `quote`, scans and every command that quotes, **when Yahoo fails or lacks a symbol** |
| Fundamentals (profile, analyst trend, surprises) | Finnhub, FMP | missing market cap on a short quote list; earnings surprises in `results` when Yahoo has none |
| Statements (quarterly, annual) | FMP | `results`, when Yahoo's statements are unavailable |
| Screener | FMP | US scans (`movers`, `screen`, `heatmap`, `--cap`) when Yahoo fails: FMP lists the companies, the keyed providers price them |
| Chart, search | — | stay with Yahoo and the primary provider |
**Field-level merge.** When two sources each supply part of a quote — Finnhub
the price, FMP the market cap, or Yahoo the price and Finnhub's profile the
market cap — the first source to answer keeps its fields and the others fill
only what is empty. `--json` quotes then carry `meta.sources`, in the order
the sources answered: `{"meta": {"sources": ["Yahoo Finance", "Finnhub"]}}`.
Quotes Yahoo answered completely cost no backup request and carry no `meta`.
Only lists of ten symbols or fewer get holes filled (one extra request each);
bigger tables stay as the first source gave them, so one table never burns a
free tier.
**Free-tier windows.** Finnhub gets 60 calls a minute; FMP gets 16 every two
hours (its 250 a day, with room for two windows to touch). A provider with no
calls left in its window is skipped for the next one, not waited for. A rate
limit or refused key pauses the provider and prints one `ⓘ` notice.
**Built from documentation.** The Finnhub and FMP clients follow the
providers' documented response formats (Finnhub `/quote`, `/stock/profile2`,
`/stock/recommendation`, `/stock/earnings`; FMP's `stable` API:
`batch-quote`, `profile`, `income-statement`, `balance-sheet-statement`,
`cash-flow-statement`, `company-screener`). They are tested against recorded
shapes, not against the live services. Free plans do not reach every
exchange or endpoint; a refused request falls through to the next source.
**BSE filings.** The BSE provider also reads a scrip's corporate
announcements and quarterly-result filings (`AnnGetData`) and the members of a
BSE group (`ListofScripData` with `Group`). These follow the format
open-source BSE clients read and are unconfirmed against the live site. BSE 500
snapshots are not read yet.
## Limits and fallback
- Each command spends at most a small allowance of provider requests —
4 for Alpha Vantage, 5 for Massive, 8 for Twelve Data — so one scan can't burn
a day's allowance or wait a minute for the next slot. The allowance comes
back after a window: a minute for Massive and Twelve Data, 4 h 48 min for
Alpha Vantage (4 a window: two windows can touch, so 5 could reach 29 in a
day against its limit of 25). A long-running `serve`, `tui`,
`mcp` or daemon therefore keeps using the provider instead of dropping to
Yahoo after the first few requests.
- If a provider reports a rate limit, refuses the key, or can't be reached, StockWise prints one
`ⓘ` notice on stderr, stops asking it for a while (to the end of the window
after a rate limit, 3 minutes after a network error, an hour after a refused
key) and uses Yahoo meanwhile. A later pause prints its own notice.
`--json` output on stdout is unaffected.
- Yahoo's cookie-and-crumb handshake, which quotes, fundamentals and options
need, is tried once per call. When it fails, the failure is remembered for
45 seconds and later calls get the same error at once, so a command that makes
dozens of fundamentals calls doesn't repeat six requests and their waits for
each. Charts and search need no crumb and are unaffected.
- When AMFI's NAV file or a scheme's NAV history can't be downloaded, `mf`
commands use the saved copy only if it is at most 7 days old; `mf nav` and
`mf history` then say so under the table and add a `stale` entry (source,
reason, day saved) to `--json`. An older copy is an error that says how old
it is.
- A provider's quote is dated by its own trading day (Alpha Vantage's "latest
trading day", Twelve Data's timestamp, the day of Massive's latest bar), so
an end-of-day quote from yesterday is never taken for today's trade.
- Provider answers share the Redis / in-memory cache (quotes 1–5 minutes,
daily history 10 minutes), so repeating a command doesn't spend another request.
API keys are never part of a cache key or an error message.
- An unknown provider name or a missing key is reported (by every command on
stderr, and by `doctor`) and StockWise carries on with Yahoo.
- `doctor` asks the provider for one `AAPL` quote to show it answers — one
request of your allowance per run. With `nse`, `bse` or `exchange` it asks
for `RELIANCE.NS` and/or `500325.BO` instead and reports each exchange on
its own line.
`--json` quotes from another provider carry a `"dataSource"` field naming it;
Yahoo-only quotes don't have the field.
## Adding a provider
Implement the `MarketData` trait in `src/providers/` (quote, daily series,
search), map Yahoo-style symbols to the provider's syntax and back, and add a
`ProviderKind`. Parse responses in pure functions with fixture tests, and test
the HTTP path against `testutil::MockServer` — tests must never call a real
API or need a real key. A source without an API (a website's JSON) goes
through `providers::session::Session`, which brings the rate limit, retries,
caching and refusal handling.
---
# AI Integration
StockWise works with AI in three ways:
- **Commentary** — `report` and `--ai` add a plain-English section to analysis
commands.
- **`stockwise ask`** — ask a question; the model calls StockWise's read-only
tools (quotes, technicals, your portfolio, ...) and answers from the
results. See [Ask in plain English](#ask-in-plain-english).
- **`stockwise mcp`** — an [MCP](https://modelcontextprotocol.io) server, so
Claude Desktop, Claude Code, Cursor and other assistants can use the same
tools. See [Use StockWise from AI assistants](#use-stockwise-from-ai-assistants-mcp).
By default the model runs **locally** through [Ollama](https://ollama.com): no
stock data, portfolio or credentials leave your machine. If you prefer a
hosted model you can opt in to OpenAI, Anthropic or Google Gemini instead (see
[Cloud providers](#cloud-providers-opt-in)). Everything else in StockWise
works without any AI.
## Setup (local, the default)
```bash
# Install Ollama (macOS; see ollama.com for Linux/Windows)
brew install ollama
# Start the server
ollama serve
# Pull the default model
ollama pull qwen3:14b
# Check that StockWise can see it
stockwise doctor
```
## Commentary: reports and `--ai`
**Dedicated reports** always use the model:
```bash
stockwise report intraday # Notes on today's intraday scan
stockwise report longterm # Research notes on the long-term scores
stockwise report portfolio # Notes on your portfolio (funds at NAV, SGBs from NSE)
stockwise report RELIANCE # Single-stock analysis
```
**Opt-in sections** on analysis commands — add `--ai` (or set `STOCKWISE_AI=1`
to make it the default):
```bash
stockwise analyze RELIANCE --ai
stockwise compare TCS INFY --ai
```
| Command | AI section |
|---------|-----------|
| `analyze` | Valuation in context, trend, risks, what to watch |
| `technical` | Whether the indicators agree, possible scenarios, levels to watch |
| `compare` | How the stocks differ on valuation, growth and risk (no overall pick) |
| `backtest` | How robust the results look, when the strategy works or fails |
| `screen` | Notes on the screened names, value-trap warnings |
| `deep` | Summary of the full report |
## Educational, not advice
Every prompt starts with the same rules: explain what the data shows — signals,
trends, valuation context, risks and what to watch — in neutral, conditional
language, and never tell the reader to buy, sell, hold, accumulate or avoid
anything, never pick a winner, and give no price targets, entry or exit levels,
position sizes or allocations. This follows SEBI's rules for investment advisers
and research analysts (and its guidance on finfluencers): StockWise is neither.
Every AI section ends with a one-line disclaimer:
> AI-generated educational analysis, not investment advice or a recommendation
> to buy, sell or hold. StockWise is not a SEBI-registered investment adviser or
> research analyst.
A model can still ignore its instructions; treat its output as commentary. The
prompts live in `src/ai.rs` (`ai::prompts`), and a test checks that none of
them asks for a verdict or a call. When the prompts change, `PROMPT_VERSION` is
bumped: it is part of the cache key, so an answer cached under older wording is
never shown again.
AI is opt-in because a local model typically takes **30–60 seconds** per
answer, which would turn these sub-second commands into minute-long waits.
StockWise prints a progress line while the model works, and caches answers for
an hour when Redis is available.
The `daemon` workers write an end-of-day AI note when local Ollama is running.
With a cloud provider they only do so when started with `--ai`.
## Ask in plain English
```bash
stockwise ask "how is my portfolio doing?"
stockwise ask "compare TCS and INFY on RSI and one-year risk" --verbose
stockwise ask "what SIP reaches 1 crore in 15 years at 12%?"
stockwise ask "which of my holdings are long term for tax?" --json
```
`ask` sends your question and the list of StockWise tools to the configured
model. The model picks tools, StockWise runs them (read-only, against live
data and your local portfolio), sends the results back, and the model answers
in plain English. The answer ends with the tools that ran. The tools are the
same ones the [MCP server](#tools) offers.
| Option | Default | Meaning |
|--------|---------|---------|
| `--verbose`, `-v` | off | Print each tool call and the size of its result on stderr |
| `--json` | off | Print the transcript: question, answer, every tool call with its arguments, token usage |
| `--max-steps N` | 6 | Most model requests (1-20); the last one runs without tools, so the model must answer |
| `--max-tokens N` | 100000 | Token budget across requests (input + output); once used, the next request must answer |
At most 12 tool calls run per question, and each result is kept to about
24 KB (12 KB for local models): long price histories keep their newest bars
and option chains their strikes nearest the money. With Ollama the model
must answer once the conversation fills most of its 16k-token context.
**Models.** `ask` needs a model with tool (function) calling:
- Ollama: `qwen3` (the default `qwen3:14b`), `llama3.1`, `qwen2.5`,
`mistral-nemo` and other models tagged *tools* on ollama.com. StockWise asks
Ollama for a 16k-token context, because the tool definitions alone take a
few thousand tokens. A model without tool support gets a clear error naming
one that has it.
- Cloud (opt-in, see below): OpenAI (Responses API function calling),
Anthropic (Messages API tool use) and Gemini (`generateContent` function
calling). Running `ask` is the opt-in, as with `report`; `--ai` is not
needed.
Answers are educational, not financial advice: the model is told to describe
what the data shows and its risks, not to tell you to buy or sell. **The tools
cannot place orders.**
**Privacy.** With a cloud provider, the question and every tool result go to
that provider — including your holdings, lots and tax figures when the model
calls `portfolio_summary`, `holdings_lots` or `tax_estimate`. Keep the default
local Ollama if that matters to you. The first request of each run prints the
usual notice.
## Use StockWise from AI assistants (MCP)
> **Guide:** [Use StockWise from Claude or Cursor](https://iamsaquib8.github.io/stockwise/guide-mcp.html) is a short walkthrough of this section.
`stockwise mcp` is a [Model Context Protocol](https://modelcontextprotocol.io)
server built on the official Rust SDK. AI assistants start it and call its
tools; it runs on your machine with your data directory and needs no account.
```bash
stockwise mcp # stdio — what desktop and editor clients use
stockwise mcp --http 127.0.0.1:8788 # Streamable HTTP at http://127.0.0.1:8788/mcp
stockwise mcp --list-tools # the tools and their JSON schemas
```
### Set it up
**Claude Code**
```bash
claude mcp add stockwise -- stockwise mcp
claude mcp add stockwise -- stockwise mcp -m us # US tickers by default
```
**Claude Desktop** — Settings → Developer → Edit Config, then restart:
```json
{
"mcpServers": {
"stockwise": {
"command": "stockwise",
"args": ["mcp"]
}
}
}
```
Use the full path from `which stockwise` (`where stockwise` on Windows) if
Claude Desktop can't find the command.
**Cursor** — `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project):
```json
{
"mcpServers": {
"stockwise": {
"command": "stockwise",
"args": ["mcp"]
}
}
}
```
**Without installing** — once the PyPI wheels are published, any client can
run it through [uv](https://docs.astral.sh/uv/): command `uvx`, args
`["stockwise", "mcp"]`. With Docker: command `docker`, args
`["run", "-i", "--rm", "-v", "stockwise:/data", "ghcr.io/iamsaquib8/stockwise", "mcp"]`.
**Other clients** — command `stockwise`, args `["mcp"]`, transport stdio; or
point an HTTP client at `stockwise mcp --http`.
Settings work as usual: `-m us` (or `STOCKWISE_MARKET`) sets the market for
bare tickers, `--portfolio NAME` picks a named portfolio, `STOCKWISE_HOME`
the data directory. Add them to `args` or the client's `env`.
### Tools
There are 35 tools. Every tool only reads. Each result is JSON with the data, a short "not
financial advice" `note`, and `truncated` when long lists were shortened.
Tools that take tickers accept `market` (`in`, `us`, `crypto`, `fx`,
`commodity`, `uk`, `de`, `jp`, `hk`, `sg`, `ca`, `au`).
| Tool | What it returns | Same as |
|------|-----------------|---------|
| `quote` | Price, change, day and 52-week range, volume, market cap, P/E for up to 20 tickers | `quote --json` |
| `history` | OHLCV bars (most recent `limit`, default 60) with the period's change, high and low | `history --json` |
| `technical` | SMAs, EMAs, RSI, MACD, Bollinger Bands, ATR, VWAP and a signal tally | `technical --json` |
| `risk` | Volatility, Sharpe, Sortino, Calmar, max drawdown, value at risk | `risk --json` |
| `verdict` | The 6-pillar scorecard: each pillar's 0–100 score, band and the facts behind it, the composite, and the data coverage. Pillars without data are marked as such. No rating, entry, stop or target, and the description tells the model it is not a recommendation | `verdict --json` |
| `scorecard` | Piotroski F-score and Altman Z-score with every criterion and its formula | `score --json` |
| `insights` | Positive, negative and neutral findings plus the shareholding split for one ticker | `insights --json` |
| `events` | Recent and upcoming corporate events with the price reaction around each | `events --json` |
| `quarterly_results` | Last 8+ quarters (or fiscal years) of results with YoY, QoQ, margin change, TTM, EPS vs consensus, bank NII and NPA | `results --json` |
| `search` | Tickers matching a company name | `search --json` |
| `markets` | Index levels; optional crypto, FX and global benchmarks | `markets --json` |
| `mf_search` / `mf_nav` / `mf_sip` | Mutual fund search, NAV with expense ratio, past SIP returns with XIRR (by AMFI code or ISIN) | `mf search/nav/sip --json` |
| `options_chain` / `options_pcr` / `options_maxpain` | Strikes around the money with OI, IV and greeks; put/call ratio; max pain | `options chain/pcr/maxpain --json` |
| `options_oi` | Open-interest walls and the build-up classification (long build-up, short covering, short build-up, long unwinding) | `options oi --json` |
| `options_payoff` | A preset strategy's max profit/loss, breakevens, probability of profit, payoff table | `options payoff --json` |
| `screen` | A preset screen (undervalued, growth, dividend, momentum, bluechip) | `screen --json` |
| `backtest` | A built-in strategy on one ticker after costs, against buy and hold (last 10 trades) | `backtest --json` |
| `portfolio_summary` | Holdings, value, P&L, weights, XIRR and totals; unpriced holdings listed, never valued at 0 | `portfolio --json` |
| `holdings_lots` | Purchase lots with days held and short/long term, and recent sales | `portfolio lots` |
| `tax_estimate` | Realized and if-sold-today capital gains with India's set-off rules and ₹1.25 lakh exemption (or US/UK) | `tax --json` |
| `tax_advance` | India's four advance-tax instalments from your realised gains, with Sec. 234C interest on a shortfall | `tax advance --json` |
| `harvest` | Lot-level loss-harvesting candidates and the unused ₹1.25 lakh LTCG exemption | `harvest --json` |
| `rebalance` | Trades to reach your saved target weights (or equal weight); buy-only when `inflow` is given | `rebalance --json` |
| `calendar` | Results, ex-dividend and split dates for tickers, or for your holdings and watchlist | `calendar --json` |
| `ipo_board` / `ipo_analyze` | Open, upcoming and recently listed IPOs from NSE; one issue's terms, subscription and allotment odds | `ipo --json` |
| `goal_sip` / `goal_education` | SIP for a goal: horizon-matched return, tax at redemption, outcome bands | `goal sip\|education --json` |
| `goal_retire` | Retirement corpus and SIP, withdrawal rate, survival after a 30% first-day fall, year-1 SWP vs FD tax (no outcome bands) | `goal retire --json` |
| `goal_swp` / `goal_prepay` | Withdrawal runway (SWP vs FD tax); loan prepayment against a SIP | `goal swp\|prepay --json` |
| `goal_list` | Your saved goals with live funded share | `goal list --json` |
| `alerts_list` | Your alerts checked against live data | `alert check --json` |
| `watchlist` | Your watchlist with live prices | `watch --json` |
Hosts see each tool's read-only and open-world hints; the portfolio tools are
marked as not reaching outside your machine.
### Resources and prompts
| Resource | Content |
|----------|---------|
| `stockwise://portfolio` | The portfolio as `portfolio --json` prints it |
| `stockwise://watchlist` | The watchlist with prices |
| `stockwise://alerts` | Alerts with their live status |
| Prompt | What it asks for |
|--------|------------------|
| `review_portfolio` (optional `focus`) | Value, P&L, concentration and risk of your holdings, without buy/sell calls |
| `tax_harvest_check` | Unrealized gains and losses by lot before 31 March, the LTCG exemption left and the set-off rules |
| `explain_option_chain` (`symbol`, optional `expiry`) | OI, PCR, max pain, IV and greeks in plain English |
### Orders stay with you
The MCP server never places, modifies or cancels orders, and never reads
broker credentials. That is a deliberate choice: an assistant can misread a
request, and a prompt hidden in a web page or document it read could try to
trade for you.
`stockwise mcp --allow-orders` adds one more tool, `order_preview`. It checks
an order you are considering against the last price (the same trigger and
price checks as `trade`), estimates its value, and returns the
`stockwise trade` command — for example
`stockwise trade buy TCS 2 --broker zerodha --limit 4000` — for **you** to
review and run. That command shows its own preview, applies the
`max_order_value` cap from your broker config and asks you to type `yes`.
Nothing is sent to a broker from the MCP server.
### HTTP
`--http ADDR` serves [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http)
at `http://ADDR/mcp`, stateless, with JSON responses. It follows the same
rules as `stockwise serve`:
- Loopback addresses only (`127.0.0.1:8788`, `localhost:8788`) unless you
pass `--allow-remote`, which requires `STOCKWISE_API_TOKEN` (16+ characters);
every request must then send `Authorization: Bearer `.
- Without a token, requests must name a loopback host (`Host` header) and
come from no browser origin or a `localhost` one, so a web page you visit
can't reach it through DNS rebinding.
- Loopback alone does **not** authenticate operating-system users. On a
shared machine, another local user can call the unauthenticated MCP HTTP
endpoint and read available portfolio data. Set `STOCKWISE_API_TOKEN` even
when listening only on `127.0.0.1`, and configure your HTTP MCP client to
send `Authorization: Bearer `.
Put a TLS proxy in front of it before exposing it beyond your network.
### Registry and Claude Desktop extension
- [`server.json`](https://github.com/iamsaquib8/stockwise/blob/main/server.json) describes the server for the
[official MCP registry](https://registry.modelcontextprotocol.io) as
`io.github.iamsaquib8/stockwise`. It lists the crates.io package and the
GHCR image (`ghcr.io/iamsaquib8/stockwise`); `uvx stockwise mcp` works once
the PyPI wheels are published.
- [`packaging/mcpb/`](https://github.com/iamsaquib8/stockwise/blob/main/packaging/mcpb/README.md) builds a Claude Desktop
extension (`.mcpb`) from a release binary.
### For agents: Agent Skill and llms.txt
- [`skills/stockwise/SKILL.md`](https://github.com/iamsaquib8/stockwise/blob/main/skills/stockwise/SKILL.md) is an
[Agent Skill](https://agentskills.io) that tells Claude Code and other agents
how to use the CLI (`--json`, markets, safe commands) and the MCP server.
Copy the folder into `~/.claude/skills/` (or a project's `.claude/skills/`).
- The documentation site publishes
[`llms.txt`](https://iamsaquib8.github.io/stockwise/llms.txt) (an index of
the docs and the cheatsheet's commands) and
[`llms-full.txt`](https://iamsaquib8.github.io/stockwise/llms-full.txt)
(every page in one file), and a Markdown copy of each page.
## Configuration
| Variable | Config key | Default | Purpose |
|----------|------------|---------|---------|
| `STOCKWISE_AI_PROVIDER` | `ai_provider` | `ollama` | `ollama`, `openai`, `anthropic` (or `claude`), `gemini` (or `google`) |
| `STOCKWISE_MODEL` | `model` | per provider | Model name; overrides the provider's default below |
| `OLLAMA_HOST` | `ollama_host` | `http://localhost:11434` | Ollama server (`host:port` or a full URL). A server that is not on this computer is refused over plain `http://` unless `STOCKWISE_ALLOW_REMOTE_OLLAMA=1` is set (use `https://` or a tunnel when you can); `ask` says when the server is remote |
| `STOCKWISE_ALLOW_REMOTE_OLLAMA` | — | off | `1` to allow plain `http://` to an Ollama server on another machine. Environment only |
| `OPENAI_API_KEY` | — | — | Key for `STOCKWISE_AI_PROVIDER=openai` |
| `ANTHROPIC_API_KEY` | — | — | Key for `STOCKWISE_AI_PROVIDER=anthropic` |
| `GEMINI_API_KEY` | — | — | Key for `STOCKWISE_AI_PROVIDER=gemini` |
| `STOCKWISE_AI` | `ai` | off | `1` to add AI sections to analysis commands by default (`--no-ai` turns it off for one run) |
Environment variables win over the [config file](https://iamsaquib8.github.io/stockwise/commands.html#configuration-file).
API keys are read from the environment only, never from the config file.
```bash
STOCKWISE_MODEL=llama3.1:8b stockwise report portfolio
STOCKWISE_ALLOW_REMOTE_OLLAMA=1 OLLAMA_HOST=192.168.1.20:11434 stockwise report TCS
```
If the Ollama model isn't pulled, StockWise tells you the exact `ollama pull`
command instead of printing an empty section. An unknown
`STOCKWISE_AI_PROVIDER` value is an error, never a silent fallback: StockWise
won't guess where to send your data.
## Cloud providers (opt-in)
| Provider | `STOCKWISE_AI_PROVIDER` | Key | Default model | API |
|----------|-------------------------|-----|---------------|-----|
| OpenAI | `openai` | `OPENAI_API_KEY` | `gpt-6.1-sol` | Responses API (`/v1/responses`), `store: false` |
| Anthropic | `anthropic` | `ANTHROPIC_API_KEY` | `claude-opus-5-5` | Messages API (`/v1/messages`) |
| Google Gemini | `gemini` | `GEMINI_API_KEY` | `gemini-3.8-flash` | `generateContent` |
```bash
export STOCKWISE_AI_PROVIDER=anthropic
export ANTHROPIC_API_KEY=... # from console.anthropic.com
stockwise doctor # shows provider and model, never the key
stockwise report RELIANCE # first cloud request of the run prints a notice
```
To use a cheaper or newer model, set `STOCKWISE_MODEL` — for example
`gpt-6-luna`, `claude-sonnet-5-5`, `claude-haiku-5-5` or
`gemini-3.5-flash-lite`. `STOCKWISE_MODEL` applies to whichever provider is
active, so change or unset it when you switch providers.
The prompts are the same for every provider. On Claude models that support it
(Opus 5.5, Opus 5, Sonnet 5.5, Fable 5.x), StockWise asks the API to retry a
safety-classifier decline on Anthropic's recommended fallback model
(`fallbacks: "default"`); a request that is still declined is reported as an
error, not as an empty answer.
### Privacy
- **Nothing is sent unless you choose a cloud provider.** The default is
Ollama on your own machine.
- **Choosing a provider is not enough on its own.** A cloud model is called
only for `report` (which you run to get AI output) or when you add `--ai`
(or set `STOCKWISE_AI=1`). Plain commands never contact it.
- **What is sent:** the prompt for that command — the market data StockWise
fetched (prices, ratios, indicators, backtest numbers) and, for
`report portfolio`, your holdings with quantities, cost and fund names. No broker
credentials, API keys for other services, or data-directory files are
included.
- **You are told when it happens:** the first cloud request of each run prints
`ⓘ Sending this request's data to (): market data, and your portfolio if a tool reads it` on stderr.
- Keys are read from the environment and sent only in request headers, never
in URLs, logs or error messages. `doctor` names the variable it found, not
its value.
- Requests to OpenAI set `store: false`. Each provider's own data-retention
policy still applies; read it before sending portfolio data.
- Answers are cached for an hour in Redis (when running) under a hash of the
prompt version, provider, model and prompt, so repeating a command doesn't
send it again.
## Suggested local models
| Model | Download | Notes |
|-------|----------|-------|
| `qwen3:14b` | ~9 GB | Default. Strong structured analysis |
| `gemma3:12b` | ~8 GB | Good general reasoning |
| `phi4:14b` | ~9 GB | Good with financial metrics |
| `llama3.1:8b` | ~5 GB | Lighter and faster |
Model output is commentary, not advice. See [DISCLAIMER.md](https://iamsaquib8.github.io/stockwise/disclaimer.html).
---
# Backtesting
`stockwise backtest` replays a strategy over daily price history and reports
what it would have made **after trading costs**. It can test one stock, a
portfolio of stocks sharing one pot of capital, or run a walk-forward test
that tunes parameters on old data and scores them only on newer data.
Strategies are small TOML files; the six built-ins are files too.
> Backtests describe the past. They are not predictions and not financial
> advice — see [DISCLAIMER.md](https://iamsaquib8.github.io/stockwise/disclaimer.html).
## Quick start
> **Guide:** [Backtest and practise on past markets](https://iamsaquib8.github.io/stockwise/guide-practice.html) walks through a backtest and a replay.
```bash
stockwise backtest INFY -s macd # one stock, 2 years, Indian delivery costs
stockwise backtest TCS -s rsi -P oversold=25 # change a parameter
stockwise backtest AAPL -m us -s sma -p 5y # US stock, US cost model
stockwise backtest portfolio TCS INFY HDFCBANK -s sma --rebalance monthly
stockwise backtest walkforward RELIANCE -s rsi --train 1y --test 3mo
stockwise backtest strategies # list built-in and your own strategies
stockwise backtest TCS -s file:my_rules.toml # your own strategy file
```
Every form takes `--json`.
## How a backtest trades
- **Data.** Daily bars from Yahoo Finance for `-p` (default `2y`; `5y` for
walk-forward). A bar missing any of open, high, low or close is dropped,
never filled in. Prices are split-adjusted; dividends are not credited.
- **Signals at the close, fills at the next open.** Entry and exit rules are
checked on each bar's close and filled at the **next bar's open** — the
first price you could actually trade at. `--fill close` fills at the signal
bar's close instead (how StockWise 0.3 and earlier filled); it peeks at a price that
only exists once the bar is over, so it flatters results.
- **Long only, whole shares.** An entry buys as many whole shares as the cash
allows after costs; an exit sells them all. Cash earns nothing. If the
capital can't buy one share the entry is skipped and counted.
- **Entry and exit on the same bar** cancel: no new position is opened, and
an open one is closed.
- **Stops and targets** (`stop_loss_pct`, `take_profit_pct` in a strategy
file) are measured from the entry price and checked against each bar's low
and high. A gap through the level fills at the open. If one bar touches
both, the stop is assumed to have hit first.
- **The last bar.** A position still open at the end is sold at the last
close, with costs, and listed as `still open`.
## Costs
Costs are on by default. `--costs auto` (the default) uses the crypto model
for coins (`-m crypto`, `BTC-USD`), the FX model for currency pairs (`-m fx`,
`USDINR=X`), India delivery for INR stocks, and the US model for everything
else. The Indian presets are refused for coins and currency pairs.
| `--costs` | What it charges |
|-----------|-----------------|
| `in-delivery` | Brokerage ₹20 or 0.1% per order, whichever is lower · STT 0.1% on buys and sells · exchange 0.00297% · SEBI ₹10/crore · GST 18% on brokerage + exchange + SEBI · stamp duty 0.015% on buys · DP charge ₹15.93 per sell |
| `in-intraday` | Brokerage ₹20 or 0.03%, lower · STT 0.025% on sells · exchange, SEBI and GST as above · stamp duty 0.003% on buys · no DP charge |
| `us` | Commission-free; slippage only |
| `crypto` | 0.1% exchange fee per order; 10 bps slippage |
| `fx` | No commission; 2 bps slippage for the spread |
| `none` | Nothing — the gross result |
The stock presets add **5 bps of slippage** per fill: buys fill 0.05% above
the price, sells 0.05% below. Override with `--slippage-bps`, and
brokerage with `--brokerage`: `20` (flat per order), `0.03%` (percent of the
order), or `20,0.03%` (whichever is lower). A Zerodha delivery account is
`--brokerage 0`. Rates are NSE equity rates as of 2025; check your broker's
contract note.
**Worked example** (delivery): buy 100 shares at ₹1,000 and sell at ₹1,100.
| | Buy (₹1,00,000) | Sell (₹1,10,000) |
|---|---:|---:|
| Brokerage | 20.00 | 20.00 |
| STT | 100.00 | 110.00 |
| Exchange | 2.97 | 3.27 |
| SEBI | 0.10 | 0.11 |
| GST | 4.15 | 4.21 |
| Stamp duty | 15.00 | — |
| DP | — | 15.93 |
| **Total** | **142.22** | **153.51** |
The ₹10,000 gross profit keeps ₹9,704.26 — before slippage. A unit test
checks these numbers.
Every report shows the **net** return (after costs), the **gross** return
(the same rules with `--costs none`), and the money paid in costs.
## What the numbers mean
| Metric | Meaning |
|--------|---------|
| Net / gross return | Change in portfolio value with / without costs |
| Buy & hold | The price change over the period (for a portfolio: weighted by the starting weights, no costs) |
| Alpha | Net return minus buy & hold |
| CAGR | Compound annual growth rate; shown for three months of data or more |
| Max drawdown | Largest fall from a peak, on the daily equity curve |
| Sharpe / Sortino | From daily returns of the equity curve, annualised with √252 (√365 for crypto, √260 for FX), risk-free rate 0 |
| Exposure | Average share of the portfolio invested rather than in cash |
| Win rate, avg win / loss | Per round trip, after costs |
| Profit factor | Money made on winners ÷ money lost on losers, after costs |
## Portfolio backtests
```bash
stockwise backtest portfolio TCS INFY HDFCBANK RELIANCE -s sma
stockwise backtest portfolio TCS INFY -s rsi --weights 0.7,0.3
stockwise backtest portfolio AAPL MSFT NVDA -m us -s macd --weights inverse-vol --rebalance monthly
```
Each symbol gets a sleeve of the capital — its weight — and trades the
strategy on its own; a flat sleeve holds cash. The report adds a per-symbol
table: trades, win rate, net P&L, **contribution** (sleeve P&L as a percent of
starting capital; the contributions add up to the total return), and how
often the symbol was held.
- `--weights equal` (default), explicit weights (`0.5,0.3,0.2`, normalised,
one per symbol), or `inverse-vol`: weight ∝ 1 ÷ volatility of the last 63
daily returns, computed at each rebalance from data before that day.
`inverse-vol` needs `--rebalance`.
- `--rebalance none|monthly|quarterly`: on the first trading day of each
month or quarter, at the open, overweight positions are trimmed, cash is
moved between sleeves, and underweight positions are topped up, so every
sleeve is back at its weight. Drifts under 1% are left alone. Trades pay
costs as usual.
- Symbols are aligned by date; a symbol with no bar on a day keeps its last
price. Every symbol must trade in the same currency and use the same cost
model (stocks and coins can't share a portfolio backtest unless you pass
one `--costs`).
## Walk-forward
A single backtest picks parameters with hindsight. Walk-forward avoids that:
```
history: |----- train 1 -----|- test 1 -|
|----- train 2 -----|- test 2 -|
|----- train 3 -----|- test 3 -|
```
For each window, every parameter combination in the strategy's `[grid]` is
backtested on the train window; the one with the best Sharpe (among those
that traded) trades the following test window. Only test windows count. The
test windows are chained with compounding capital into one out-of-sample
equity curve, after costs.
Positions carry across windows: the chosen parameters trade from the start of
their train window, so a position they would already hold is open when the
test window starts, and a position open at a window's end is valued, not
sold — only the last window sells at the final close. A trade is listed in
the window it closes in. Switching parameters between windows is assumed to
cost nothing.
```bash
stockwise backtest walkforward RELIANCE -s rsi --train 1y --test 3mo -p 10y
```
- `--train` / `--test` take `90d`, `13w`, `6mo`, `1y`, counted in daily bars:
for stocks a week is 5, a month 21 and a year 252 trading days; crypto
trades every day (7, 30, 365); FX years are 260 weekdays. Train needs 20
bars or more, test 5.
Windows step forward by the test length; a short final test window is kept
if it has at least half the test length.
- The report lists each test window with the chosen parameters, in-sample
return and Sharpe, and out-of-sample return and Sharpe, then the stitched
out-of-sample result and an **overfitting check**:
- *Walk-forward efficiency*: out-of-sample annualised return ÷ the average
in-sample annualised return.
- **Verdict**: `inconclusive` with fewer than 2 windows or no
out-of-sample trades; `no edge` if it loses money even in-sample;
`likely overfit` if out-of-sample loses money, keeps under 30% of the
in-sample return, or its Sharpe is more than 1.0 lower; `degraded` under
60% or a Sharpe gap over 0.5; otherwise `robust`.
- A strategy without a `[grid]` has nothing to tune; walk-forward then checks
how stable fixed parameters are across periods.
## Strategy files
`-s` takes:
1. A built-in: `rsi`, `macd`, `sma` (`sma_crossover`), `bb` (`bollinger`),
`vwap`, `mr` (`mean_reversion`).
2. The name of a file in your strategies folder: `-s trend_pullback` loads
`trend_pullback.toml` from
- `strategies/` next to your config file — `stockwise config path` shows
where that is (`~/Library/Application Support/stockwise/` on macOS,
`~/.config/stockwise/` on Linux, `%APPDATA%\stockwise\` on Windows), or
- `$STOCKWISE_HOME/strategies/` when `STOCKWISE_HOME` is set.
`stockwise backtest strategies` prints the folder it uses.
Built-in names win over files of the same name. A `NAME.wasm` plugin in
the folder is found too (after `NAME.toml`).
3. A path: `-s file:path/to/rules.toml` (or any path ending in `.toml`).
4. A WASM plugin: `-s wasm:path/to/strategy.wasm` — see
[WASM plugins](#wasm-plugins). Needs a build with `--features wasm-plugins`.
The built-ins live in [`examples/strategies/`](https://github.com/iamsaquib8/stockwise/tree/main/examples/strategies/) next to
two more examples (`trend_pullback`, `breakout`) — copy one to start.
`stockwise backtest strategies` lists everything and checks your files;
`stockwise backtest strategies FILE...` checks specific files. Both exit 1 if
a file has errors.
### Format
```toml
name = "trend_pullback" # optional; defaults to the file name
description = "Buy RSI dips above the 200-day average"
entry = ["close > sma(200)", "rsi(14) < $dip"] # all must hold
exit = "rsi(14) > 70 or close < sma(200)"
stop_loss_pct = 8 # optional, from the entry price
take_profit_pct = 15 # optional
[params] # numbers, used as $name
dip = 35
[grid] # optional: values walk-forward tries
dip = [30, 35, 40]
```
| Key | |
|-----|---|
| `entry` | A condition, or a list of conditions that must all hold. Required |
| `exit` | Same. Optional when `stop_loss_pct` or `take_profit_pct` is set |
| `stop_loss_pct` | Exit when the price falls this far below the entry (0–100) |
| `take_profit_pct` | Exit when the price rises this far above the entry |
| `[params]` | Default parameter values; override with `-P name=value` (repeatable). A `-P` value also fixes that parameter for walk-forward |
| `[grid]` | For each parameter, the values walk-forward tries (at most 500 combinations) |
Unknown keys are errors, so a typo like `stop_los_pct` is caught.
### Conditions
A condition compares two values:
| Operator | True on a bar when |
|----------|--------------------|
| `<` `<=` `>` `>=` | the comparison holds |
| `a crosses_above b` (or `crosses above`) | `a > b` on this bar and `a <= b` on the previous one |
| `a crosses_below b` (or `crosses below`) | `a < b` on this bar and `a >= b` on the previous one |
Combine conditions with `and`, `or`, `not` (or `&&`, `||`) and parentheses.
Values can be numbers, `$params`, the series below, and `+ - * /` between
them: `close < sma(20) * (1 - $band_pct / 100)`. Keywords and indicator names
are case-insensitive; parameter names are not.
| Series | |
|--------|---|
| `close` `open` `high` `low` `volume` | The bar's prices and volume |
| `sma(n)` `ema(n)` | Moving averages of the close |
| `rsi(n)` | Wilder RSI of the close |
| `atr(n)` | Average true range |
| `macd` `macd_signal` `macd_hist` | MACD(12, 26, 9) line, signal line, histogram; or `macd_hist(fast, slow, signal)` |
| `bb_upper(n)` `bb_middle(n)` `bb_lower(n)` | Bollinger Bands, 2 standard deviations; `bb_upper(n, k)` for k |
| `vwap` | Volume-weighted average price from the first bar |
| `highest(n)` `lowest(n)` | Highest high / lowest low of the previous `n` bars (not including this one) |
These are the indicators of `src/technical.rs`; a test checks that every value
matches. Periods are whole numbers from 1 to 1000 and may be `$params`.
While an indicator is warming up it has no value, and a condition that uses
it is neither true nor false — so nothing fires, even under `not`. Division
by zero behaves the same way.
The file is checked before anything runs, with messages that name the file
and the condition:
```
Error: rules.toml: entry "rsx(14) < 30": unknown indicator `rsx`. Available: close, open, ...
Error: rules.toml: `$dip` is used in a condition but not defined under [params]
Error: strategy `rsi` has no parameter `oversld` (it has: overbought, oversold, period)
```
## Strategies in the intraday bot
```bash
stockwise intraday 50000 1.5 -s trend_pullback
```
`intraday -s` adds a strategy file as one more signal next to the bot's nine:
a **Long** vote (strength 70) when its entry rule holds on the latest daily
bar, a **Short** vote (strength 60) when its exit rule holds. The rule gets
its own daily history (one year, or more for slow indicators), so the
built-in signals are unchanged. The vote shows as `Custom rule` in the plan's
strategy list.
Not yet: `daemon intraday` and `sim start` don't take `-s`.
## WASM plugins
**Experimental.** When a strategy needs state or logic that TOML conditions
can't express — a custom indicator, a regime filter, a model — write it in
any language that compiles to WebAssembly and load it with `-s wasm:PATH`:
```bash
cargo install stockwise --features wasm-plugins
cd examples/wasm-strategy-rsi
cargo build --target wasm32-unknown-unknown --release # rustup target add wasm32-unknown-unknown
cd -
P=examples/wasm-strategy-rsi/target/wasm32-unknown-unknown/release/wasm_strategy_rsi.wasm
stockwise backtest RELIANCE -s wasm:$P -P oversold=25
stockwise backtest portfolio TCS INFY HDFCBANK -s wasm:$P
stockwise backtest walkforward TCS -s wasm:$P
stockwise intraday 50000 1.5 -s wasm:$P
stockwise backtest strategies $P # check a plugin: name, ABI, parameters
```
A plugin works everywhere a TOML strategy does: single, portfolio and
walk-forward backtests, `-P` overrides, `[grid]`-style tuning, and the
intraday bot's extra vote. Copy it into your strategies folder as
`NAME.wasm` to run it as `-s NAME`; `backtest strategies` lists it with its
declared name and ABI version. `-s wasm:FILE.wat` also loads WebAssembly
text, handy for tiny experiments.
[`examples/wasm-strategy-rsi`](https://github.com/iamsaquib8/stockwise/tree/main/examples/wasm-strategy-rsi/) is a complete
plugin in Rust with no dependencies: RSI mean reversion with exactly the
built-in `rsi` strategy's signals (a test checks every grid combination).
[`src/backtest/fixtures/level.wat`](https://github.com/iamsaquib8/stockwise/blob/main/src/backtest/fixtures/level.wat) is the
smallest complete one, in WebAssembly text.
### Sandbox
- **No imports.** A plugin gets no host functions: no WASI, files, network,
clock or randomness. A module that imports anything is refused at load
(build for `wasm32-unknown-unknown`, not `wasm32-wasip1`).
- **Fuel.** Each call runs on a budget of about one unit per instruction:
10 million for a `stockwise_on_bar` call, 50 million for instantiation
and each setup call. An endless loop stops with an error at the first bar.
- **Memory.** 64 MB of linear memory per instance, one memory, plugin files
up to 16 MB, and wasmi's strict limits on module structure (functions,
globals, tables, data segments).
- **Errors, not crashes.** A trap (a Rust panic, an out-of-bounds access,
division by zero, stack overflow), running out of fuel or memory, or a
return value outside the ABI ends the command with exit code 1 and a
message naming the plugin, the call and the bar:
```
Error: loop.wasm: `stockwise_on_bar` on bar 1 of 496 (2024-10-23): ran past its budget of 10000000 instructions for one call — an endless loop?
Error: src/backtest/fixtures/level.wat: the plugin rejected {"level":0.0} (`stockwise_init` returned 1): level must be a whole number above 0
```
- **No look-ahead.** The plugin sees bars one at a time, oldest first, so a
signal can only depend on the bars before it — the same guarantee as TOML
rules. Signals take effect as usual: at the next bar's open by default.
### ABI (version 1)
A plugin is a WebAssembly module with these exports. Integers are `i32` /
`i64`; strings cross as UTF-8 in the plugin's exported `memory`, returned as
one `i64` packing `(pointer << 32) | length`.
| Export | Signature | |
|--------|-----------|---|
| `stockwise_abi_version` | `() -> i32` | Required. Returns `1` |
| `stockwise_on_bar` | `(time: i64, open: f64, high: f64, low: f64, close: f64, volume: f64) -> i32` | Required. One call per daily bar, oldest first; returns the signal at that bar's close |
| `memory` | memory | Required by the exports below |
| `stockwise_meta` | `() -> i64` | Optional. Packed pointer/length of the metadata JSON |
| `stockwise_alloc` | `(len: i32) -> i32` | With `stockwise_init`. A buffer of `len` bytes for the parameters |
| `stockwise_init` | `(ptr: i32, len: i32) -> i32` | Optional; required when `params` are declared. Receives the parameters; returns `0`, or anything else to refuse them |
| `stockwise_error` | `() -> i64` | Optional. Packed pointer/length of a message explaining the last refusal or error |
**Signals.** `stockwise_on_bar` returns `0` (nothing), `1` (entry), `2`
(exit) or `3` (both: an exit while holding, nothing when flat — as when a TOML
strategy's entry and exit rules both hold). A negative value is an error
reported by the plugin (with `stockwise_error`'s message); anything else is
refused as bad output. `time` is the bar's start in Unix seconds (UTC);
`volume` may be 0.
**Lifecycle.** At load, StockWise instantiates the module once to check the
exports and their signatures, call `stockwise_abi_version` and read
`stockwise_meta`. Then every price series — each symbol, each parameter set
in walk-forward, each stock the intraday bot scans — gets a **fresh
instance**: `stockwise_alloc(len)`, the parameters written there,
`stockwise_init(ptr, len)`, then `stockwise_on_bar` for each bar. Keep state
in globals; it never leaks between series. Parameter defaults and every
`grid` combination are tried through `stockwise_init` at load, so a plugin
can refuse values up front.
**Metadata** (`stockwise_meta`), every key optional, unknown keys refused:
```json
{
"name": "rsi-wasm",
"description": "RSI(14) mean reversion as a WASM plugin",
"params": { "period": 14, "oversold": 30, "overbought": 70 },
"grid": { "oversold": [25, 30, 35], "overbought": [65, 70, 75] },
"lookback": 15,
"stop_loss_pct": 8,
"take_profit_pct": 15
}
```
`name` defaults to the file name. `params` and `grid` follow the TOML rules
(names of letters, digits and `_`; at most 500 grid combinations); `-P` sets
a parameter and fixes it for walk-forward. `lookback` (default 0, at most
10,000) is how many bars the plugin needs before its first signal: backtests
refuse shorter histories and the intraday bot fetches enough.
`stop_loss_pct` and `take_profit_pct` work as in TOML.
**Parameters** reach `stockwise_init` as compact JSON: one flat object of
every parameter, names sorted, numbers as JSON numbers —
`{"overbought":70.0,"oversold":25.0,"period":14.0}`. Keys never contain
`,` `:` or quotes, so a small hand-written reader is enough (the example
does this).
### Runtime
Plugins run in [wasmi](https://github.com/wasmi-labs/wasmi) 2.x, a pure-Rust
WebAssembly interpreter. It was chosen over wasmtime because:
- **Size and build time.** With the feature, the release binary grows by
2.4 MB (12.9 → 15.3 MB on aarch64 macOS): 1.5 MB for wasmi and 0.9 MB for
the WAT text parser. Its crates add about 35 s of CPU time to a release
build, mostly in parallel with the rest. wasmtime with Cranelift is several
times larger, slower to compile, and pulls in a much bigger dependency
tree.
- **MSRV.** wasmi 2.0 builds on Rust 1.86, well inside StockWise's 1.97.
wasmtime requires the newest compilers (wasmtime 50 needs 1.97, and each
monthly release moves that up), which would tie StockWise's MSRV to
wasmtime's release train.
- **Limits.** wasmi meters fuel deterministically per instruction and
enforces memory, table and module-structure limits — what a plugin sandbox
needs. wasmtime's extra features (JIT speed, epoch interruption, WASI) are
unneeded: a plugin does a few hundred instructions per bar, and has no
host access.
- **No unsafe host surface.** No imports are linked, the module is validated
before it runs, and an interpreter cannot jump to plugin-generated machine
code.
The feature is off by default, so default builds and binaries are unchanged.
## JSON output
`--json` prints one object. Percentages end in `_percent`; money is in the
stock's currency.
- **`backtest SYMBOL`**: `symbol, currency, strategy, period, interval, bars,
start, end, fill, capital`, the metrics (`start_value, end_value,
total_return_percent, cagr_percent, max_drawdown_percent, sharpe, sortino,
exposure_percent, trade_count, win_rate_percent, avg_win_percent,
avg_loss_percent, profit_factor, costs_paid`), `gross_return_percent,
buy_hold_return_percent, alpha_percent, skipped_entries`, `costs`
(`brokerage, stt, exchange, sebi, gst, stamp, dp, slippage, total`),
`cost_model`, `rules` (the strategy as loaded, with `-P` applied),
`trades: [trade]` and `equity: [{date, value}]`.
A trade is `{symbol, entry_date, exit_date, qty, entry_price, exit_price,
gross_pnl, costs, net_pnl, pnl_percent, bars_held, exit_reason}` with
`exit_reason` one of `signal, stop-loss, take-profit, rebalance, end-of-data`.
- **`backtest portfolio`**: the same, with `symbols, weights, rebalance,
rebalances` and `per_symbol: [{symbol, weight, pnl, contribution_percent,
trades, win_rate_percent, costs, exposure_percent}]`.
- **`backtest walkforward`**: `symbol, currency, strategy, period, interval, fill,
capital, cost_model, rules, train_days, test_days, combos`,
`folds: [{params, in_sample, out_of_sample}]` (each window `{start, end,
bars, return_percent, annualised_percent, sharpe, max_drawdown_percent,
trade_count}`), `out_of_sample` (the metrics), `out_of_sample_gross_return_percent,
out_of_sample_annualised_percent, in_sample_annualised_percent,
in_sample_sharpe, efficiency, verdict, verdict_reason, trades, equity`.
- **`backtest strategies`**: `{builtin: [{aliases, strategy}], directory,
user: [{path, ok, strategy | error}], skipped_plugins: [path]}`.
`skipped_plugins` lists `.wasm` files a build without `wasm-plugins` did
not load.
For a WASM plugin, `rules` (and `strategy` above) has empty `entry` and
`exit`, the plugin's `params` and `grid`, and `plugin: {abi_version,
lookback}`.
```bash
for s in rsi macd sma bb vwap mr; do
stockwise backtest INFY -s "$s" --json | jq -r '"\(.strategy)\t\(.total_return_percent)"'
done
```
## Limits
- Long only; no short selling, leverage or F&O.
- Daily bars only. Intraday costs (`--costs in-intraday`) can be applied, but
positions are still held from one daily bar to another.
- Dividends are not credited; symbols that were delisted are not in Yahoo's
data (survivorship bias).
- WASM plugins are **experimental** and opt-in (`--features wasm-plugins`);
the ABI may change in a later version, which bumps `abi_version`.
---
# Practice mode: replays, challenges and scores
Practice mode turns the paper-trading simulator into a way to learn. You trade
a past market one day at a time, without seeing what comes next, and you get a
score for **how** you traded: return for the risk you took, how deep your
drawdowns went, whether you kept to your own rules, and how widely you spread
your money. Raw return never scores on its own.
Everything here uses paper money. Nothing in practice mode can reach a broker,
and every scorecard and card says so: **paper trading — not financial advice.**
- [A five-minute tutorial](#a-five-minute-tutorial)
- [Replays](#replays)
- [Challenges](#challenges)
- [Scores, grades and badges](#scores-grades-and-badges)
- [Profile and result cards](#profile-and-result-cards)
- [Your own scenarios and challenges](#your-own-scenarios-and-challenges)
- [JSON output](#json-output)
- [Files and limits](#files-and-limits)
For the bot-driven daily simulator (`sim start`, `sim settle`) see
[bots.md](https://iamsaquib8.github.io/stockwise/bots.html#paper-trading-simulator).
## A five-minute tutorial
> **Guide:** [Backtest and practise on past markets](https://iamsaquib8.github.io/stockwise/guide-practice.html) puts this next to backtesting.
```bash
stockwise sim replay list # the built-in scenarios
stockwise sim replay start covid-crash-2020 # 3 Feb 2020, ₹1,00,000 of paper money
stockwise sim challenge start crash-survivor # optional: play for a goal
```
You see the account, the challenge and the market on the replay date: every
stock's close, its change on the day and over 20 trading days. That is all
you know — the same as an investor on that morning.
```bash
stockwise sim replay chart index # NIFTY 50 up to today's close
stockwise sim replay chart TCS --days 120
stockwise sim replay buy TCS 10 --stop 1900 # decide where you are wrong first
stockwise sim replay buy HINDUNILVR 8 --stop 2000 --target 2600
stockwise sim replay next # orders fill at tomorrow's open
stockwise sim replay status
stockwise sim replay next 5 # five trading days at once
stockwise sim replay set TCS --stop 2000 # move a stop
stockwise sim replay sell TCS # the whole position, at the next open
stockwise sim replay end # scorecard, badges, what happened
stockwise sim card --svg card.svg # share the result
```

## Replays
`sim replay start` takes a built-in scenario, a scenario file, or your own
window:
| Scenario | Market | Window | Benchmark |
|----------|--------|--------|-----------|
| `covid-crash-2020` | India, 20 NIFTY 50 stocks | Feb – Jun 2020 | NIFTY 50 |
| `gfc-2008` | India, 19 large companies | Aug 2008 – Mar 2009 | NIFTY 50 |
| `adani-2023` | India, 6 Adani companies + 10 heavyweights | Jan – Apr 2023 | NIFTY 50 |
| `dotcom-2000` | US, 16 stocks | 2000 | Nasdaq Composite |
| `bull-run-2021` | India, 18 NIFTY 50 stocks | Jan – Oct 2021 | NIFTY 50 |
| `rate-hikes-2022` | US, 16 large companies | Jan – Oct 2022 | S&P 500 |
```bash
stockwise sim replay start --from 2022-01-03 --to 2022-06-30 --symbols TCS,INFY,HDFCBANK,ITC
stockwise -m us sim replay start --from 2008-09-02 --to 2009-03-31 --symbols JPM,BAC,XOM,KO --benchmark ^GSPC
stockwise sim replay start covid-crash-2020 --capital 500000 --costs none
```
A custom window uses the global market (`-m in`, the default, or `-m us`) and
benchmarks against NIFTY 50 or the S&P 500 unless you pass `--benchmark`. Its
stocks have no sector, so the diversification part of the score only counts
the number of stocks; write a [scenario file](#your-own-scenarios-and-challenges)
to name sectors.
**The future stays hidden.** At the start, StockWise downloads each stock's
daily history once (about a year before the window, for charts, through the
window's end) and saves it next to the replay. From then on the replay runs
offline. The price data lives in one place, and every view — tables, charts,
scorecards and `--json` — reads it only up to the replay date. A test plants
impossible prices after the replay date and checks that none of them, and none
of their dates, appear in any output.
**How orders fill.** The replay day is over when you see it: you know its
close. Orders you place fill at the **next trading day's open**:
- Sells fill before buys on the same morning, so you can rotate from one stock
to another in one step. A buy that the cash no longer covers (a gap up) is
cut to what you can afford.
- Every fill pays slippage and the same charges as the backtester
([costs](https://iamsaquib8.github.io/stockwise/backtesting.html#costs)): India delivery (brokerage, STT, exchange
and SEBI fees, GST, stamp duty, DP charge) for NSE stocks, the US model for
US stocks. `--costs none|in-delivery|in-intraday|us` overrides the choice.
- After the fills, stop-losses and targets are checked against the day's low
and high. A stop fills at its level, or at the open if the stock gapped
through it; when a day touched both, the stop is assumed first.
- Long only: no short selling and no leverage. A stock that did not trade on a
day (a halt, or a gap in the data) keeps its order waiting.
`sim replay next N` moves up to `N` trading days and prints what happened each
day. On the last day of the window, run `sim replay end`; you can also end
early — a run of fewer than five trading days is not scored. `end` shows the
scorecard and then the scenario's debrief: what actually happened.
## Challenges
A challenge is a set of rules and goals, played over a replay or over your
live paper sessions (the bot's `sim start` picks, settled by `sim settle`).
```bash
stockwise sim challenge list
stockwise sim challenge start steady-hands # inside the running replay
stockwise sim challenge status
stockwise sim challenge end # give up, or score a finished live one
```
| Challenge | Length | Rules (checked as you trade) | Goals (checked at the end) |
|-----------|--------|------------------------------|----------------------------|
| `beat-the-index` | 30 days | — | return above the benchmark; ≥ 30% invested on average |
| `steady-hands` | 30 days | drawdown ≤ 10% (hard) | ≥ 50% invested on average |
| `position-limit` | 30 days | no position above 20% of capital (hard) | hold 5 stocks at once |
| `stop-loss` | 30 days | every buy has a stop-loss (hard) | 5 or more buys |
| `sip-discipline` | whole replay | ≥ 90% invested at every month end | end above a plain monthly SIP into the benchmark |
| `crash-survivor` | whole replay | — | worst drawdown under half the benchmark's; ≥ 30% invested |
**Hard rules** cannot be broken by accident: an order that would break one is
refused with the reason, and a hard rule broken by the market (a drawdown)
ends the challenge as failed. **Soft rules** are recorded and cost discipline
points. Position size is judged when you place the order, at the last close,
and the score judges it the same way. A challenge must fit in the days left in
the replay, so start a 30-day challenge at least 30 trading days before the end.
`sip-discipline` starts on the replay's first day, before any order: your
account begins with one month's contribution (10,000 in the replay's currency)
and the same again arrives on the first trading day of every month. It is
compared with a plain SIP that buys the benchmark with the same money on the
same days — can your timing beat discipline? Returns are time-weighted, so
the money you add never counts as profit.
Over **live paper sessions** a challenge counts settled sessions from the day
you start it: the first session settled on each trading day, up to the
challenge's length. The rules are checked over the trades the bot made. Start
it outside a replay, then use `sim start` each morning and `sim settle` after
the close. Your return compounds each session's intraday P&L, while the
benchmark moves close to close over the same dates, overnight gaps included —
a tougher comparison than in a replay.
## Scores, grades and badges
A finished run gets a score from 0 to 100, from four parts:
| Part | Points | What earns them |
|------|-------:|-----------------|
| Risk-adjusted return | 40 | your Sharpe and Sortino ratios against the benchmark's: half the points for matching it, all of them two points above it |
| Drawdown control | 20 | a worst drawdown small next to the benchmark's, scaled to how invested you were on average (with a 10% floor in calm markets), so cash alone earns nothing here |
| Discipline | 25 | buys with a stop-loss (60%) and positions of at most 20% (40%); −10% per soft rule broken; none after a hard break or with no buys |
| Diversification | 15 | sectors held at the same time (5 for full marks, 60%) and stocks used (8, 40%) |
The first two parts scale with how invested you were, reaching full weight at
30% invested on average. Sitting in cash through a crash avoids the loss, but
it is not a trading decision worth points. Grades: **A+** 90+, **A** 80+,
**B** 70+, **C** 60+, **D** 50+, **F** below. The score is a pure function of
the run, so the same trades always score the same.
Badges are earned once and kept in your profile:
| Badge | How |
|-------|-----|
| Kept your stops | three or more buys, every one with a stop-loss |
| Diversified across 5 sectors | hold stocks from five sectors at the same time |
| Survived the crash | the benchmark falls 20%+ and you lose at most half as much, 30%+ invested, 20+ days |
| Beat the index | a higher return than the benchmark with a smaller drawdown, 30%+ invested |
| Steady hands | 20+ days at least half invested with a drawdown under 10% |
| Risk-adjusted winner | a Sharpe ratio of 1+ over 20+ days, above the benchmark's |
| Challenge complete | pass any challenge |
| Never missed a SIP | pass the SIP challenge without missing a month |
| Went the distance | play a replay to its last day |
| Three in a row | three disciplined runs in a row |
**Streaks.** A *disciplined run* is a scored run where every buy had a
stop-loss and was at most 20% of the account, and no rule was broken. The
profile counts disciplined runs in a row and challenges passed in a row.
## Profile and result cards
```bash
stockwise sim profile # badges (earned and how to earn the rest), streaks, best scores, recent runs
stockwise sim card # your last run as a text card, ready to paste
stockwise sim card --svg card.svg # ...or as a 640 × 360 image
stockwise sim card --svg card.svg --text
```
The card shows the scenario, dates, score and grade, your return, drawdown and
Sharpe ratio next to the benchmark's, your curve against the benchmark, badges,
challenges and the disclaimer. It is built from your local profile: nothing is
uploaded, and there is no leaderboard.
## Your own scenarios and challenges
A scenario is a TOML file in the same format as the built-ins in
[`src/practice/scenarios/`](https://github.com/iamsaquib8/stockwise/tree/main/src/practice/scenarios):
```toml
name = "my-2018-midcaps"
title = "The 2018 mid-cap fall"
market = "in" # in or us
from = "2018-01-01"
to = "2018-12-31"
benchmark = "^NSEI"
benchmark_name = "NIFTY 50"
capital = 100000
description = "Shown before and during the replay: don't give the ending away."
debrief = "Shown after `sim replay end`."
[[universe]]
symbol = "RELIANCE.NS"
sector = "Energy"
```
```bash
stockwise sim replay start file:my-2018-midcaps.toml
```
Challenges use the `[[challenge]]` format of
[`src/practice/challenges.toml`](https://github.com/iamsaquib8/stockwise/blob/main/src/practice/challenges.toml), with these
rule kinds: `max_drawdown {percent, hard}`, `max_position {percent, hard}`,
`stop_loss_required {hard}`, `invest_each_month {percent, hard}` (rules), and
`beat_benchmark {margin_percent}`, `beat_sip` (needs `monthly_contribution`),
`min_exposure {percent}`, `min_positions {count}`, `min_trades {count}`,
`drawdown_vs_benchmark {ratio}` (goals). `days = 0` runs until the replay ends.
```bash
stockwise sim challenge start file:my-challenges.toml # the file's first challenge
```
## JSON output
`--json` works on `sim replay list|start|next|buy|sell|status|end`,
`sim challenge list|start|status|end` and `sim profile`. Field names are listed
in [commands.md](https://iamsaquib8.github.io/stockwise/commands.html#scripting-with---json-and---csv). Every replay
view carries the same no-future guarantee as the tables.
## Files and limits
| File (data directory) | What |
|------|------|
| `sim_replay.json` | the running replay: account, orders, fills, daily marks, challenge |
| `sim_replay_tape.json` | its cached daily prices (deleted by `sim replay end`) |
| `sim_challenge.json` | a challenge running over live paper sessions |
| `sim_profile.json` | badges, streaks, best scores and your last 50 runs |
- One replay at a time, and one challenge at a time inside it (start another
when it finishes). Replays use daily bars only: no intraday fills.
- Prices are Yahoo Finance daily bars, adjusted for splits but not dividends,
so a replay ignores dividends. A stock missing from Yahoo for the window is
left out with a note; it is not valued at zero.
- Live paper trades carry no sector, so live challenges can't earn the sector
badge.
- Practice mode is not shown in `tui` or the web dashboard yet.
---
# Trading Bots
## Intraday Bot
9-strategy multi-signal engine for day trading.
```bash
stockwise bot 25000 2 # ₹25K capital, 2% daily target
stockwise -m us bot 5000 1 # US market, $5K, 1% target
```
### Strategies
| # | Strategy | Signal | Trigger |
|---|----------|--------|---------|
| 1 | RSI Reversal | Long/Short | RSI <30 (oversold) or >70 (overbought) |
| 2 | VWAP Reclaim | Long | Price crosses above VWAP from below |
| 3 | BB Squeeze | Long | Tight Bollinger bands + price above middle |
| 4 | BB Bounce | Long | Price touching lower Bollinger Band |
| 5 | MACD Cross | Long/Short | Histogram sign change (fresh crossover) |
| 6 | Volume Breakout | Long | 2x+ avg volume with positive price action |
| 7 | Support Bounce | Long | Price at pivot S1 level |
| 8 | OBV Divergence | Long | Volume accumulating while price flat/down |
| 9 | Mean Reversion | Long | Price >3% below 20-SMA |
### Scoring
- Each strategy generates a strength score (0-100)
- Consensus bonus: +8 per additional agreeing strategy
- Volume multiplier: 1.15x if volume >1.5x average
- Regime multiplier: 1.2x if trade aligns with strong trend, 0.6x if counter-trend
- Confidence: HIGH (score >=75, 3+ strategies), MEDIUM (>=60, 2+), LOW (others)
### Position Sizing (Kelly Criterion)
- Half-Kelly fraction for safety (max 25% of capital)
- 1% max risk per trade
- Never more than 30% of capital in one position
- At most 5 plans, and **together they never need more than your capital**:
walking down the ranking, a plan that doesn't fit in what's left is shrunk
to what does (or dropped)
- Exit level for each paper position: uses support levels when available, else ATR-based
### Output
- Sector heat map (which sectors are moving)
- Market regime detection (Strong Uptrend → Strong Downtrend)
- Top 15 signals ranked by score
- The strategies behind the five strongest signals, with each one's strength and reason (`intraday` shows no position sizes or price levels; the paper-trading `daemon` and `sim` still size simulated trades and show them as long or short with level 1, level 2 and an exit level)
---
## Long-Term Scores
6-pillar scoring of the market's stock universe with a Monte Carlo illustration. It shows data and how each score was built: no buy, sell or hold calls, no entries, stops or targets, and no allocation advice.
```bash
stockwise longterm 15000 # ₹15K/month SIP budget
stockwise -m us longterm 1000 # US market, $1K/month
```
### 6 Scoring Pillars
| Pillar | Weight | Key Metrics |
|--------|--------|-------------|
| Valuation | 18% | P/E, PEG ratio, P/B, forward P/E compression, EV/EBITDA, earnings yield |
| Growth | 22% | Revenue growth, quarterly earnings growth, EPS trajectory |
| Quality | 25% | Profit margins, ROE, debt/equity, current ratio |
| Momentum | 10% | Price against the 50- and 200-day averages, analyst mean rating, analyst mean price estimate against the price |
| Dividend | 10% | Yield level, DRIP compounding |
| Safety | 15% | Beta, market cap, historical volatility, debt levels |
### Moat Analysis
Stocks get Wide/Narrow/No Moat signals from these rules:
- Profit margins >25% (pricing power)
- ROE >25% (capital efficiency)
- Revenue growth >25% (market dominance)
- Market cap >$1T (scale advantage)
### Monte Carlo Simulation
- 1000 simulated 5-year paths using historical volatility
- Reports: P10 (worst case), Median, P90 (best case) returns
- Uses deterministic PRNG for reproducibility
### Output
- Top 12 names ranked by composite score, with each pillar's score (a dash where the pillar had no data; `n/a` when under half the weight had data)
- Detailed top 3: score band, data coverage, risk level (Lower / Moderate / Higher), PEG, earnings yield, and the facts that added or took away points
- 5Y and 10Y growth at an *assumed* annual return set from the score (not a forecast), with dividends reinvested
- SIP arithmetic (the monthly amount that would reach ₹10L in 10 years at that rate, and what your budget compounds to)
- A simulated 5-year range: 10th percentile, median and 90th percentile
---
## Paper Trading Simulator
Test the intraday bot without risking real money.
```bash
stockwise sim start 25000 2 # Morning: lock in trades
stockwise sim status # During day: live P&L
stockwise sim settle # After the close: replay the day, record results
stockwise sim history # Performance over time
```
`sim start` only runs on a trading day before the close (the clock is IST, or
ET with `-m us`, and knows exchange holidays), and records when each trade
was entered.
### How `sim settle` decides
Each open session is settled from **its own day's 5-minute bars**, not from
whatever the price is when you run the command:
1. Bars before the entry time are ignored.
2. The bars are walked with the same position manager as the daemon: book
half at level 1 and move the exit level to the opening price, close the
rest at level 2, trail the exit level after level 1, tighten it in the
wind-down. (Levels are fixed when the paper position opens; they are
simulation mechanics, not advice.)
3. Whichever of the exit level or level 2 the price reached first decides the
trade. Inside a single bar the order of the high and low is unknown, so a
bar that touched both counts as **the exit level** (conservative). Levels
fill at their price; a bar that opens beyond one fills at the open.
4. Otherwise the trade exits at the open of the 15:15 IST bar (15:45 ET).
Yahoo keeps 5-minute bars for about 60 days. Older sessions fall back to
hourly bars, then to the daily bar (same rules, exit at the close). Today's
session is refused until the market closes; `sim settle --now` settles it at
the latest bar. A session whose bars can't be fetched stays open and the
command exits non-zero if nothing was settled.
### What It Tracks
- Per trade: opening and closing price, whether level 2 or the exit level was reached, P&L, strategies used
- Per day: total P&L, win/loss count, how often level 2 was reached
- Cumulative: equity curve, Sharpe ratio, win streaks, day/trade win rates
### Confidence Rating (after 5+ days)
| Rating | Criteria |
|--------|----------|
| HIGH | >65% day win rate + >55% trade win rate + profitable |
| MODERATE | >50% day win rate + profitable |
| LOW | Below moderate threshold |
### Practice mode
To practise your own decisions rather than the bot's, replay a past market
day by day with the future hidden — the COVID crash, 2008, the dot-com bust —
take on goal-based challenges, and get a risk-adjusted score, badges and a
shareable card: `stockwise sim replay start covid-crash-2020`. See
[simulator.md](https://iamsaquib8.github.io/stockwise/simulator.html).
---
## Daemon Mode
> **Guide:** [Set up alerts and the daemon](https://iamsaquib8.github.io/stockwise/guide-alerts.html#5-run-the-paper-trading-daemon-optional).
Persistent background workers that run the bots continuously.
### Intraday Daemon
```bash
stockwise daemon intraday 25000 # NSE, ₹25K paper capital
stockwise -m us daemon intraday 5000 # NYSE hours (ET), $5K
stockwise daemon intraday 25000 --once # one session, then exit
```
The daemon keeps running: after it settles a session it waits for the next
trading day (skipping weekends and exchange holidays) and runs that one, so
one process covers every day until you stop it with Ctrl+C or SIGTERM.
Start it any time: before or during a session it picks that session up (a
late start enters no new positions); after the close or on a weekend it waits
for the next pre-market. Add `--once` to run a single session and exit with
status 0 (for example from cron or a systemd timer).
Lifecycle (NSE; NYSE uses the same offsets from 09:30–16:00 ET):
```
9:00 AM Pre-market scan (sectors, gaps)
9:15 AM Enter up to 5 positions (9 strategies, Kelly sizing, ≤ capital in total)
9:30–3:15 Monitor every 60 seconds:
- Level 1 reached → book 50%, move the exit level to the opening price
- Level 2 reached → close fully
- Exit level reached → close, record the loss
- Trailing exit level updates (1.5% trail)
- Wind-down (2 PM+): tighten the exit level to 0.5%
3:15 PM Square off all remaining positions
3:20 PM Settle → save to sim_history.json → AI EOD report → wait for the
next session (with --once: exit)
```
The clock is the exchange's, not your machine's: IST (+05:30) for NSE, US
Eastern with daylight saving for NYSE. Weekends and exchange holidays are
closed all day; the daemon prints the reason and the next session, then
sleeps until 15 minutes before it.
Risk controls:
- 1% max risk per trade, 30% max per position, total allocation ≤ capital
- 3% daily loss kill switch (closes everything; counts every realised exit,
including level 2 exits)
- Max 5 concurrent positions
- Entries only in the first 15 minutes; no new entries after that
- Forced square-off 15 minutes before the close
Resilience:
- One Yahoo session is reused across ticks. A failed tick is logged and
retried after 15 s, 30 s, 60 s … (capped at 5 minutes); after three
failures in a row the session is rebuilt. Network blips never stop it.
- The day's state (positions, risk manager, entries) is saved to
`daemon_state.json` (`daemon_state_us.json` with `-m us`) in the data
directory after every tick. Restarting the daemon the same day resumes it —
the capital from the saved state wins.
- **Ctrl+C** (or SIGTERM, e.g. from systemd) squares off open positions at
their last price, records the day in the simulator history, saves, and
exits 0. A second Ctrl+C exits immediately; the saved state is resumed next
time.
- If the daemon missed the square-off (crash, laptop asleep), it hands that
day's entries to the simulator as an open session instead of closing them
at stale prices: run `stockwise sim settle` to settle them from the day's
5-minute bars.
- One NSE and one NYSE daemon can run side by side; don't start two for the
same market — they would share one state file.
The daemon is **paper-only**: it never calls a broker order API. Results are
saved to simulation history — check with `stockwise sim history`. With
[notifications](#notifications) configured, it also sends entries, exits,
the kill switch and the end-of-day summary.
### Market calendar
Holidays come from a table in `src/clock.rs`: the NSE equity-segment list
(2026 official; 2027 provisional — only fixed-date holidays until NSE
publishes its circular in December) and the NYSE calendar for 2026–2028,
including 1:00 pm early closes. When the current year's list is missing or
provisional, the daemon prints a warning. Muhurat trading sessions are not
traded.
### Long-Term Daemon
```bash
stockwise daemon longterm
```
Runs 5 steps:
1. Score 30 stocks on 6 pillars + Monte Carlo and print the three highest scores
2. Check portfolio value, snapshot wealth tracker
3. Check all price alerts
4. List holdings priced below their average cost
5. Generate AI research notes via Ollama
Run daily after market close, or weekly for deep analysis.
### Deep Dive
One command for everything about a single stock:
```bash
stockwise deep RELIANCE
```
Shows: company info, price action, valuation (P/E, PEG, P/B, EV/EBITDA), profitability, financial health, dividends, analyst ratings, 1Y chart, all technicals (RSI + gauge, SMA, MACD, Bollinger, ATR, VWAP), support/resistance, Fibonacci, risk (vol, Sharpe, drawdown, VaR), OBV trend, open gaps, insights, the 6-pillar score, and (with `--ai`) AI analysis.
---
## Price and Technical Alerts
> **Guide:** [Set up alerts and the daemon](https://iamsaquib8.github.io/stockwise/guide-alerts.html) walks through alerts, notifications and cron.
```bash
stockwise alert add TCS above 4200
stockwise alert add AAPL -m us below 180
stockwise alert add INFY rsi-below 30 # RSI(14) at or below 30
stockwise alert add RELIANCE cross-above-sma 200
stockwise alert add AAPL -m us breakout-high 52
stockwise alert add HDFCBANK change-below 3 # a fall of 3% or more today
stockwise alert add TCS volume-spike 2 # 2x the 20-day average volume
stockwise alert add TCS results-in 7 # results due within a week
stockwise alert watch # check every 60 s while markets are open
stockwise alert watch --interval 30 # every 30 s (minimum 10)
stockwise alert watch --once # one pass over every alert, for cron
```
`alert watch` checks each alert only while its market is open — `.NS`/`.BO`
symbols on the NSE clock, US tickers on the NYSE clock, crypto and FX
(`BTC-USD`, `USDINR=X`) around the clock — and sleeps until the next open
otherwise. Alerts are re-read every pass, so `alert add` in another terminal
takes effect without a restart.
Technical conditions (RSI, moving-average crosses, breakouts, day change,
volume spikes) are evaluated on daily bars, fetched once per symbol per check
and brought up to the live price. A cross fires on the session it happens in,
not on the days after. `results-in` reads the next results date from
`stockwise calendar` (Yahoo, plus NSE board meetings for Indian stocks,
cached for a day). Every condition is listed in
[commands.md](https://iamsaquib8.github.io/stockwise/commands.html#alert-conditions).
Each trigger is announced **once**: an alert fires when its condition becomes
true, re-arms after the condition turns false again, and fires at most once a
trading day. Day-change and volume-spike alerts describe one session, so they
re-arm with each new session, using the quote's own session date. Until today's session has traded they give no reading, so a weekend or holiday does not repeat Friday's move; `results-in` re-arms once the results date has
passed. That
memory lives in `alert_watch.json`, so a restart doesn't repeat
notifications. If every configured channel fails, the trigger is not marked
and the next check tries again. Network errors are retried with backoff;
Ctrl+C stops it.
---
## Notifications
`alert watch` and `daemon intraday` send to every channel you configure.
All channels are opt-in, set with environment variables:
| Channel | Variables |
|---------|-----------|
| Telegram | `STOCKWISE_TELEGRAM_TOKEN` (from @BotFather), `STOCKWISE_TELEGRAM_CHAT_ID` |
| Slack | `STOCKWISE_SLACK_WEBHOOK` — an incoming-webhook URL |
| Discord | `STOCKWISE_DISCORD_WEBHOOK` — a channel webhook URL |
| Email | `STOCKWISE_SMTP_HOST`, `STOCKWISE_SMTP_USER`, `STOCKWISE_SMTP_PASSWORD`, `STOCKWISE_EMAIL_TO` (comma-separated); optional `STOCKWISE_EMAIL_FROM`, `STOCKWISE_SMTP_PORT`, `STOCKWISE_SMTP_TLS` (`starttls` — default, port 587 — `tls` for 465, or `none` for a server on this machine) |
| Desktop | `STOCKWISE_DESKTOP_NOTIFY=1`, in a build with the `desktop-notify` feature: `cargo install stockwise --features desktop-notify` |
The same settings can live in the [config file](https://iamsaquib8.github.io/stockwise/commands.html#configuration-file)
under `[notify]`, named without the `STOCKWISE_` prefix in lower case
(`telegram_chat_id = "123"`, `smtp_host = "smtp.gmail.com"`). An environment
variable wins over the file. If you put a token or password there, keep the
file private (`chmod 600`); `config show` and `doctor` warn otherwise.
```bash
stockwise notify # which channels are configured (secrets are never shown)
stockwise notify test # send a test message to each; exits 1 if any fails
```
Security: the bot token, webhook URLs and SMTP password are only sent to their
own endpoint. Redirects are not followed, error messages are stripped of
URLs, and `notify` shows only the chat id, the webhook's host, or the SMTP
server. Email uses TLS (rustls); `STOCKWISE_SMTP_TLS=none` with a password is
refused unless the server is `localhost`. A failed notification is printed
as a warning and never stops the daemon or the alert loop.
Telegram setup: message @BotFather → `/newbot` → copy the token; send your
bot a message, then open `https://api.telegram.org/bot/getUpdates` in
a browser and copy `chat.id`.
---
# Bot Design & Architecture
## Current State vs Vision
### What exists today
- **On-demand scanners** (`intraday`, `longterm`): scan once, show the scores, exit.
- **`daemon intraday`**: a paper-only worker on the exchange clock (IST /
ET, holidays included) that enters at the open, manages positions every
60 seconds, squares off before the close and settles into the simulator
history. It survives network errors, saves its state every tick
(`daemon_state.json`), resumes after a restart, and squares off on Ctrl+C.
- **`daemon longterm`**: a one-shot post-market scan.
- **`alert watch`**: checks price alerts while their market is open.
- **Notifications** to Telegram, Slack, Discord, email and the desktop for
alert triggers and daemon entries, exits, kill switch and end of day.
Not built: live order execution from the daemon (Phase 2), the learning loop
(Phase 4), and scheduling (run the workers from cron/systemd/launchd).
### Design
Two persistent background workers that run throughout the day:
```
┌─────────────────────────────────────────────────────┐
│ INTRADAY WORKER (9:00 AM – 3:30 PM IST) │
│ │
│ 9:00 Pre-market: scan overnight gaps, sector heat │
│ 9:15 Market open: lock initial positions │
│ 9:15–10:00 Aggressive phase: momentum entries │
│ 10:00–2:00 Monitoring: trail exit levels, book level 1 │
│ 2:00–3:00 Wind-down: no new entries, tighten stops │
│ 3:15 Square off: close all intraday positions │
│ 3:30 EOD: settle, record P&L, update sim history │
│ │
│ Runs every 60 seconds during market hours │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ LONGTERM WORKER (runs daily after market close) │
│ │
│ 4:00 PM Scan all stocks, score on 6 pillars │
│ 4:01 Run Monte Carlo on top candidates │
│ 4:02 Portfolio value, wealth snapshot │
│ 4:03 Check alerts: any triggered? │
│ 4:04 Holdings priced below their average cost │
│ 4:05 AI research notes (optional) │
│ │
│ Runs once daily, optionally weekly deep scan │
└─────────────────────────────────────────────────────┘
```
---
## Intraday Worker: Detailed Design
### Lifecycle
```
PRE-MARKET (9:00 – 9:14)
├── Fetch sector heat (NIFTY IT, Bank, Pharma, etc.)
├── Scan for overnight gaps in all 30 stocks
├── Check global cues (US futures, SGX NIFTY)
├── Identify watchlist candidates (top 10 by score)
└── Print morning brief
MARKET OPEN (9:15 – 9:30)
├── Run 9-strategy engine on all candidates
├── Filter by confidence (HIGH/MEDIUM only)
├── Size positions via Kelly criterion
├── Lock paper positions (opening price, level 1, level 2, exit level, trailing exit level)
├── Record them in the simulator (the daemon never places orders)
└── Print the positions
MONITORING LOOP (9:30 – 3:00, every 60s)
├── Fetch live prices for all open positions
├── For each position:
│ ├── If price >= level 1: book 50%, move exit level to the opening price
│ ├── If price >= level 2: book remaining, position closed
│ ├── If price <= exit level: close, record loss
│ ├── Update trailing exit level (max of previous, price - 1.5*ATR)
│ └── Log P&L snapshot
├── Check for new signals (only in first 2 hours)
├── Print status table every 5 minutes
└── Notify when level 1, level 2 or the exit level is reached
WIND-DOWN (3:00 – 3:15)
├── No new entries
├── Tighten all trailing stops to 1x ATR
├── Book any position with >0.5% profit
└── Prepare for square-off
SQUARE OFF (3:15 – 3:20)
├── Close all remaining positions at market
├── Calculate final P&L
├── Settle simulation session
└── Generate EOD report
POST-MARKET (3:30+)
├── Save results to sim_history.json
├── Update wealth tracker
├── Generate AI EOD report
└── Print daily scorecard
```
### State Machine
```
┌──────────┐
│ IDLE │ (before 9:00 or after 3:30)
└────┬─────┘
│ 9:00 AM
┌────▼─────┐
│ PRE_SCAN │ gaps, sectors, candidates
└────┬─────┘
│ 9:15 AM
┌────▼─────┐
│ ENTERING │ place orders, lock trades
└────┬─────┘
│ orders filled
┌────▼─────┐
┌──►│ WATCHING │◄──── main loop (60s ticks)
│ └────┬─────┘
│ │ level 1 / exit level
│ ┌────▼─────┐
└───┤ MANAGING │ partial exits, trail stops
└────┬─────┘
│ 3:15 PM
┌────▼─────┐
│ CLOSING │ square off all
└────┬─────┘
│ done
┌────▼─────┐
│ SETTLED │ record P&L, generate report
└──────────┘
```
### Data Flow
```
Yahoo Finance (prices) ──┐
├──► Intraday Engine ──► Trade Manager ──► Angel One API
Ollama (AI insights) ────┘ │ │
│ ▼
Score/Signal Order Placed
│ │
▼ ▼
sim_history.json Real P&L
```
### Risk Controls
| Rule | Implementation |
|------|---------------|
| Max 1% risk per trade | Kelly sizing capped, exit level enforced |
| Max 30% capital in one stock | Position size limit |
| Max 5 concurrent positions | Hard limit in trade manager |
| Total allocation ≤ capital | Plans are shrunk or dropped, best first, until they fit |
| No new entries after the opening window | Entries only 9:15–9:30; nothing after |
| Square off by 3:15 PM | Forced exit at market price |
| No trading on circuit-breaker days | *Planned:* check if NIFTY moved >3% pre-market |
| No trading on holidays | Exchange holiday table (`src/clock.rs`) |
| Daily loss limit (3% of capital) | Kill switch — close all if breached |
---
## Long-Term Worker: Detailed Design
### Daily Run (after market close)
```
SCAN (4:00 PM)
├── Fetch quotes for all 30 popular stocks
├── Fetch 1Y daily charts for each
├── Score on 6 pillars (val, growth, quality, momentum, dividend, safety)
├── Run Monte Carlo (1000 sims × 5 years)
├── Detect moat rating
└── Rank by total score
PORTFOLIO CHECK (4:02 PM)
├── Fetch current holdings from portfolio.json
├── Value them in the market's currency
└── Snapshot the value in wealth_history.json (skipped when a price is missing)
ALERT CHECK (4:03 PM)
└── Check all price alerts
HOLDINGS BELOW COST (4:04 PM)
└── List holdings whose price is under their average cost
REPORT (4:05 PM)
└── Optional AI research notes via Ollama (score, data coverage, moat signals,
risk level), printed to the terminal
```
Not built: weekly deep scan, saved reports, allocation or rebalance output.
`longterm` and `daemon longterm` show scores and the facts behind them. They do
not allocate a budget, rank names as picks, place orders or say when to act.
### Score bands
The composite and each pillar use the same descriptive bands as `verdict`. A
band describes the score; it is not an instruction.
```
Score 65 and above Strong
Score 45 to 64 Moderate
Score below 45 Weak
No inputs No data (the composite shows n/a under 50% data coverage)
```
### SIP arithmetic
`longterm` compounds your monthly budget at an *assumed* annual return that is
set from the composite score (a rule of thumb, not a forecast) and shows the
monthly amount that would reach 10 lakh / 1 million in 10 years at that rate.
Every instalment grows only for the time it stays invested. It places no
orders and does not size a position.
---
## Implementation Plan
### Phase 1: Daemon Mode (what to build)
```bash
stockwise daemon intraday # Run intraday worker
stockwise daemon longterm # Run longterm daily worker
stockwise daemon --all # Run both
```
Implementation (done):
- Tokio loop; the phase comes from `clock::market_phase(market, now)` — IST
for NSE, US Eastern (with daylight saving) for NYSE, holidays closed
- Intraday: 60-second tick during market hours; one Yahoo session reused,
failed ticks retried with bounded backoff (15 s → 5 min), never fatal
- State persisted to `daemon_state.json` (`daemon_state_us.json` for NYSE)
after every tick and resumed on a same-day restart; a day whose square-off
the daemon missed is handed to `sim settle`
- Ctrl+C / SIGTERM: square off at last prices, settle into the simulator
history, save, exit 0
- Longterm: single run, cron-scheduled or manual
- Not built: `daemon --all`
### Phase 2: Trade Execution (planned — not implemented)
> Today the daemon is **paper-only**: it never sends orders to a broker. Live
> orders are placed only by `stockwise trade buy/sell/limit`, one at a time,
> after a preview and confirmation.
```
Signal Generated ──► Confirmation Check ──► Angel One API
│
Risk check:
- Within daily loss limit?
- Position count < 5?
- Not in wind-down period?
- Capital available?
```
- Auto-execute: `stockwise daemon intraday --auto-trade`
- Paper mode (default): `stockwise daemon intraday --paper`
- Requires Angel One configured
### Phase 3: Notifications (done)
`src/notify.rs` sends to Telegram (bot API), Slack and Discord (incoming
webhooks), email (SMTP via lettre + rustls) and the desktop (notify-rust,
behind the `desktop-notify` feature). Channels are configured with
environment variables; `stockwise notify test` checks them. The daemon sends
openings, closings (level 1, level 2, exit level, square-off), the kill switch and the
end-of-day summary; `alert watch` sends each trigger once. See
[bots.md](https://iamsaquib8.github.io/stockwise/bots.html#notifications).
### Phase 4: Learning Loop
```
After 30+ simulation days:
├── Analyze which strategies win most
├── Adjust strategy weights automatically
├── Identify which market regimes favor which strategies
├── Backtest adjusted weights on historical data
└── If improvement > 5%: adopt new weights
```
---
## File Structure
```
src/
clock.rs Exchange clock: IST / US Eastern, holiday tables, phases
daemon.rs Positions, risk manager, daemon state + events, shutdown
commands/worker.rs The intraday and long-term loops
commands/alerting.rs `alert watch` and `notify`
notify.rs Telegram, Slack, Discord, email, desktop
simulator.rs Sessions, stats, and the bar replay behind `sim settle`
```
Planned: `scheduler.rs` (cron-like scheduling for the long-term worker).
---
## Market Hours Reference
| Market | Open | Close | Pre-market |
|--------|------|-------|------------|
| NSE/BSE | 9:15 AM IST | 3:30 PM IST | 9:00 AM |
| NYSE/NASDAQ | 9:30 AM ET | 4:00 PM ET (1:00 PM on early-close days) | 4:00 AM |
Holidays: NSE 2026 (official), 2027 (provisional), NYSE 2026–2028. To add a
year, see the module docs at the top of `src/clock.rs`.
---
## FAQ
**Q: Should the bot trade automatically?**
A: Not yet. Start with paper trading (`sim` commands). Once you have 20+ days of HIGH confidence rating from `sim history`, then consider a future auto-trade mode (not built yet — today the daemon only paper-trades).
**Q: How much capital should I start with?**
A: Paper trade with the same amount you'd use for real. ₹25,000–₹50,000 is a good starting point for intraday. For long-term SIP, whatever your monthly budget is.
**Q: What if the bot loses money?**
A: The 1% per-trade risk limit means max loss per trade is ₹250 on ₹25K capital. Daily loss limit of 3% = ₹750 max. The kill switch stops all trading if this is breached.
**Q: Can I run both workers simultaneously?**
A: Yes, they're independent. Intraday worker runs during market hours, longterm worker runs after close. No conflict.
---
# Brokers: live trading and holdings import
StockWise can read your holdings and place orders through a broker's API, and
import holdings from broker exports and CAS statements.
| Broker | `--broker` | Status | Holdings / positions / orders | MARKET, LIMIT | SL, SL-M | GTT |
|--------|-----------|--------|-------------------------------|---------------|----------|-----|
| Angel One SmartAPI | `angel` (default) | original integration | yes | yes | experimental | experimental |
| Zerodha Kite Connect | `zerodha` / `kite` | **experimental** | yes | yes | yes | single-leg |
| Upstox API v2/v3 | `upstox` | **experimental** | yes | yes | yes | not supported |
| Dhan API v2 | `dhan` | **experimental** | yes | yes | yes | single Forever Order |
| Fyers API v3 | `fyers` | **experimental** | yes | yes | yes | single-leg |
> **Experimental — untested against a live account.** The Zerodha, Upstox,
> Dhan and Fyers integrations, and stop-loss / GTT orders on Angel One, were
> built from each broker's public API documentation and tested against recorded
> and handwritten responses on a local mock server. Nobody has run them against
> a real account yet. Until you opt in, they only read (holdings, positions,
> orders) and preview (`--dry-run`); see [Turning on live orders](#turning-on-live-orders).
> Reports from real accounts are very welcome: run `stockwise trade selftest` and
> paste its report — see [Help verify a broker](#help-verify-a-broker).
Pick the broker per command with `--broker`, or set a default with
`STOCKWISE_BROKER=zerodha`. Without either, `trade` uses Angel One, as before.
```bash
stockwise trade holdings --broker zerodha
STOCKWISE_BROKER=dhan stockwise trade orders
```
## Order safety
Every order — any broker, any order type, GTT included — goes through the same
steps:
1. **Exact instrument.** The symbol must match one NSE cash-equity (`EQ`
series) instrument in the broker's own instrument list: Angel One's
`searchScrip`, Kite's instrument dump, Upstox's NSE instrument file, Dhan's
NSE_EQ scrip master, Fyers' NSE_CM symbol master. Partial names, other
series (`-BE`), ETFs with a similar name, and F&O contracts never match; two
matches are refused.
2. **Price checks.** Prices must be positive with at most 2 decimals (never
silently rounded). For stop-loss orders the trigger must sit on the right
side of the market (above it for a BUY, below it for a SELL — otherwise it
would fire at once), and the limit must be at or beyond the trigger in the
order's direction. A limit more than 20% away from its trigger is treated as
a typo. A GTT trigger must be at least 0.25% away from the last price, and a
stop GTT (SELL below the market, BUY above it) needs a limit that is
marketable at the trigger. Stop and GTT orders need a last price; if it
can't be fetched the order is refused. Orders go to NSE only: a `.BO`
symbol is refused rather than traded on NSE.
3. **Preview.** Instrument and broker token, side, quantity, order type,
product (delivery / CNC), last price, estimated value.
4. **`max_order_value`.** If set in the broker's config file, any order whose
estimated value is higher is refused (also on `--dry-run`). With a cap set,
an order whose value can't be estimated (a MARKET order without a last
price) is refused too. Without a cap the preview says so, and `--yes`
(step 7) is refused.
5. **`--dry-run`** stops here: nothing is sent.
6. **Opt-in for experimental paths** (`"enable_orders": true`, below).
7. **Typed confirmation.** You type `yes`. Without a terminal (scripts, cron)
the order is refused unless you pass `--yes`. `--yes` also needs
`max_order_value` in the broker's config file, because it removes the last
guard against a wrong quantity; without a cap, the order is refused before
StockWise logs in to the broker. `--dry-run` is not affected.
If the network fails while an order is in flight, StockWise says the order
state is **unknown** — check the broker app or `stockwise trade orders` before
retrying, so you don't place it twice. Cancelling a GTT goes through the same
preview, opt-in and confirmation.
The paper-trading daemon never sends broker orders.
## Order types
```bash
stockwise trade buy RELIANCE 10 # MARKET
stockwise trade buy RELIANCE 10 --limit 1300 # LIMIT
stockwise trade limit RELIANCE 10 1300 # LIMIT buy (0.3 form)
stockwise trade sell RELIANCE 10 --trigger 1250 # SL-M: market sell once 1250 trades
stockwise trade sell RELIANCE 10 --trigger 1250 --limit 1245 # SL: limit sell at 1245 once 1250 trades
stockwise trade buy RELIANCE 10 --trigger 1400 --limit 1405 # SL buy (breakout entry)
```
All orders are delivery (CNC) on NSE with DAY validity. MARKET and SL-M orders
carry the broker's automatic market protection where the broker requires it
(Kite, Upstox).
### GTT (good till triggered)
A single-leg GTT places a LIMIT order when the trigger trades; it stays active
for up to a year.
```bash
stockwise trade gtt sell TCS 5 --trigger 3400 --limit 3390 --broker zerodha # stop-loss below the market
stockwise trade gtt sell TCS 5 --trigger 4200 --limit 4190 # target above it
stockwise trade gtt buy INFY 10 --trigger 1350 --limit 1355 # buy the dip
stockwise trade gtt list
stockwise trade gtt cancel 112127
```
Two-leg (OCO) GTTs are not supported yet. Upstox GTT rules have no limit price,
so StockWise can't express "LIMIT at this price once triggered" there and
refuses GTTs for Upstox.
## Syncing trades and GTTs into the portfolio
`trade sync` reads the broker's trade book (executed fills) and turns delivery
trades into the lot-based portfolio: a buy becomes a lot with its fill date,
average fill price and order id; a sell comes out of the oldest lots (FIFO) and
is recorded as a realized sale for `tax`. It previews by default and writes only
with `--apply`.
```bash
stockwise trade sync --broker zerodha # preview: what would change
stockwise trade sync --broker zerodha --apply # write it
stockwise trade sync --broker dhan --since 2026-10-01 --apply
stockwise trade sync --apply --notify --json # for cron: JSON on stdout, notify on changes
```
- **Read-only at the broker.** Sync and `gtt list` run on a session that
refuses any order, GTT or cancel request.
- **Idempotent.** Every fill applied is remembered (broker, order and trade id) in the
portfolio file, and lots and sales keep the broker order id. Running sync
twice, or after a partial fill completes, never counts a fill twice: the
later part of a partly filled order becomes its own lot.
- **Delivery equity only.** Intraday (MIS / INTRADAY), margin (MTF), F&O and
commodity fills are listed as skipped, not turned into lots. NSE and BSE
fills of a stock you already hold go to that holding; a new holding takes
the exchange of its fill (`.NS` or `.BO`).
- **Sells need the shares.** A sell larger than the shares the portfolio held
on that date is left out (and not marked as synced) until you add the
missing lots, e.g. with `portfolio add SYMBOL QTY PRICE --date`. Sells of a
stock whose lots still wait for a split or bonus are held back too — run
`portfolio actions SYMBOL --apply` first.
- **Dates.** Fills are dated by the broker's timestamp. Angel One gives a time
only, so its fills are dated with the last NSE session (today once the
market has opened, else the previous trading day).
- **Today only, mostly.** Angel One, Zerodha, Upstox and Fyers return today's
trades only — run sync at the end of each trading day (or from cron). Dhan
keeps a trade history, so `--since` fetches older fills there (up to 50
pages; history rows get their NSE symbol from Dhan's scrip master). For
older history on other brokers, `stockwise import` a tradebook export.
- **Open orders** (pending, partly filled) are listed; their fills sync once
they appear in the trade book.
- **Quantity check.** For each symbol it touched, sync compares the
portfolio's quantity with the broker's holdings list. Today's buys reach that
list only after settlement (T+1), so a same-day gap is normal; a lasting gap
means lots are missing or doubled. Use either `trade sync` (dated lots from
fills) or `trade holdings` (quantities only, estimated dates) as your source
for a broker — `trade holdings` matches quantities, so after a sync it only
changes something when the two disagree.
### GTT status changes
`trade gtt list` remembers what it saw (in `gtt_seen_.json` in the
data directory) and shows what changed since the last look: GTTs that
triggered, were cancelled or expired, or no longer appear (brokers drop old
ones from the list). `--notify` sends those changes to your notification
channels (Telegram, Slack, Discord, email — see `stockwise notify status`);
only real state changes and vanished GTTs are sent, not a status spelling
change such as Angel One's `NEW` → `ACTIVE`. If every channel fails, the
record is not updated, so the changes are reported again next time.
`trade sync --apply` updates the same record and includes the changes;
`trade sync --apply --notify` notifies about them and about synced orders.
```bash
stockwise trade gtt list --broker zerodha --notify
stockwise trade gtt list --json # {broker, gtts, changes, first_check, last_checked}
```
## Turning on live orders
Read-only commands work as soon as a broker is configured. To send real orders
through an experimental path, first compare a `--dry-run` preview with what the
broker app shows, then add to that broker's config file:
```json
"enable_orders": true
```
This applies to every order on Zerodha, Upstox, Dhan and Fyers, and to SL,
SL-M and GTT orders on Angel One. Angel One MARKET and LIMIT orders work as in
0.3 without it.
Most Indian brokers now accept API orders only from a **static IP registered
with your app** (Kite, Upstox, Dhan, Fyers and Angel One since April 2026).
Reads work from anywhere.
## Credentials and the OS keyring
`stockwise trade config --broker ` creates `_config.json` in your
data directory (the path is printed; `stockwise doctor` shows it too) with
owner-only permissions (0600 on Unix). Fill in the placeholders and run the
command again.
On the next `trade` command run in a terminal (or `trade config` again),
secrets in that file — passwords, TOTP secrets, API secrets, access tokens —
**move into the OS keyring** when one is available
(macOS Keychain, Windows Credential Manager, the Secret Service on Linux
desktops), and the file is rewritten without them. It keeps the non-secret
settings plus `"secret_store": "keyring"` and the keyring entry name. Each data
directory (`STOCKWISE_HOME`) gets its own keyring entry.
- `stockwise trade config --keyring` moves them now and says why if it can't.
- Scripts and cron jobs (no terminal) never move secrets themselves; after a
move they read them from the keyring, so on a machine where the keyring is
locked when cron runs, keep `STOCKWISE_KEYRING=off`.
- With no keyring (headless Linux, containers, CI) the secrets stay in the
owner-only file. Set `STOCKWISE_KEYRING=off` to always use the file.
- To change a secret later, put the new value in the file; it moves into the
keyring again on the next command.
- `stockwise doctor` shows, per broker, where its secrets live.
- On macOS a rebuilt or upgraded binary may trigger a Keychain prompt the first
time it reads an entry; choose "Always Allow". Unattended runs right after an
upgrade may wait for that prompt.
Never pass credentials as command-line arguments — they end up in your shell
history. StockWise never prints or logs them, and sends them only to the
broker's own API.
Common optional settings in every broker config: `"max_order_value": 50000`
and `"enable_orders": true`.
## Angel One
1. Create an account at [angelone.in](https://www.angelone.in/) and an app at
[smartapi.angelone.in](https://smartapi.angelone.in/); note its API key.
2. Enable TOTP on your account and note the base32 secret.
3. `stockwise trade config`, then fill in:
```json
{
"api_key": "YOUR_API_KEY",
"client_id": "YOUR_CLIENT_ID",
"password": "YOUR_PASSWORD_OR_PIN",
"totp_secret": "YOUR_TOTP_SECRET",
"max_order_value": 50000
}
```
Angel One logs in with your PIN and a TOTP generated locally (built-in
HMAC-SHA1) on every command, so there is no `trade login` step. Existing
`angel_config.json` files keep working; their secrets move into the keyring on
first use.
## Zerodha Kite (experimental)
1. Create a Kite Connect app at [developers.kite.trade](https://developers.kite.trade/)
(a paid Kite Connect plan); note the **API key** and **API secret**. Set the
redirect URL to anything you can copy from the address bar, e.g.
`http://127.0.0.1/`.
2. `stockwise trade config --broker zerodha` and fill in `api_key` and
`api_secret`.
3. Every trading day: `stockwise trade login --broker zerodha`. It prints the
Kite login URL; log in in your browser, then paste the address the browser
was redirected to (it contains `request_token=…`). StockWise exchanges it for
an access token (`checksum = sha256(api_key + request_token + api_secret)`)
and saves it. Kite sessions end at 06:00 IST.
Holdings include T1 shares. Selling holdings without DDPI/POA may need a CDSL
TPIN authorisation in the Kite app first (Kite answers HTTP 428).
## Upstox (experimental)
1. Create an app at [account.upstox.com/developer/apps](https://account.upstox.com/developer/apps);
note the **API key** and **API secret** and set a redirect URL such as
`http://127.0.0.1/`.
2. `stockwise trade config --broker upstox`; fill in `api_key`, `api_secret`
and the same `redirect_uri`.
3. Every trading day: `stockwise trade login --broker upstox` — log in in the
browser, paste the redirect address (it contains `code=…`). Sessions end at
03:30 IST. Orders use the v3 endpoint on `api-hft.upstox.com`.
## Dhan (experimental)
1. On [web.dhan.co](https://web.dhan.co) open My Profile → Access DhanHQ APIs
and generate an **access token** (valid 24 hours).
2. `stockwise trade config --broker dhan`; put your Dhan **client id** in
`api_key`.
3. `stockwise trade login --broker dhan` and paste the token (input is hidden).
It is checked against your Dhan profile and saved (in the keyring when
available). Repeat when it expires.
GTT orders on Dhan are single "Forever Orders".
## Fyers (experimental)
1. Create an app at [myapi.fyers.in/dashboard](https://myapi.fyers.in/dashboard);
note the **App ID** (e.g. `XXXX-100`) and **Secret ID**, and set a redirect
URL such as `http://127.0.0.1/`. Only App IDs ending in `-200` can place
orders.
2. `stockwise trade config --broker fyers`; put the App ID in `api_key`, the
Secret ID in `api_secret`, and the `redirect_uri`.
3. Every trading day: `stockwise trade login --broker fyers` — log in in the
browser and paste the redirect address (it contains `auth_code=…`).
StockWise validates it with `appIdHash = sha256(app_id:secret)`. Sessions end
around 06:30 IST.
## Commands
```bash
stockwise trade setup [--broker B] # How to connect
stockwise trade config [--broker B] [--keyring]
stockwise trade login --broker B # Zerodha, Upstox, Fyers, Dhan
stockwise trade holdings # Live holdings (synced into `portfolio`)
stockwise trade positions # Open positions
stockwise trade orders # Today's order book
stockwise trade buy RELIANCE 10 --dry-run # Resolve + preview, send nothing
stockwise trade sell TCS 5 --trigger 3400 # SL-M sell
stockwise trade gtt list [--notify] # GTT orders + status changes since the last look
stockwise trade sync [--since DATE] [--apply] # Fills into the portfolio (preview first)
stockwise trade selftest [--redact-amounts] [--json] # Read-only check; report for a GitHub issue
```
## Help verify a broker
Zerodha, Upstox, Dhan and Fyers — and stop-loss / GTT orders on Angel One —
were built from public API docs and have never been run against a real account
by the maintainers. If you have an account with one of them, you can help take
the "experimental" label off in about five minutes, without placing an order.
1. Install the latest StockWise and set the broker up as described above
(`trade setup`, `trade config`, `trade login`).
2. Run the self-test:
```bash
stockwise trade selftest --broker zerodha
stockwise trade selftest --broker zerodha --redact-amounts # also hide prices
stockwise trade selftest --broker zerodha --json # machine-readable
```
It is **strictly read-only**. It logs in (or loads today's session), reads
the profile, holdings, positions, order book, trade book and GTT list,
resolves RELIANCE, TCS and NIFTYBEES (the Nifty 50 ETF) exactly, runs a
1-share LIMIT buy 5% below the market through every order check and stops
before sending (like `--dry-run`), and compares the field names of each
response with the documented ones. The session it uses refuses any order,
GTT or cancel in code, and the self-test never calls those methods.
3. Read the report, then paste it into a
[broker test report](https://github.com/iamsaquib8/stockwise/issues/new?template=broker_test_report.yml).
It contains no credentials, tokens, names, e-mail addresses or holdings
symbols; the account id is cut to its last two characters, and long numbers
in error messages are masked. Field names, row counts, statuses and
instrument tokens stay, as do the dry-run price and value unless you pass
`--redact-amounts`. The command exits with status 1 if a check failed.
4. Optional, and only if you want to: place **one small order yourself** —
say a 1-share LIMIT buy of a liquid stock below the market, which you can
cancel — after adding `"enable_orders": true` to the broker's config. Check
it in the broker's app, run `trade orders` and `trade sync`, and say in the
report what happened. Nobody will ask you to place an order; this is the
step that proves the write path.
### Verification status
| Broker | Status | Self-test reports | Small live order confirmed by a tester |
|--------|--------|-------------------|----------------------------------------|
| Angel One — MARKET, LIMIT | original integration (0.2), not experimental | reports welcome | — |
| Angel One — SL, SL-M, GTT | experimental | 0 of 3 | no |
| Zerodha Kite | experimental | 0 of 3 | no |
| Upstox | experimental | 0 of 3 | no |
| Dhan | experimental | 0 of 3 | no |
| Fyers | experimental | 0 of 3 | no |
**From experimental to verified.** A broker (or order path) loses the
experimental label in a release when all of these hold:
1. **3 independent self-test reports** — different testers and accounts — on
a release that includes any fixes they led to, with no failed checks and no
missing fields left unexplained.
2. **One confirmed small live order**, placed by a tester on their own
initiative (never on request) through `trade buy` / `trade sell` (and, for
GTT paths, `trade gtt`), that matched the preview in the broker's app and
showed up in `trade orders` and `trade sync`.
3. A maintainer has reviewed the reports and updated this table, the
`BrokerKind::experimental` flag and the CHANGELOG.
Until then, live orders on that path need `"enable_orders": true`, and every
preview says the path is experimental.
## Importing holdings
> **Guide:** [Import your trades and see true returns](https://iamsaquib8.github.io/stockwise/guide-portfolio.html) walks through a tradebook and CAS import.
Import holdings from other brokers without manual entry:
```bash
stockwise import kite ~/Downloads/holdings.csv # Zerodha Kite
stockwise import indmoney ~/Downloads/portfolio.csv # INDmoney
stockwise import groww ~/Downloads/holdings.xlsx # Groww (CSV or .xlsx)
stockwise import upstox ~/Downloads/holdings.csv # Upstox
stockwise import cas ~/Downloads/cas.json # CAMS / KFintech / NSDL / CDSL CAS via casparser
stockwise import csv ~/Downloads/stocks.csv # Any CSV
```
Columns are detected by name (Symbol/Instrument/Stock Name/ISIN,
Quantity/Qty, Avg Cost/Average buy price), and leading account-detail rows are
skipped. A holdings export is a snapshot: existing lots and their dates are
kept and only the quantity follows the file, so re-importing never doubles a
holding. `trade holdings` syncs broker holdings the same way.
An `.xlsx` workbook may have several sheets (a mutual-fund tab, a summary).
StockWise reads only one: the first, in tab order, whose header has the columns
the importer needs. It prints the sheet it used and the ones it skipped; choose
another with `--sheet NAME`.
- **Groww** exports holdings as `.xlsx` with no ticker column: import the file
as it is, and StockWise looks up each ISIN's NSE ticker (Yahoo search, one exact
result only). Mutual-fund ISINs that don't trade on an exchange become
`MF:`.
- **Upstox** holdings exports have no published layout; StockWise detects the
usual columns (Symbol / Scrip Name / ISIN, Qty, Avg. Price). If it can't,
use `import csv` with Symbol, Quantity and Average Price columns — and please
open an issue with the header row.
### CAS (consolidated account statement)
CAS PDFs are password-protected and their tables don't survive plain text
extraction, so StockWise reads the output of the open-source
[casparser](https://github.com/codereverser/casparser) instead:
```bash
pip install casparser
casparser CAS.pdf -p -o cas.json # or -o cas.csv
stockwise import cas cas.json
```
- **CAMS / KFintech** (mutual funds): each scheme with units left becomes
`MF:` (or `MF:`). When the statement's transactions cover a
scheme's whole balance (a CAS from the first purchase), every purchase becomes
a dated lot (stamp duty included) and every redemption a FIFO sale, so `tax`
and XIRR use the real dates. Otherwise the statement's cost value is used to
sync the quantity; a statement that starts with units of unknown cost only
updates funds you already track. A scheme you have fully redeemed is imported
only as its sales (no holding is left) when the statement covers every
purchase; otherwise it is skipped with a reason. The JSON and both CSV forms
(transactions, `-s` summary) are supported. A scheme with lots from elsewhere is only rebuilt
with `import cas FILE --replace`.
- **NSDL / CDSL** (demat): equities carry no purchase cost, so they only update
the quantity of holdings already in your portfolio (keeping your cost);
new ones are listed with a `portfolio add` hint. Mutual funds in a demat CAS
are imported with their average cost when it is given.
- Feeding the PDF directly prints these instructions. Imported funds are
valued at AMFI's latest end-of-day NAV in `portfolio`, `dashboard`, `wealth`,
`rebalance`, `tax` and the rest, matched by AMFI code or by either ISIN
(growth/payout or IDCW reinvestment). A scheme AMFI no longer lists stays
N/A with a note, never 0 (see
[Mutual funds and SGBs](https://iamsaquib8.github.io/stockwise/commands.html#mutual-funds-and-sgbs)).
---
# Terminal dashboard (`stockwise tui`)
A full-screen, live version of `stockwise dashboard`: market indices, your
watchlist, portfolio P&L, price alerts, and a chart of the selected symbol,
refreshed in place.

```bash
stockwise tui # Indian market (default)
stockwise tui -m us # US indices; bare tickers are US symbols
stockwise tui -i 60 # refresh every 60 seconds (default 30, range 10-3600)
```
**Read-only.** The TUI never places orders and has no trading keys. The only
thing it changes is your local watchlist (add and remove). Live trading stays
in `stockwise trade`, with its preview and confirmation.
## Layout
| Pane | Shows |
|------|-------|
| Header | Market, time of the last refresh, ⟳ while a refresh runs |
| Indices | NIFTY 50, SENSEX, BANK NIFTY, NIFTY IT (or the US indices with `-m us`). Prices are dropped on narrow terminals |
| Watchlist | Price and day change for every watched symbol |
| Chart | Closing prices of the selected symbol for the selected period, with the change over the period |
| Portfolio | Total value and P&L, then each holding. Holdings without a live price show N/A and are left out of the totals (the title says how many) |
| Alerts | Triggered alerts first (marked `!`), then the rest, with the live price. An alert never fires without a price |
| Footer | Key hints, or the latest message (errors in red) |
The chart follows the selection: move through the watchlist or, after `Tab`,
the portfolio. With both empty it shows the first index.
## Keys
| Key | Action |
|-----|--------|
| `↑` `↓` / `j` `k` | Move the selection (the chart follows) |
| `g` `G` / `Home` `End` | First / last row |
| `Tab` | Switch between watchlist and portfolio |
| `1`-`6` / `[` `]` | Chart period: `1d` `5d` `1mo` `6mo` `1y` `5y` |
| `/` or `a` | Search a company or ticker and add it to the watchlist (`Enter` searches, `↑` `↓` choose, `Enter` adds, `Esc` cancels). Listings on your market's exchanges (NSE/BSE for India) are listed first |
| `x` / `Del` | Remove the selected watchlist symbol |
| `r` | Refresh now |
| `?` | Show or hide the help |
| `q` / `Ctrl+C` | Quit |
## Refresh and rate limits
Every refresh is one batched quote request for the indices, watchlist,
holdings and alert symbols together, plus one chart request. All requests go
through the same client as the CLI, so they share its rate limiter (5
requests/second), retries, and cache. Quotes are cached for 30 seconds (in
Redis too, when it runs), so an interval below 30s mostly re-reads the cache.
If a refresh fails (network down, Yahoo rate-limiting), the last data stays on
screen, the footer shows the error, and the next refresh tries again.
## Terminal support
- Works in any terminal crossterm supports (macOS, Linux, Windows Terminal).
- **80×24** shows everything. Smaller (down to 40×12) drops the chart and
shows the alerts as one line; smaller still shows a "too small" message
until you resize.
- `NO_COLOR=1` turns colours off; direction is still shown by ▲/▼ and +/−
signs, and the selection by reverse video.
- The terminal is restored on `q`, `Ctrl+C`, `SIGINT`, errors, and panics.
- Needs an interactive terminal. For a one-shot view use `stockwise dashboard`;
for scripts, the `--json` commands or [`stockwise serve`](https://iamsaquib8.github.io/stockwise/server.html).
## Screenshots
`stockwise tui --snapshot 100x30` fetches the data once, prints a single
frame, and exits. With `CLICOLOR_FORCE=1` the frame keeps its colours when
piped, which is how `assets/tui.svg` is made:
```bash
CLICOLOR_FORCE=1 stockwise tui --snapshot 100x30 \
| python3 assets/ansi2svg.py assets/tui.svg "stockwise tui"
```
## Building without it
The TUI is the `tui` cargo feature, on by default. A minimal build leaves out
ratatui and crossterm:
```bash
cargo install stockwise --no-default-features # no tui, no serve
cargo install stockwise --no-default-features --features tui
```
`stockwise tui` in a build without the feature exits with a message saying so.
---
# Web dashboard, HTTP API and metrics (`stockwise serve`)
`stockwise serve` runs a small web dashboard for your browser, exposes
StockWise data as JSON for remote dashboards, spreadsheets, Grafana or Home
Assistant, and serves Prometheus metrics for the server and the intraday
daemon.
```bash
stockwise serve # http://127.0.0.1:8787; Ctrl+C stops it
stockwise serve --open # ...and open the dashboard in your browser
stockwise serve --bind 127.0.0.1:9000 # another port (or STOCKWISE_BIND)
stockwise serve -m us # bare tickers are US symbols
```
**Read-only.** There are no endpoints that trade, change your portfolio,
watchlist or alerts, or touch the broker. Broker credentials are never read.
## Web dashboard
Open the address the server prints at startup (`Dashboard
http://127.0.0.1:8787/`), or start it with `--open`. One page shows:
- **Indices** — your market's indices, then the other stock market's. Select
one to chart it.
- **Chart** — the selected symbol's price for 1D, 5D, 1M, 3M, 6M, 1Y, 5Y or
Max, with its live quote and day range. Hover it, or focus it and use ←/→,
PgUp/PgDn, Home/End, to read a date's close, high, low and volume; Esc hides
the crosshair. Times are in your browser's time zone.
- **Watchlist** — price, change and volume for `stockwise watch`.
- **Portfolio** — value, invested amount and P&L (Indian amounts in lakhs and
crores, e.g. ₹1.74 L, ₹2.10 Cr), an allocation bar, and every holding with
quantity, average cost, price, value, P&L, XIRR and weight. Column headers
sort the table; *Totals in* switches the totals to INR or USD (`?base=`).
Holdings without a live price or exchange rate are listed below the table
and left out of the totals, as in `portfolio`. Mutual funds show their
scheme name and are valued at AMFI's end-of-day NAV (its date is noted under
the table); SGBs come from NSE's list or a labelled gold-spot estimate.
- **Alerts** — every alert with its live price; triggered ones first.
- **Search** — type a company or ticker, pick a result with the arrow keys
and Enter (or a click) to chart it.
The page refreshes every 30 seconds while its tab is visible (intraday charts
too), and right away when you come back to it. The selected symbol and period
are kept in the address (`/#symbol=TCS.NS&period=6mo`), so you can bookmark a
chart. It works on a phone (narrow cards drop the less important columns),
follows your system's light or dark theme, and is usable with the keyboard
alone.
The dashboard is a few static files (`web/` in the repository) compiled into
the binary: no build step, no CDN, no fonts or scripts from anywhere else.
It reads the same `/api/*` endpoints described below, so it shows exactly
what the CLI prints, and it can't change anything — it only sends `GET`
requests.
**With a token.** When the server has `STOCKWISE_API_TOKEN` (required for
`--allow-remote`), the page asks for it on first load. The token is kept in
the tab's session storage (gone when you close the tab; *Sign out* forgets it
sooner) and sent only as an `Authorization: Bearer` header to the same
server — never in the address, a cookie or a log. A wrong token brings the
prompt back.
## Endpoints
All responses are JSON except `/metrics`. Bare tickers follow the server's
market (`-m`, default India: `TCS` → `TCS.NS`); `quote`, `history`,
`markets` and `portfolio` accept `?market=in|us|crypto|fx` to override it per
request.
| Endpoint | Returns | Same as |
|----------|---------|---------|
| `GET /api/quote?symbols=RELIANCE,TCS` | Array of Yahoo quote objects (camelCase fields). Up to 50 symbols. Unknown symbols are listed in the `X-Stockwise-Missing` header; 404 if none are known | `quote --json` |
| `GET /api/history/{symbol}?period=1mo` | `{symbol, currency, period, interval, bars: [{time, open, high, low, close, volume}]}`. Periods as in the CLI; default `1mo` | `history --json` |
| `GET /api/markets?crypto=true&fx=true` | Array of `{region, symbol, name, price, change, change_percent, currency}`, your market first; `crypto` / `fx` add those sections | `markets --json` |
| `GET /api/search?q=tata motors` | Array of `{symbol, shortname, longname, quoteType, exchange}` | `search --json` |
| `GET /api/portfolio?base=usd` | Holdings (lots, FIFO cost) valued at live prices, totals in one currency (below); `base` is `inr` or `usd` | `portfolio --json` |
| `GET /api/watchlist` | Array of `{symbol, price, change, change_percent, volume, currency}` in watchlist order | `watch --json` |
| `GET /api/alerts` | Array of `{index, symbol, condition, target, price, currency, status, period, description, value, reference}`; `status` is `triggered`, `waiting`, `no_price` or `no_data` (technical alert with too little history) | `alert check --json` |
| `GET /api/events` | Server-sent events: `quotes` (`{type, quotes:[{symbol, price, change_percent}]}`) when held or watched prices move, `task` (`{type, task:{id, title, status, progress_pct}}`) when a background task changes. Keep-alive comments every 15 s | — |
| `GET /healthz` | `{"status": "ok", "version": "..."}` — never needs a token | |
| `GET /metrics` | Prometheus text format (below) | |
`/api/portfolio`:
```json
{
"holdings": [
{"symbol": "TCS.NS", "shares": 10.0, "avg_cost": 3500.0, "cost_basis": 35000.0,
"price": 4100.0, "value": 41000.0, "pnl": 6000.0, "pnl_percent": 17.14,
"weight_percent": 100.0, "xirr_percent": 15.2, "currency": "INR",
"name": "TATA CONSULTANCY SERV LT", "price_source": "quote", "nav_date": null}
],
"totals": {"cost_basis": 35000.0, "value": 41000.0, "pnl": 6000.0,
"pnl_percent": 17.14, "currency": "INR"},
"unpriced": [],
"unconverted": [],
"notes": []
}
```
`price_source` is `quote`, `amfi_nav` (a mutual fund at its NAV, dated by
`nav_date`), `nse_sgb`, `gold_estimate` (an SGB valued at its gram of gold) or
`null`; `notes` explain fund and SGB prices (see
[Mutual funds and SGBs](https://iamsaquib8.github.io/stockwise/commands.html#mutual-funds-and-sgbs)).
A holding's money values are in its own currency; `totals` and
`weight_percent` are converted to the base currency at the live rate. A
holding without a live price has `null` price, value, P&L and weight, is
listed in `unpriced`, and is left out of the totals — it is never valued at 0.
One whose currency couldn't be converted is listed in `unconverted`. The
server uses the active portfolio (`--portfolio` / `STOCKWISE_PORTFOLIO` when
it was started).
Likewise an alert without a price has `"price": null, "status": "no_price"`
and never counts as triggered; a technical alert without enough daily bars
reads `"status": "no_data"`.
Errors are `{"error": "..."}` with status 400 (bad parameter), 401 (missing
or wrong token), 403 (request not addressed to localhost while no token is
set, or sent by a web page on another origin; see below), 404 (unknown symbol
or endpoint), 405 (not GET), 500 (a local
data file such as `portfolio.json` could not be read; details only in the
server's log), or 502 (market data could not be fetched). Responses carry
`Cache-Control: no-store`.
```bash
curl -s 'localhost:8787/api/quote?symbols=RELIANCE,TCS' | jq '.[] | {symbol, regularMarketPrice}'
curl -s 'localhost:8787/api/history/INFY?period=6mo' | jq '.bars | map(.close) | max'
curl -s localhost:8787/api/portfolio | jq '.totals | {value, pnl_percent}'
curl -s localhost:8787/api/alerts | jq '.[] | select(.status == "triggered")'
```
## Prometheus metrics
```
stockwise_build_info{version="0.8.1"} 1
stockwise_uptime_seconds 3600.1
stockwise_http_requests_total{method="GET",route="/api/quote",status="200"} 42
stockwise_http_request_duration_seconds_bucket{route="/api/quote",le="0.25"} 40
stockwise_upstream_requests_total 57 # requests sent to Yahoo, retries included
stockwise_upstream_errors_total 1 # fetches that failed after all retries
stockwise_cache_hits_total{layer="redis"} 12
stockwise_cache_hits_total{layer="memory"} 30
stockwise_cache_misses_total 57
stockwise_daemon_up{market="IN"} 1
stockwise_daemon_up{market="US"} 0
stockwise_daemon_status_age_seconds{market="IN"} 41
stockwise_daemon_last_update_timestamp_seconds{market="IN"} 1792560600
stockwise_daemon_open_positions{market="IN"} 3
stockwise_daemon_day_pnl{market="IN",currency="INR"} 412.5
stockwise_daemon_realized_pnl{market="IN",currency="INR"} 120
stockwise_daemon_unrealized_pnl{market="IN",currency="INR"} 292.5
stockwise_daemon_capital{market="IN",currency="INR"} 25000
stockwise_daemon_kill_switch{market="IN"} 0
stockwise_daemon_settled{market="IN"} 0
```
`route` is the route template (`/api/history/{symbol}`), so label values stay
bounded; anything that matched no route is `unmatched`, and unusual HTTP
methods are counted as `OTHER`.
**Daemon metrics.** `stockwise daemon intraday` saves its day state in the
data directory after every tick — `daemon_state.json` for NSE,
`daemon_state_us.json` for `-m us` — and the server reads both, so run them
against the same data directory (`STOCKWISE_HOME`). Every daemon gauge has a
`market` label (`IN` or `US`). `stockwise_daemon_up` is 1 if that market's
state is less than 45 minutes old. A running daemon saves at least every 30
minutes, including overnight, at weekends and on holidays, so it stays 1 around
the clock; it is 0 when no daemon is running (never started, stopped, crashed,
or exited after `daemon intraday --once`). The other `stockwise_daemon_*` gauges appear
once a daemon has run. The daemon paper-trades only.
The money gauges (`day_pnl`, `realized_pnl`, `unrealized_pnl`, `capital`) also
carry a `currency` label (`INR` for `IN`, `USD` for `US`, the exchange's
currency for the others): the values are in each market's own currency, so
`sum()` them by `currency`, never across it. A settled day (the session is over
and recorded in `sim history`, including one the daemon handed to `sim settle`
after missing the square-off) reports `stockwise_daemon_open_positions 0` and
`stockwise_daemon_unrealized_pnl 0`, with `stockwise_daemon_settled 1`.
`stockwise_daemon_last_update_timestamp_seconds` is the Unix time of the last
save; `time() - stockwise_daemon_last_update_timestamp_seconds` gives the age
in a query, and `stockwise_daemon_status_age_seconds` is the same figure
computed at scrape time.
```yaml
# prometheus.yml
scrape_configs:
- job_name: stockwise
static_configs:
- targets: ["127.0.0.1:8787"]
# with a token:
# authorization: { type: Bearer, credentials: "" }
```
Example alert: `stockwise_daemon_kill_switch{market="IN"} == 1`, or
`stockwise_daemon_up{market="IN"} == 0` for as long as you expect the daemon to
be running (all the time, unless you start it with `--once` from a scheduler).
## Remote access
The API shows your holdings, so it listens on loopback only by default and
refuses any other address. Without a token it also only answers requests
whose `Host` is `localhost`, `*.localhost` or a loopback IP, so a web page
can't reach it through DNS rebinding.
**Shared-machine security:** Loopback prevents remote network access, not
access by other operating-system users. On a machine with multiple user
accounts, another local user can connect to the unauthenticated loopback API
and read portfolio holdings and other private data. Set `STOCKWISE_API_TOKEN`
even when binding only to `127.0.0.1` on a shared machine. The bearer token
is required on loopback too; keep it private to your user account.
```text
$ stockwise serve --bind 0.0.0.0:8787
Error: Refusing to listen on 0.0.0.0:8787: other machines could read your portfolio.
```
To serve other machines, set a bearer token (16+ characters) and pass
`--allow-remote`:
```bash
export STOCKWISE_API_TOKEN=$(openssl rand -hex 32)
stockwise serve --bind 0.0.0.0:8787 --allow-remote
curl -H "Authorization: Bearer $STOCKWISE_API_TOKEN" http://my-host:8787/api/portfolio
```
When `STOCKWISE_API_TOKEN` is set, every endpoint except `/healthz` and the
dashboard's static files (`/`, `/assets/*`, which hold no data) requires it —
on loopback too. The server speaks plain HTTP: across an untrusted network,
put it behind a TLS reverse proxy (Caddy, nginx) or a private network such as
Tailscale or WireGuard, so the token isn't sent in clear text.
## Browser security
The dashboard and the API are meant to be read from the server's own origin
only:
- **No CORS.** The server never sends `Access-Control-Allow-*` headers, so a
page on another site can't read responses, and preflight requests get 405.
- **Cross-origin requests are refused.** Browsers label each request with
`Sec-Fetch-Site`; `/api/*` and `/metrics` answer 403 unless it is
`same-origin` or `none` (typed address, bookmark). Clients that aren't
browsers (curl, Prometheus, Grafana's server) don't send the header and are
unaffected.
- **Strict headers on every response:** `Content-Security-Policy:
default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self';
connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors
'none'; require-trusted-types-for 'script'; trusted-types 'none'`, plus
`X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`,
`Cross-Origin-Opener-Policy` and `Cross-Origin-Resource-Policy:
same-origin`, `X-Content-Type-Options: nosniff` and `Cache-Control:
no-store`.
- **No inline code.** The page has no inline scripts, styles or event
handlers, and inserts API data only as text, never as HTML (the tests in
`src/server/web.rs` check both).
- **No cookies.** Authentication is a header the page adds itself, so another
site can't make your browser send it (no CSRF).
- Without a token, the `Host` check above also covers the dashboard, so a
DNS-rebinding page can't load it.
## Rate limits and caching
All requests share one Yahoo client, so they share its rate limiter (5
requests/second), retries, and cache (quotes 30s, charts 1-10 min; Redis when
available). The Yahoo session starts on the first request that needs it and is
retried on the next request if it fails, so the server starts without
network access.
## Building without it
The server is the `server` cargo feature (axum), on by default.
`cargo install stockwise --no-default-features` leaves it out; `stockwise
serve` then exits with a message saying so.
---
# Troubleshooting
Start with `stockwise doctor`. It checks the config file, the data directory,
whether Yahoo Finance answers, the optional Redis cache, the AI model and your
broker config, and exits with status 1 if market data is unreachable or the
config file is invalid.
Walkthroughs for the common jobs are in [What do you want to do?](https://iamsaquib8.github.io/stockwise/guides.html).
The messages below are quoted from StockWise. `...` stands for text that
changes from run to run.
## Where is my data?
Your portfolio, watchlist, alerts and caches are plain files in one folder.
`stockwise doctor` prints it (the line "Data directory").
| System | Default folder |
|--------|----------------|
| macOS | `~/Library/Application Support/stockwise/` |
| Linux | `~/.local/share/stockwise/` |
| Windows | `%LOCALAPPDATA%\stockwise\` |
`STOCKWISE_HOME` (or `home` in the [config file](https://iamsaquib8.github.io/stockwise/commands.html#configuration-file))
chooses another folder. Use a scratch folder to try things without touching your
portfolio: `export STOCKWISE_HOME=$(mktemp -d)`. The files are listed in
[Data storage](https://iamsaquib8.github.io/stockwise/architecture.html#data-storage). `cache/` is safe to delete.
A named portfolio (`--portfolio NAME`) lives in `portfolios/NAME.json`.
## Yahoo Finance is unreachable or throttled
Most prices and history come from Yahoo Finance. Three messages come from it:
- `Cannot reach Yahoo Finance (...). Check your internet connection.` The
request did not get an answer. Check the network, a VPN or a proxy, then run
`stockwise doctor`.
- `Yahoo Finance is rate limiting requests. Wait a minute and try again.` Yahoo
answered HTTP 429. StockWise already retries three times and honours
`Retry-After`, so run fewer commands at once (a loop over many symbols is the
usual cause) and try again in a minute. Redis, if you run it, caches answers
across commands.
- `Yahoo Finance refused to start a session (HTTP ...)` or `Yahoo Finance
returned HTTP ... after 3 attempts`. Yahoo is having a problem. Wait and retry,
or see [data providers](https://iamsaquib8.github.io/stockwise/data-providers.html).
## NSE refuses automated access (HTTP 403)
NSE's website serves browsers and often blocks scripts. StockWise opens one polite
session, spaces its requests, never retries a refusal and does not try to get
around it. You see one of:
- `NSE refused the option-chain request (HTTP 403). NSE's API serves browser
sessions and sometimes blocks scripts — wait a minute and retry, or open
nseindia.com/option-chain. ...` Affects `options chain`, `options pcr`,
`options oi` and `options maxpain` for Indian symbols. US options (`-m us`) use
Yahoo instead. BSE has the same kind of message (`BSE refused the
option-chain request ...`).
- `NSE refused the session (HTTP 403)`, `NSE refused the request (...)` or `NSE
refused an earlier request; not asking again for now`. After a refusal
StockWise stays away from that site for a while (15 minutes for most
features, an hour for the calendar), so retrying at once does not help.
Long-running `serve`, `tui` and `daemon` processes try again after that.
- `NSE refused the request (HTTP 403); ... Showing Yahoo Finance dates only` in
`calendar`: you still get Yahoo's dates, without NSE's board meetings and
corporate actions.
Features that read NSE lose that data while it refuses: `ipo`, the filings in
`events`, `calendar`, `--universe` scans (a built-in list is used, with a note
on stderr), SGB prices (the last saved list is shown, or no price) and the NSE
data provider. Waiting a while, or a different network, usually fixes it.
## AMFI file unavailable
Mutual fund NAVs come from AMFI's `NAVAll.txt`, cached in `cache/` for 12
hours. If the download fails:
- With an older copy in `cache/`, the command still works and prints
`Warning: ...; using the copy saved from ` on stderr.
- Without one, you get `AMFI returned HTTP ...`, `Cannot reach AMFI (...)` or
`AMFI's NAV file had no schemes; try again later`.
`mf search`, `mf nav`, and `portfolio`, `tax in` and `harvest` for funds you
hold all need that file (`mf history` and `mf sip` also use api.mfapi.in). A fund that AMFI does not list stays `N/A`
with a note, never 0. Expense ratios (TER) come from a second AMFI download;
when it fails, NAVs and returns still print with a note, but `mf screen -s ter`
and `--max-ter` stop with an error.
## `Not enough price data`
```
Error: Not enough price data for technical analysis — try a longer period, e.g. -p 6mo
```
An indicator needs enough daily bars: `technical` needs 30, so `-p 1mo`
(about 21 bars) is too short, and `risk` and `forecast` have similar limits.
Use a longer period (`-p 6mo`, `-p 1y`). A newly listed stock may not have
enough history at all.
## Unknown ticker
```
Error: No quote for RELIANC.NS (Indian market is the default — for US stocks add `-m us`; use `stockwise search ` to find a ticker)
Did you mean: RELIANCE.NS (Reliance Industries)
```
- A bare ticker is Indian (`TCS` means `TCS.NS`). For a US stock add `-m us`
(`stockwise quote AAPL -m us`), or set `STOCKWISE_MARKET=us`.
- `stockwise search "company name"` finds the ticker.
- `portfolio add`, `watch add` and `alert add` refuse a ticker Yahoo does not
know and save nothing: `Nothing was saved. Add --force to keep it anyway.` Use
`--force` only when you are sure, for example for a delisted stock.
- A mutual fund is `MF:` or `MF:`, not a ticker (`stockwise mf search`
finds the code).
## `--yes` needs `max_order_value`
```
--yes skips the typed confirmation, so it needs a cap on the order value. Add
"max_order_value": 50000 to (your config), or leave out --yes and
confirm by typing yes. --dry-run always works.
```
`trade buy`, `trade sell` and the other order commands normally show a preview
and ask you to type `yes`. `--yes` skips that, and StockWise only allows it when
your broker config file has a `max_order_value`. Add the key (the message names
the file) or drop `--yes`. If the order's value is above the cap, or cannot be
estimated (a market order without a price), the order is refused: `Estimated
value ₹... exceeds max_order_value ₹... in your ... config`. Try the order with
`--dry-run` first. See [Brokers](https://iamsaquib8.github.io/stockwise/brokers.html).
## An import skips a symbol
`stockwise import` prints one line per skipped symbol, and exits with status 1
(`Nothing imported from FILE — see the skipped holdings above.`) when it
updated nothing.
- `skipped: it has lots or sales from elsewhere (manual entries, another import,
or a tradebook imported by an older version) that this file would replace.
Re-run with --replace to rebuild it from this file.` A dated file (a tradebook
or a list of lots) rebuilds a symbol's lots, so StockWise will not do it over
lots you entered another way. Run the import again with `--replace` if the
file should win. Check first with `stockwise portfolio lots SYMBOL`.
- `not applied yet: sells of N shares have no earlier buy in the trades
imported so far.` A tradebook sold shares that an earlier file bought. Import
the earlier tradebook too (the trades of this file are kept, in any order),
or use `--replace` to rebuild from this file alone.
- A holdings file with no dates only changes quantities: more shares add a lot
dated today, fewer take from the oldest lots. A split or bonus that is not applied
yet blocks it; run `stockwise portfolio actions --apply` first.
- An unrecognised date is refused, and an `.xlsx` workbook with several sheets
uses the first one whose header fits (`--sheet NAME` picks another).
More in [importing holdings](https://iamsaquib8.github.io/stockwise/brokers.html#importing-holdings) and
[the portfolio commands](https://iamsaquib8.github.io/stockwise/commands.html#lots-sales-and-tax).
## The config file is invalid
`Invalid config file : ...` stops every command with exit code 1. `config`,
`doctor` and `completions` still run. Fix the key it names, or write a fresh
template with `stockwise config init --force`.
## Still stuck?
Run the command with `--json` (errors go to stderr, so stdout stays clean) or
check the [commands reference](https://iamsaquib8.github.io/stockwise/commands.html). To report a bug, include the
command, its full output and `stockwise --version` in an
[issue](https://github.com/iamsaquib8/stockwise/issues/new/choose). Do not paste
files from your data folder, broker config or API keys.
---
# How StockWise compares
StockWise is a free, open-source command-line toolkit that runs on your own
machine. This page compares it with tools people often use instead, so you can
pick the right one. It is based on each project's public documentation as of
October 2026, and the StockWise column was checked against the code in November
2026. If something here is out of date,
[open an issue](https://github.com/iamsaquib8/stockwise/issues) and we'll fix it.
Key: ✓ yes · ◐ partly · ✗ no
## Open-source tools
| | StockWise | OpenBB | ticker | Ghostfolio | Wealthfolio | Portfolio Performance | Freqtrade | OpenAlgo |
|---|---|---|---|---|---|---|---|---|
| Runs as | CLI, TUI, local web | Python, API | TUI | Self-hosted web | Desktop app | Desktop app | Bot + web UI | Self-hosted web |
| Indian stocks (NSE/BSE) | ✓ default | ◐ | ◐ | ◐ | ◐ | ◐ | ✗ | ✓ via brokers |
| US and global stocks | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ◐ |
| Indian mutual funds (AMFI) | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Indian capital-gains tax, ITR-style export | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Portfolio with lots and XIRR | ✓ | ✗ | ◐ | ✓ | ✓ | ✓ | ✗ | ✗ |
| Option chains, greeks, payoff | ✓ NSE, BSE, US | ✓ US | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ |
| Backtesting with Indian costs | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ◐ crypto | ◐ |
| Paper trading and historical replay | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ dry-run | ✓ sandbox |
| Live broker orders | ◐ Angel One; 4 experimental | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ crypto | ✓ many brokers |
| Scripting (`--json`, REST) | ✓ | ✓ | ✗ | ◐ | ◐ | ✗ | ✓ | ✓ |
| No account or API key needed | ✓ | ◐ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ |
| Single binary, no runtime | ✓ | ✗ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ |
## Web and app platforms
Popular Indian platforms such as Screener.in, Trendlyne, Tickertape,
StockEdge, Sensibull, Zerodha Console and Kuvera go deeper in their own areas —
fundamentals and screening data, open-interest analytics, family accounts —
and some of that depth relies on licensed data that a free tool can't
redistribute. StockWise's angle is different:
- **Your data stays on your machine.** Holdings live in local files, broker
secrets in your OS keyring, and AI runs locally by default.
- **One tool across the whole journey** — research, funds, options, tax,
backtests, practice and orders — instead of a separate app for each.
- **Scriptable.** Many commands take `--json` and tables take `--csv`, and
`stockwise serve` exposes a local API.
- **Free and open source** (MIT).
## When to use something else
- You want the deepest fundamentals and custom screening on Indian companies:
a dedicated screener such as Screener.in.
- You trade options daily and need intraday open-interest charts or a strategy
builder with live P&L: a dedicated options platform. `options oi` is a
snapshot of one expiry.
- You want a polished desktop or phone app for a household's net worth:
Wealthfolio, Ghostfolio or your broker's app.
- You run 24/7 crypto trading bots: Freqtrade.
- You want broker connectivity across dozens of Indian brokers: OpenAlgo.
StockWise is for education and information, not financial advice — see the
[disclaimer](https://iamsaquib8.github.io/stockwise/disclaimer.html).
---
# Roadmap
The plan from today to a public 1.0, then what comes after. Each release has a goal, the issues
in it, and a test for "done". Target dates are aims, not promises: a release ships when its
"done when" list is true. Have an opinion? Open a
[Discussion](https://github.com/iamsaquib8/stockwise/discussions) or upvote an
[issue](https://github.com/iamsaquib8/stockwise/issues) — the roadmap follows demand.
The live view is on GitHub: [milestones](https://github.com/iamsaquib8/stockwise/milestones) for
releases, and eleven epics for the themes. Every open issue is a sub-issue of exactly one epic, so
each epic shows its full list and progress.
| Epic | Goal | Until |
|---|---|---|
| [#257](https://github.com/iamsaquib8/stockwise/issues/257) Install anywhere | One command on every platform, updates included | 0.9 |
| [#258](https://github.com/iamsaquib8/stockwise/issues/258) Trustworthy portfolio data | Multi-currency & multi-broker imports never lose or corrupt lots | 0.10 |
| [#259](https://github.com/iamsaquib8/stockwise/issues/259) Global & Indian tax season | Capital gains, lot engines (`FIFO`/`HIFO`/`Sec 104`/`ACB`), wrappers & tax exports | 0.9 |
| [#260](https://github.com/iamsaquib8/stockwise/issues/260) Neutral, accurate research | Data and transparent scores, no buy/sell calls, correct numbers | 0.10 |
| [#261](https://github.com/iamsaquib8/stockwise/issues/261) StockWise for AI assistants | Ask about your portfolio, data kept local | 0.10 |
| [#262](https://github.com/iamsaquib8/stockwise/issues/262) A simple, stable 1.0 | A small CLI ([#205](https://github.com/iamsaquib8/stockwise/issues/205)), a compatibility promise, quality gates | 0.10 |
| [#278](https://github.com/iamsaquib8/stockwise/issues/278) Reliable market data | Sources that recover, fast commands, a live-data canary | 0.10 |
| [#279](https://github.com/iamsaquib8/stockwise/issues/279) Security and trading safety | Private secrets, safe local servers, guarded live orders | ongoing |
| [#280](https://github.com/iamsaquib8/stockwise/issues/280) Dashboards, alerts and practice | A dependable TUI, web dashboard, alerts and simulator | ongoing |
| [#281](https://github.com/iamsaquib8/stockwise/issues/281) Launch and community | Be found, be tried in a minute, hear from users | 1.0 |
| [#282](https://github.com/iamsaquib8/stockwise/issues/282) More markets and deeper research | Coverage and research depth users ask for | after 1.0 |
Legend: ✅ shipped · 🛠️ in progress · 📋 planned · 💡 exploring
## ✅ Shipped
**0.8.1** — fix what was wrong
- Imports no longer lose lots: a second tradebook or broker file merges, same-day trades are
intraday, HDFC and Mindtree trades use the merger ratios, and writes are locked
- Gold, silver, liquid and international ETFs are no longer taxed as listed shares
- Correct SIP projection, BSE codes reported as not found, USD statements converted, dividend yield
- `verdict`, `ipo`, `picks` and `intraday` show scores and data, never buy/sell calls
- Yahoo's session opens lazily, so one failed handshake no longer stops every command
- Tax: 4% cess, dated advance-tax payments, one classification across `tax in`, `tax export in`,
`tax advance` and `harvest`, sale-date rates in the export, and FIFO-aware `harvest`
- Calculations: put build-up for the underlying, date-aligned correlation, 365-bar crypto
annualisation, prior-session pivots, dollar size cut-offs
- Alerts, simulator and trade safety fixes; `--yes` orders need `max_order_value`
- Scorecards count only the signals that have data; statements in another currency convert
correctly; neutral wording in `technical`, `ipo`, `longterm`, the report and the daemon
- Docs site: a Troubleshooting page, README and docs checked against the binary; changelog
fragments replace edits to `CHANGELOG.md`
**0.8** — portfolio depth, tax season and an easier start
- Portfolio X-Ray (look-through overlap across stocks, funds and ETFs), target allocations with
drift bands and inflow-only rebalancing, and lot-level `harvest` with `--json`/`--csv`
- Advance tax instalments (Section 211/234C); open-interest analytics and build-up; transparent
Piotroski F-score and Altman Z scorecards; an Indian IPO workbench
- Direct `.xlsx` broker imports; `stockwise SYMBOL`, short command names and name aliases;
`--explain` on `verdict`, `technical`, `score` and `options oi`
- Eight more MCP/`ask` tools; a research workbench in the web dashboard; TUI stock inspector
**0.7** — key events timeline, quantified insights and decision engine
- `stockwise events`: NSE corporate filings + RSS news timeline with regex financial extraction,
benchmark-adjusted market reactions and upcoming corporate radar (`-m in` and `-m us`)
- `stockwise insights`: INDmoney-style positive/negative/neutral impact cards across 9 dimensions,
with live NSE XBRL shareholding (FII/DII/promoter/pledges) and US SEC insider & institutional data
- `stockwise verdict`: 6-pillar 0–100 decision engine with entry zone, stop-loss, target price,
risk-reward ratio, multi-horizon guidance and bull vs. bear thesis
**0.6** — AI assistants and more ways to install (first released in 0.7.0)
- `stockwise mcp`: 24 read-only tools, resources and prompts for Claude, Cursor and other AI
assistants (no orders); `stockwise ask` with tool calling; `llms.txt` and an Agent Skill
- Install: PyPI wheels (`pipx`/`uvx`, published once enabled), `.deb`/`.rpm`, a Termux build,
nixpkgs and Termux recipes, a Docker Hub mirror; Linux binaries run on glibc 2.34+
(Ubuntu 22.04, Debian 12, RHEL 9); download stats without telemetry
- Mutual funds and SGBs valued across portfolio views and tax; `MF:` symbols fixed
- Educational AI wording instead of buy/sell verdicts
- Documentation site with a cheatsheet and a comparison with other tools
**0.5** — planning, reporting and more markets
- Planning: capital-gains export (`tax export in|us`), portfolio vs benchmark with attribution,
goal planner (`goal sip|retire|education`)
- Alerts: RSI, moving-average crosses, breakouts, day-change and volume spikes; results calendar
- Markets: UK, Germany, Japan, Hong Kong, Singapore, Canada, Australia from one table; gold and
silver (spot ₹/10 g, ETFs, SGBs, COMEX); G-sec yields and a YTM calculator; NSE/BSE data provider
- Funds & options: AMFI expense ratios, BSE options, options strategy builder with payoff charts
- Simulator: historical replay with the future hidden, challenges, risk-adjusted scores, badges
- Brokers: read-only `trade selftest` + tester kit, `trade sync` of fills and GTT status
- Web dashboard in `stockwise serve`; experimental WASM strategy plugins; MSRV 1.97
**0.4** — the whole 0.3 roadmap
- Markets: crypto and FX (`-m crypto`, `-m fx`), mutual funds via AMFI (`mf search/nav/history/sip/screen`),
fund/ETF overlap, F&O option chains with greeks, IV, PCR and max pain (NSE + US)
- Portfolio: lots with purchase dates, FIFO sales, XIRR, splits/bonuses/dividends, named
portfolios, INR/USD totals, Indian LTCG/STCG with set-off rules
- Brokers: Zerodha, Upstox, Dhan, Fyers (**experimental** — untested against live accounts)
next to Angel One; stop-loss and GTT orders; OS keyring; Groww, Upstox and CAS imports
- Bots & alerts: resilient paper daemon (Ctrl+C square-off, saved state), IST/US exchange clock
with holidays, `sim settle` from intraday bars, `alert watch`, Telegram/Slack/Discord/email/desktop
- Backtesting: Indian/US cost models, next-bar fills, portfolio and walk-forward backtests,
TOML strategy files usable by the intraday bot
- Interfaces: `stockwise tui` dashboard, `stockwise serve` JSON API + Prometheus metrics
- Scripting: `--json` / `--csv` across commands, `config.toml`, man pages
- Data & AI: Alpha Vantage / Massive (Polygon) / Twelve Data providers, opt-in OpenAI /
Anthropic / Gemini, Hindi (`--lang hi`, partial)
- Distribution: Docker image, Nix flake, Homebrew/Scoop/winget/AUR manifests
**0.3** — usability and safety
- `doctor`, shell completions, grouped help, `--json` on core commands
- Typed subcommands with suggestions; real exit codes
- Live orders behind exact instrument matching, preview, and confirmation
- Opt-in local AI; owner-only credential storage; atomic data files
- One-line installer, `cargo binstall`, checksums, Linux ARM64 binaries
**0.2** — first public release
- 57 commands across analysis, screening, bots, trading, and portfolio
- Daemon workers, paper-trading simulator, Angel One live trading
- Broker import (Kite, INDmoney, CSV), Redis cache, braille charts
## The road to 1.0
Who 1.0 is for: a self-directed global investor or developer — across the US, UK, Europe, Canada,
Australia, APAC, and India (including cross-border expats and NRIs) — who is comfortable in a
terminal or an AI assistant, holds stocks, ETFs and funds across brokers and currencies, files
their own taxes, and wants their financial data kept strictly private on their own machine. Four
jobs must be excellent everywhere: **one true multi-currency portfolio**, **local capital-gains tax
done right**, **instant keyless market lookups**, and **asking an AI about your money locally**.
Everything else is secondary until 1.0.
### 0.9 — global-first markets, `stockwise use` switcher, investor profiles, multi-jurisdiction tax & year-end checkup ([milestone](https://github.com/iamsaquib8/stockwise/milestone/2), target: 31 December 2026)
Epics: [#257](https://github.com/iamsaquib8/stockwise/issues/257), [#258](https://github.com/iamsaquib8/stockwise/issues/258), [#259](https://github.com/iamsaquib8/stockwise/issues/259), [#260](https://github.com/iamsaquib8/stockwise/issues/260), [#261](https://github.com/iamsaquib8/stockwise/issues/261), [#262](https://github.com/iamsaquib8/stockwise/issues/262), [#278](https://github.com/iamsaquib8/stockwise/issues/278), [#279](https://github.com/iamsaquib8/stockwise/issues/279), [#280](https://github.com/iamsaquib8/stockwise/issues/280), [#281](https://github.com/iamsaquib8/stockwise/issues/281), [#282](https://github.com/iamsaquib8/stockwise/issues/282).
Year-end (31 Dec US/EU/CA tax-loss harvesting cutoff, December US/Indian estimated/advance tax, and Q1 UK ISA / Indian Jan–Mar tax windows) is when global and Indian investors look for local-first portfolio, tax, and goal tools.
- 📋 **Wave 1 CLI modularisation, one-command installs & self-update** ([#178](https://github.com/iamsaquib8/stockwise/issues/178), [#257](https://github.com/iamsaquib8/stockwise/issues/257), [#398](https://github.com/iamsaquib8/stockwise/issues/398)) — split `src/main.rs` into `cli/` modules ([#178](https://github.com/iamsaquib8/stockwise/issues/178)) for conflict-free parallel contributor PRs, `stockwise update` install-channel-aware self-updater with SHA-256 verification ([#398](https://github.com/iamsaquib8/stockwise/issues/398)), npm and `npx -y stockwise mcp` ([#228](https://github.com/iamsaquib8/stockwise/issues/228)), PyPI
([#169](https://github.com/iamsaquib8/stockwise/issues/169)), Homebrew ([#233](https://github.com/iamsaquib8/stockwise/issues/233)), winget ([#234](https://github.com/iamsaquib8/stockwise/issues/234)), AUR ([#235](https://github.com/iamsaquib8/stockwise/issues/235)), a Scoop bucket ([#236](https://github.com/iamsaquib8/stockwise/issues/236)),
nixpkgs and Termux ([#237](https://github.com/iamsaquib8/stockwise/issues/237)), the Docker Hub mirror ([#271](https://github.com/iamsaquib8/stockwise/issues/271)), and hardened `install.sh` checksum verification ([#130](https://github.com/iamsaquib8/stockwise/issues/130))
- 📋 **Instant market switcher (`stockwise use`), statusbar widget, investor profiles, zero-`-m` resolution & visual demo** — **`stockwise use ` / `use -` / interactive session picker / `--shell` / `--status` prompt badge / `markets world` / `config set` and per-portfolio market binding** ([#388](https://github.com/iamsaquib8/stockwise/issues/388)), **`<5ms` `stockwise statusbar` (`tmux`/`waybar`/`sketchybar`/`starship`) & 1-click Raycast/Cursor/VS Code/Zed MCP setup** ([#408](https://github.com/iamsaquib8/stockwise/issues/408)), `stockwise init` ([#192](https://github.com/iamsaquib8/stockwise/issues/192)) from install to first portfolio, **Investor Profile modes** (`passive`, `active` with `low|med|high` risk, and `ultra` full-telemetry mode) with personal tax-slab & concentration guardrails ([#374](https://github.com/iamsaquib8/stockwise/issues/374)), zero-`-m` cross-market ticker/`EXCHANGE:SYM`/ISIN resolution, aliases and completion ([#54](https://github.com/iamsaquib8/stockwise/issues/54), [#388](https://github.com/iamsaquib8/stockwise/issues/388)), and the animated VHS terminal demo in the README ([#23](https://github.com/iamsaquib8/stockwise/issues/23))
- 📋 **Multi-currency `base_currency` portfolio, total `networth`, 12M dividend/income calendar, Index Twin (`pme`), RSU/ESPP, encrypted backups, family `continuity` & global broker imports** ([#248](https://github.com/iamsaquib8/stockwise/issues/248), [#389](https://github.com/iamsaquib8/stockwise/issues/389), [#396](https://github.com/iamsaquib8/stockwise/issues/396), [#397](https://github.com/iamsaquib8/stockwise/issues/397), [#403](https://github.com/iamsaquib8/stockwise/issues/403), [#404](https://github.com/iamsaquib8/stockwise/issues/404), [#407](https://github.com/iamsaquib8/stockwise/issues/407)) — trade-date historical FX cost basis (auto-backfilled via ECB/Frankfurter [#341](https://github.com/iamsaquib8/stockwise/issues/341)), `Local Return + FX Impact + Net Dividends` attribution, **`TTWROR` (time-weighted) alongside `XIRR`**, **Index Twin / Public Market Equivalent (`portfolio pme` vs `SPY`/`VWCE`/`NIFTYBEES`)** ([#404](https://github.com/iamsaquib8/stockwise/issues/404)), **12-month dividend/coupon calendar (`stockwise income`) with Yield-on-Cost (`YoC`) & FCF payout safety** ([#403](https://github.com/iamsaquib8/stockwise/issues/403)), **global & Indian total `networth` (`401k`/`IRA`/`HSA`, `ISA`/`SIPP`, `RRSP`/`TFSA`, `EPF`/`PPF`/`NPS`, FDs/CDs/T-Bills/SGBs, cash & liabilities)** ([#248](https://github.com/iamsaquib8/stockwise/issues/248)), **RSU/ESPP equity-compensation importers (`E*TRADE`, `Shareworks`, `Schwab EAC`, `Fidelity NetBenefits`), `sell-to-cover` basis & unvested grant runway** ([#396](https://github.com/iamsaquib8/stockwise/issues/396)), **passphrase-encrypted portable backups (`stockwise backup`), `beancount`/`hledger` export** ([#397](https://github.com/iamsaquib8/stockwise/issues/397)), **family emergency `continuity` dossier** ([#407](https://github.com/iamsaquib8/stockwise/issues/407)), and 1-command importers for **IBKR (Flex/CSV), Schwab, Fidelity, Robinhood, Vanguard, Trading 212, DEGIRO, Scalable Capital, Wealthsimple, Portfolio Performance (`.xml`/CSV) and Sharesight** alongside Zerodha, Groww, Upstox and PDF CAS ([#375](https://github.com/iamsaquib8/stockwise/issues/375))
- 📋 **Universal `--json` & `--csv` serialization parity, local AI assistant (`stockwise ask`), agentic `ask --auto` & MCP** — 100% `--json` and `--csv` coverage across all data/tabular commands with unified schemas and JSON errors ([#381](https://github.com/iamsaquib8/stockwise/issues/381), [#223](https://github.com/iamsaquib8/stockwise/issues/223), [#180](https://github.com/iamsaquib8/stockwise/issues/180)), `stockwise mcp setup ` ([#193](https://github.com/iamsaquib8/stockwise/issues/193)), the official MCP
registry ([#194](https://github.com/iamsaquib8/stockwise/issues/194)), MCP directories ([#242](https://github.com/iamsaquib8/stockwise/issues/242)), expanded MCP tools ([#53](https://github.com/iamsaquib8/stockwise/issues/53)), **embedded local LLM inference via pure-Rust `candle`/GGUF (`stockwise ask --local`)** ([#401](https://github.com/iamsaquib8/stockwise/issues/401)), and **profile-aware `stockwise ask --auto` / `-i` REPL with safe workspace actions (`stockwise undo`) and built-in playbooks (`checkup`, `tax-season`, `earnings`)** ([#378](https://github.com/iamsaquib8/stockwise/issues/378))
- 📋 **Global & Indian tax engines, tax-free wrappers, audit packs & global corporate actions** ([#259](https://github.com/iamsaquib8/stockwise/issues/259), [#390](https://github.com/iamsaquib8/stockwise/issues/390), [#399](https://github.com/iamsaquib8/stockwise/issues/399), [#405](https://github.com/iamsaquib8/stockwise/issues/405)) — pluggable lot-accounting engines (**US & AU `HIFO`/`LIFO`/`MinTax` SpecID**, **UK HMRC Section 104 Pool + 30-day Bed & Breakfast** with SA108, **Canada `ACB` + Superficial Loss + T1135**, **Germany *Abgeltungsteuer* + 30% ETF *Teilfreistellung***, and **SG/HK 0% CGT + WHT**), US 30-day wash sales, NIIT, ESPP Sec. 423 qualifying dispositions ([#396](https://github.com/iamsaquib8/stockwise/issues/396)), IRS quarterly estimated tax & Form 8949 ([#97](https://github.com/iamsaquib8/stockwise/issues/97)), **tax-advantaged wrappers (`Roth IRA`, `401k`, `ISA`, `SIPP`, `TFSA`, `RRSP`, `NISA`)**, **penny-exact event-boundary rounding & reproducible `tax export --audit-pack` workpapers** ([#399](https://github.com/iamsaquib8/stockwise/issues/399)), **global corporate-action auto-detector, Return of Capital (`RoC`) basis reduction, reverse splits, fractional merger cash ([#317](https://github.com/iamsaquib8/stockwise/issues/317)), and IRS Form 8937 / HMRC / SEBI spin-off ratio tables** ([#405](https://github.com/iamsaquib8/stockwise/issues/405)), plus full Indian ITR readiness (non-equity ETF categories [#318](https://github.com/iamsaquib8/stockwise/issues/318), Jan 2018 grandfathering [#247](https://github.com/iamsaquib8/stockwise/issues/247), dividend/interest TDS [#246](https://github.com/iamsaquib8/stockwise/issues/246), lot edit/undo [#94](https://github.com/iamsaquib8/stockwise/issues/94), bonus lots [#95](https://github.com/iamsaquib8/stockwise/issues/95), demergers/renames [#96](https://github.com/iamsaquib8/stockwise/issues/96), `tax reconcile` [#203](https://github.com/iamsaquib8/stockwise/issues/203), and **`portfolio sell --preview` with LTCG countdown** [#375](https://github.com/iamsaquib8/stockwise/issues/375))
- 📋 **Global & local post-tax goal & FIRE planning** ([#356](https://github.com/iamsaquib8/stockwise/issues/356), [#392](https://github.com/iamsaquib8/stockwise/issues/392)) — horizon-matched return & local sovereign yield baselines (`US Treasury`, `ECB Bund`, `UK Gilt`, `JGB`, `RBI G-Sec`) with pre-goal glide paths, per-instalment local tax & wrapper math (`401k`/`Roth`/`ISA`/`TFSA`/`NISA`), `P10/P50/P90` shortfall odds, sub-year horizons (`--months`, `--by`), saved goals (`goals.json`) linked to `portfolio`, **Global FIRE modes (`4%` SWR, Coast-FIRE, Guyton-Klinger guardrails)**, retirement Year-1 crash checks, and `goal swp|emergency|prepay`
- 📋 **19+ global stock markets, SQLite WAL cache, background worker & universal cap tiers** ([#267](https://github.com/iamsaquib8/stockwise/issues/267), [#357](https://github.com/iamsaquib8/stockwise/issues/357), [#377](https://github.com/iamsaquib8/stockwise/issues/377)) — expand to **South Korea, Taiwan, France, Netherlands, Switzerland, China and Brazil** ([#267](https://github.com/iamsaquib8/stockwise/issues/267)), `DXY` & global FX pairs ([#18](https://github.com/iamsaquib8/stockwise/issues/18)), zero-setup `cache.db` (L2 HTTP cache replacing Redis, indexed `market_snapshots`, incremental daily `ohlcv_bars`, quarterly results, and `tasks` queue), market-aware **background live-update worker & OS service** (`stockwise worker` + live push for `tui`/`serve`), capability routing, shared NSE session ([#159](https://github.com/iamsaquib8/stockwise/issues/159)), curated global index constituents ([#337](https://github.com/iamsaquib8/stockwise/issues/337)), provenance footer ([#338](https://github.com/iamsaquib8/stockwise/issues/338)), SEC EDGAR ([#339](https://github.com/iamsaquib8/stockwise/issues/339)), Stooq ([#340](https://github.com/iamsaquib8/stockwise/issues/340)), Frankfurter FX ([#341](https://github.com/iamsaquib8/stockwise/issues/341)), `midcap150`/`smallcap250`/`nifty500` ([#16](https://github.com/iamsaquib8/stockwise/issues/16)), and FX-adjusted / SEBI-rank `Mega / Large / Mid / Small / Micro` badges (`--cap`)
- 📋 **Global sovereign yield curves, SEC Form 4/13F, S-1 IPOs, American options, UCITS ETFs & year-end checkup** ([#391](https://github.com/iamsaquib8/stockwise/issues/391), [#19](https://github.com/iamsaquib8/stockwise/issues/19), [#361](https://github.com/iamsaquib8/stockwise/issues/361), [#376](https://github.com/iamsaquib8/stockwise/issues/376), [#392](https://github.com/iamsaquib8/stockwise/issues/392)) — US Treasury (`1M`–`30Y` + TIPS + `2s10s`), Bund, Gilt & JGB curves with `Act/Act` & `Act/360` bond math, SEC EDGAR **Form 4 insider** & **13F institutional** flows, **US/Global S-1 IPO & lock-up board**, **American equity option pricing & `GEX`**, **UCITS (`Acc`/`Dist`) & global ETF intelligence**, 8–12Q `stockwise results ` collation, plus **`portfolio checkup` (holistic red-flag, global TER bleed, employer-RSU concentration, non-US investor US-domicile 30% WHT / \$60k estate-tax vs UCITS check, Regular-vs-Direct MF bleed & asset-location scan) and `portfolio wrap [--redact]`**
- 📋 **Global (`Alpaca`, `IBKR`) & Indian multi-broker execution, statutory order-cost preview, streaming TUI & Tailscale-ready Web PWA** ([#382](https://github.com/iamsaquib8/stockwise/issues/382), [#383](https://github.com/iamsaquib8/stockwise/issues/383), [#392](https://github.com/iamsaquib8/stockwise/issues/392), [#400](https://github.com/iamsaquib8/stockwise/issues/400)) — **Alpaca Markets** (US equities/crypto paper + live + WebSocket stream) and **Interactive Brokers (`IBKR`)** alongside Zerodha, Angel One, Upstox, Dhan and Fyers, keyring TOTP / OAuth renewal and cached instrument lists ([#31](https://github.com/iamsaquib8/stockwise/issues/31)), full market-specific fee & rule breakdown (**US SEC/FINRA + `PDT` counter, UK `0.5%` SDRT, HK board lots, and Indian STT/GST** + slippage & breakeven) in `trade` previews, position sizing in `stoploss` ([#22](https://github.com/iamsaquib8/stockwise/issues/22)), event-driven streaming TUI (`m` market hotkey, custom `[tui]` layouts, live option Greeks Δ, Γ, Θ, ν), and **`stockwise serve` web dashboard parity with live SSE push & private mobile-friendly PWA** ([#400](https://github.com/iamsaquib8/stockwise/issues/400))
- 📋 **Institutional Backtest Engine v2, Shadow Paper-Trading Bridge (`--deploy-paper`), On-Demand ML Models (`stockwise model`) & Strategy Base-Rate Atlas** ([#383](https://github.com/iamsaquib8/stockwise/issues/383), [#394](https://github.com/iamsaquib8/stockwise/issues/394), [#395](https://github.com/iamsaquib8/stockwise/issues/395), [#401](https://github.com/iamsaquib8/stockwise/issues/401), [#406](https://github.com/iamsaquib8/stockwise/issues/406)) — multi-timeframe (`5m`–`1wk`), shorting & fractional shares, ATR/vol-target/Kelly sizing, Chandelier/trailing stops, sovereign `Rf` cash sweep & `--after-tax` tearsheets, cross-asset benchmark/VIX regime filters, `filed_at` point-in-time fundamental rules, Purged/Embargoed walk-forward (`CPCV`) + **Deflated Sharpe (`DSR`)**, Monte Carlo drawdown distributions, `backtest compare` & `--universe` runs over `stockwise data sync`, **1-command shadow paper-trading deployment (`backtest --deploy-paper`) & live forward-Sharpe drift monitor** ([#406](https://github.com/iamsaquib8/stockwise/issues/406)), empirical survivorship-free **Strategy Base-Rate Atlas**, and **on-demand local models** (`stockwise model pull`, built-in pure-Rust `GARCH(1,1)` + `HMM` regime, `chronos-bolt` quantile cones, `finbert` filing scorer, and `onnx:`/`lgbm:` BYO models with mandatory **`training_cutoff` leakage guards**)
- 📋 **Global & regional soft launch** — how-to, local-first privacy and year-end portfolio-checkup posts across `r/commandline`, `r/Bogleheads`, `r/UKPersonalFinance`, `r/Finanzen`, `r/PersonalFinanceCanada`, `r/algotrading`, `r/IndiaInvestments`, `r/developersIndia` and TradingQnA ([#200](https://github.com/iamsaquib8/stockwise/issues/200))
Done when: `src/main.rs` is split into `cli/` modules ([#178](https://github.com/iamsaquib8/stockwise/issues/178)), every channel in "Coming soon" on the install page is live with `stockwise update` ([#398](https://github.com/iamsaquib8/stockwise/issues/398)), every data command emits `--json`/`--csv`, `stockwise init` + `stockwise use` + `profile` + `portfolio checkup` + `backtest` v2 ([#394](https://github.com/iamsaquib8/stockwise/issues/394)) + `stockwise model` ([#395](https://github.com/iamsaquib8/stockwise/issues/395), including `training_cutoff` OOS leakage guards) work end-to-end across global and Indian portfolios, and hand-checked reference portfolios pass penny-exact `--audit-pack` ([#399](https://github.com/iamsaquib8/stockwise/issues/399)) tax exports across US (Form 8949 / `HIFO` / Wash Sale / RSU & ESPP [#396](https://github.com/iamsaquib8/stockwise/issues/396)), UK (Section 104 / SA108), Canada (`ACB`), and India (Schedule 112A).
### 0.10 — simplify and stabilise: the 1.0 release candidate ([milestone](https://github.com/iamsaquib8/stockwise/milestone/4), target: January 2027)
Epics: [#262](https://github.com/iamsaquib8/stockwise/issues/262), [#258](https://github.com/iamsaquib8/stockwise/issues/258), [#260](https://github.com/iamsaquib8/stockwise/issues/260), [#261](https://github.com/iamsaquib8/stockwise/issues/261), [#278](https://github.com/iamsaquib8/stockwise/issues/278), [#279](https://github.com/iamsaquib8/stockwise/issues/279).
Feature freeze. Make the CLI small, the data safe and the promise written.
- 📋 **A smaller CLI** — the simplification plan ([#205](https://github.com/iamsaquib8/stockwise/issues/205)): about 25 commands in the short help,
old names kept as hidden aliases for one release; help that fits a terminal ([#150](https://github.com/iamsaquib8/stockwise/issues/150)) and a README written for one user and
four jobs ([#191](https://github.com/iamsaquib8/stockwise/issues/191))
- 📋 **Safe data** ([#258](https://github.com/iamsaquib8/stockwise/issues/258)) — versioned files with migrations and
backups ([#256](https://github.com/iamsaquib8/stockwise/issues/256)) and named demat accounts ([#315](https://github.com/iamsaquib8/stockwise/issues/315))
- 📋 **Neutral, accurate research** ([#260](https://github.com/iamsaquib8/stockwise/issues/260)) and **AI answers checked by an eval set** ([#255](https://github.com/iamsaquib8/stockwise/issues/255))
- 📋 **A written promise** — the 1.0 stability policy ([#195](https://github.com/iamsaquib8/stockwise/issues/195)) and frozen 1.0 `--json` schemas
([#223](https://github.com/iamsaquib8/stockwise/issues/223), [#180](https://github.com/iamsaquib8/stockwise/issues/180))
- 📋 **Quality gates** — required CI ([#171](https://github.com/iamsaquib8/stockwise/issues/171)), cargo-deny ([#172](https://github.com/iamsaquib8/stockwise/issues/172)), hardened workflows
([#173](https://github.com/iamsaquib8/stockwise/issues/173)), CLI snapshot tests ([#181](https://github.com/iamsaquib8/stockwise/issues/181)), parser fuzzing ([#182](https://github.com/iamsaquib8/stockwise/issues/182)), a weekly live-data canary
([#245](https://github.com/iamsaquib8/stockwise/issues/245))
Done when: the release candidate runs a full user journey (install, init, import, portfolio, tax,
MCP) on macOS, Linux and Windows without a known bug in the four jobs.
### 1.0 — public launch ([milestone](https://github.com/iamsaquib8/stockwise/milestone/3), target: February 2027)
Epic: [#281](https://github.com/iamsaquib8/stockwise/issues/281).
Launch into Q1 global & regional tax/investing season (US Form 8949 / IRA window, UK 5 April ISA/CGT deadline, Canadian RRSP/ACB filing, and India's 31 March harvesting & ITR window), when the four jobs matter most.
- 📋 **Show it** — "Open in Codespaces" ([#240](https://github.com/iamsaquib8/stockwise/issues/240)) and three
task guides for the core jobs ([#201](https://github.com/iamsaquib8/stockwise/issues/201))
- 📋 **Launch** — Show HN ([#199](https://github.com/iamsaquib8/stockwise/issues/199)), Reddit and TradingQnA posts timed to the harvest window
([#200](https://github.com/iamsaquib8/stockwise/issues/200)), Product Hunt last ([#272](https://github.com/iamsaquib8/stockwise/issues/272))
- 📋 **Measure** — a launch log and before/after numbers ([#204](https://github.com/iamsaquib8/stockwise/issues/204)); targets for the 90 days after
launch: 150 stars, 150 installs a week, 500 unique visitors per 14 days
Done when: 1.0 is tagged, the stability page is published, and the launch posts are out.
## 💡 After 1.0
Planned, but only once the four jobs are solid. Ordered roughly by demand. Items marked "idea" live in
[Discussions](https://github.com/iamsaquib8/stockwise/discussions/categories/ideas): upvote the ones you want. Most of these live in
[#282](https://github.com/iamsaquib8/stockwise/issues/282) (more markets and deeper research) and [#280](https://github.com/iamsaquib8/stockwise/issues/280) (dashboards, alerts and practice).
**Your money**
- 📋 **Indian ITR toolkit, rest** ([#264](https://github.com/iamsaquib8/stockwise/issues/264)) — Schedule FA for foreign holdings, Form 67 for US dividend tax,
debt funds under Sec. 50AA, crypto under Sec. 115BBH, and the Income-tax Act 2025's new forms
- 📋 **Read-only broker sync** of holdings and trades instead of CSV imports ([#254](https://github.com/iamsaquib8/stockwise/issues/254)), and quotes
from the user's own broker API ([#253](https://github.com/iamsaquib8/stockwise/issues/253))
**Everyday use**
- 📋 **Two-way Telegram bot and scheduled digests** ([#265](https://github.com/iamsaquib8/stockwise/issues/265))
- 📋 **More install and launcher options** — mise/aqua ([idea #285](https://github.com/iamsaquib8/stockwise/discussions/285)), a signed APT/RPM repository
([idea #286](https://github.com/iamsaquib8/stockwise/discussions/286)), a GitHub Action ([idea #287](https://github.com/iamsaquib8/stockwise/discussions/287)), a Raycast extension ([idea #288](https://github.com/iamsaquib8/stockwise/discussions/288))
**Research & market discovery**
- 📋 **Multi-source data router, snapshot cache & universal cap tiers** ([#357](https://github.com/iamsaquib8/stockwise/issues/357)) — capability routing and field merging across NSE/BSE bulk & XBRL feeds, SEC EDGAR ([#339](https://github.com/iamsaquib8/stockwise/issues/339)), AMFI, Stooq ([#340](https://github.com/iamsaquib8/stockwise/issues/340)), Frankfurter ([#341](https://github.com/iamsaquib8/stockwise/issues/341)), Finnhub/FMP and connected brokers ([#253](https://github.com/iamsaquib8/stockwise/issues/253)), a fast 500–2,000 stock snapshot cache, and SEBI-rank / FX-adjusted `Mega / Large / Mid / Small / Micro` badges (`--cap`) across every market
- 📋 **Cross-market movers, activity boards, intraday RVOL curves & hot verdicts** ([#358](https://github.com/iamsaquib8/stockwise/issues/358)) — gainers/losers across all caps and timeframes, Most Traded by value & volume, elapsed-time-normalized Volume Shockers with braille cumulative curves, Most Bought (delivery %, bulk/block deals, institutional/Form 4), In-News buzz, `markets hot` with inline 6-pillar `verdict` scores, Top Intraday, and `markets index` (`nifty50`, `sp500`, `nasdaq100`)
- 📋 **Sector sub-industries & cross-market thematic explorers** ([#359](https://github.com/iamsaquib8/stockwise/issues/359)) — 3-level Sector → Sub-Industry → Constituents drilldown across all exchanges (including UK/DE/SG), neutral RRG rotation quadrants, and curated global/local themes (`ai`, `semis`, `battery`/`ev`, `minerals`, `defence`, `clean-energy`, `datacenter`, `ems`, `cyber`, `biotech`, `fintech`, `psu`)
- 📋 **Screener queries, trading setups, quant rulebooks & global ETF explorer** ([#266](https://github.com/iamsaquib8/stockwise/issues/266), [#360](https://github.com/iamsaquib8/stockwise/issues/360)) — `stockwise screen "roce > 20 and pe < 25"` across NIFTY Midcap 150 / Smallcap 250 / 500 ([#16](https://github.com/iamsaquib8/stockwise/issues/16)) and global indices, technical setups (`volume-shocker`, `52w-breakout`, `stage2-uptrend`, `bollinger-squeeze`, `pocket-pivot`), published quant frameworks (`quality-moat`, `garp`, `magic-formula`, `canslim`, `coffee-can`, `piotroski-strong`), `compounders`, `analyst-consensus`, `sip-consistent`, `dividend-leaders`, and a dedicated Indian/US/Global **ETF explorer** (`etf`)
- 📋 **Quarterly & annual financial results collation** ([#361](https://github.com/iamsaquib8/stockwise/issues/361)) — 8–12+ quarter and 5–10 FY tables per symbol (`Revenue/NII`, `EBITDA/OPM%`, `PAT`, `EPS`, bank `GNPA/NNPA`, `YoY%`/`QoQ%`, `CFO/PAT`, beat/miss + reaction) merged from NSE XBRL, BSE, SEC EDGAR ([#339](https://github.com/iamsaquib8/stockwise/issues/339)) and global providers, plus market/portfolio earnings-season scoreboards
- 📋 **Market-wide F&O scanners & mutual fund rolling/holdings discovery** ([#362](https://github.com/iamsaquib8/stockwise/issues/362), [idea #291](https://github.com/iamsaquib8/stockwise/discussions/291)) — `options scan` (`buildup`, `iv` rank/percentile, `uoa`, `pcr`, `rollover`) and NSE F&O ban/MWPL list (`options ban`), plus MF category heatmaps, 3Y/5Y rolling return consistency, Sharpe/Sortino/downside capture, `mf compare`, direct-vs-regular cost, US/global funds, and monthly MF holdings activity (`mf holders`, `mf activity`)
- 📋 **Market mood** ([idea #290](https://github.com/iamsaquib8/stockwise/discussions/290)), **`--explain` everywhere** — `risk`, option greeks and `tax` ([#52](https://github.com/iamsaquib8/stockwise/issues/52)), and **symbol discovery** — suggestions in every command, a picker and symbol completion ([#54](https://github.com/iamsaquib8/stockwise/issues/54))
**Markets**
- 📋 **Energy and industrial metals** — platinum, palladium and copper futures ([#20](https://github.com/iamsaquib8/stockwise/issues/20)), plus global & Indian REIT/InvIT lists
**Practice and trading**
- 📋 **Daily replay challenge** with a shareable card ([idea #289](https://github.com/iamsaquib8/stockwise/discussions/289))
- 📋 **Virtual F&O trading** in the simulator, with lot sizes and margin estimates ([idea #292](https://github.com/iamsaquib8/stockwise/discussions/292))
- 🛠️ **Broker integrations verified against live accounts** ([#269](https://github.com/iamsaquib8/stockwise/issues/269), help wanted) — `trade selftest` and the
[verification criteria](https://iamsaquib8.github.io/stockwise/brokers.html#help-verify-a-broker) are ready; we need 3 reports per
broker plus one small live order a tester places themselves
- 📋 **SEBI retail-algo rules** in `trade` — order-rate limits, algo tagging and static-IP setup
([#132](https://github.com/iamsaquib8/stockwise/issues/132))
- 📋 **More brokers through OpenAlgo** — one REST adapter to the brokers it supports ([#270](https://github.com/iamsaquib8/stockwise/issues/270))
**Exploring**
- 💡 Options strategy backtests on end-of-day chains ([idea #293](https://github.com/iamsaquib8/stockwise/discussions/293))
- 💡 Trade journal and two-leg (OCO) GTTs; position sizing in `stoploss` ([#22](https://github.com/iamsaquib8/stockwise/issues/22))
- 💡 Ownership signals — shareholding and pledge changes, alerts on phrases in filings ([idea #294](https://github.com/iamsaquib8/stockwise/discussions/294))
- 💡 An assistant pane in the TUI and web dashboard ([idea #295](https://github.com/iamsaquib8/stockwise/discussions/295))
- 💡 Gulf exchanges; MCX futures and live G-sec prices via optional broker feeds ([idea #296](https://github.com/iamsaquib8/stockwise/discussions/296))
- 💡 WASM plugins for importers and data providers — kept experimental until someone uses them ([idea #297](https://github.com/iamsaquib8/stockwise/discussions/297))
### Investor intelligence, guided flows and portfolio math
Today StockWise answers dozens of separate questions (`portfolio`, `xray`, `rebalance`, `harvest`,
`tax`, `goal`, `verdict`, `score`, `insights`, `events`), but leaves the investor to stitch them
together. After 1.0's core data and CLI foundation ([#205](https://github.com/iamsaquib8/stockwise/issues/205),
[#258](https://github.com/iamsaquib8/stockwise/issues/258)) are locked in, the next frontier is
connecting those engines around **your actual portfolio, cashflows and goals** — while keeping
every output transparent, deterministic, local-first, and clear of buy/sell calls or stock tips
([#260](https://github.com/iamsaquib8/stockwise/issues/260), [DISCLAIMER.md](https://iamsaquib8.github.io/stockwise/disclaimer.html)).
**Guided flows (by investor moment, not by command)**
- 📋 **First run, `stockwise use` switcher, investor profile & smart symbols** ([#192](https://github.com/iamsaquib8/stockwise/issues/192), [#374](https://github.com/iamsaquib8/stockwise/issues/374), [#388](https://github.com/iamsaquib8/stockwise/issues/388), [#54](https://github.com/iamsaquib8/stockwise/issues/54)) — `stockwise init`, `stockwise use ` / `use -` / per-portfolio market & `base_currency` binding, `passive | active:low/med/high | ultra` profile modes with personal tax-slab & concentration guardrails, zero-`-m` cross-market ticker/ISIN resolution, and one-line contextual "next check" hints when a command spots high overlap, fee drag or unused tax headroom
- 💡 **Monthly payday / DCA / SIP deploy flow** ([idea #364](https://github.com/iamsaquib8/stockwise/discussions/364)) (`portfolio deploy `) — extend inflow rebalancing (`rebalance --inflow`) to route fresh monthly cash across underweight holdings, wrappers (`ISA`/`Roth IRA`/`TFSA`) and underfunded goals while respecting X-Ray single-stock caps, whole/fractional share rules and fund minimums, with zero sell orders
- 💡 **Pre-buy fit & what-if sandbox** ([idea #369](https://github.com/iamsaquib8/stockwise/discussions/369)) (`portfolio fit [AMOUNT]`) — before adding a stock, ETF or fund, show the exact before → after shift in look-through overlap (`xray`), sector, currency and cap weights, portfolio beta, correlation and historical drawdown
- 📋 **Pre-sell lot, tax & exit-load check** ([#375](https://github.com/iamsaquib8/stockwise/issues/375), [#390](https://github.com/iamsaquib8/stockwise/issues/390), [idea #366](https://github.com/iamsaquib8/stockwise/discussions/366)) (`portfolio sell --preview`) — compare **`FIFO` vs `HIFO` vs `MinTax`** (US/AU), **Section 104 / Bed & Breakfast** (UK), **`ACB`** (CA) or **per-demat FIFO** (IN/DE), exact short- vs long-term tax, exit-load windows, and a **"wait N days" countdown** when a lot is close to crossing the long-term threshold or triggering a 30-day wash/superficial loss
- 💡 **March / year-end checklist** (`tax checklist`) — one guided run combining unused ₹1.25L Section 112A LTCG step-up, FIFO-aware loss harvesting (`harvest`), 8-year carry-forward loss expiry warnings, Section 234B/234C advance-tax status, unapplied corporate actions ([#95](https://github.com/iamsaquib8/stockwise/issues/95), [#96](https://github.com/iamsaquib8/stockwise/issues/96)) and broker P&L reconciliation ([#203](https://github.com/iamsaquib8/stockwise/issues/203))
- 💡 **Calm "only what changed" digest** (building on [#118](https://github.com/iamsaquib8/stockwise/issues/118), [#121](https://github.com/iamsaquib8/stockwise/issues/121) and [#265](https://github.com/iamsaquib8/stockwise/issues/265)) — post-market or Sunday briefing that surfaces only material deltas: new red flags, drift band breaches, lots turning long-term, upcoming earnings/ex-dates and goal progress
**Overall portfolio checkup & portfolio-aware analysis**
- 📋 **Holistic portfolio checkup & year-end wrap** ([#376](https://github.com/iamsaquib8/stockwise/issues/376), [#392](https://github.com/iamsaquib8/stockwise/issues/392), [idea #365](https://github.com/iamsaquib8/stockwise/discussions/365)) (`portfolio checkup` / `portfolio wrap [--redact]`) — a single, prioritized diagnostic across six lenses (*Concentration & Overlap*, *Fundamental Quality*, *Valuation Stretch*, *Downside Resilience*, *Fee, Domicile & Tax Drag*, *Goal Alignment*), ranking factual observations by `base_currency` impact (including global ETF TER bleed, non-US investor 30% US WHT / \$60k estate exposure vs UCITS, asset-location drag, and Indian Regular-vs-Direct MF bleed) alongside a shareable year-in-review scorecard
- 💡 **Portfolio-aware stock & fund lens** (`--for-portfolio` on `verdict`, `deep` and `mf`) — evaluate any candidate *relative to what you already hold*: marginal contribution to portfolio risk (MCTR), correlation with your top holdings, added X-Ray overlap, and whether it dilutes or deepens existing sector and factor tilts
- 📋 **Index Twin / Public Market Equivalent** ([#404](https://github.com/iamsaquib8/stockwise/issues/404), [idea #370](https://github.com/iamsaquib8/stockwise/discussions/370)) (`portfolio pme`) — mirror every historical buy, sell and dividend on its exact date into an index (`SPY`, `VOO`, `VWCE`, `NIFTYBEES`, `NIFTY500`) or a custom blended asset benchmark (e.g. 60% equity / 30% bond / 10% gold) to measure exact `base_currency` value added or lost, Direct Alpha, and Kaplan-Schoar PME vs passive indexing
- 💡 **Household / family view** (`--household`, extending [#116](https://github.com/iamsaquib8/stockwise/issues/116), [#248](https://github.com/iamsaquib8/stockwise/issues/248), [#315](https://github.com/iamsaquib8/stockwise/issues/315)) — aggregate named portfolios across spouse, parents or HUF for combined asset allocation, emergency-fund runway, and per-PAN ₹1.25L LTCG exemption headroom
**Early red flags & forensic risk radar**
- 📋 **Portfolio-wide red-flag scan** ([#376](https://github.com/iamsaquib8/stockwise/issues/376), [idea #371](https://github.com/iamsaquib8/stockwise/discussions/371)) (`portfolio checkup` / `redflags`) — scan every held and watchlisted stock and mutual fund in one pass, ranking warnings by `position value × severity` so risks in core positions surface before small tracking bets
- 💡 **Forensic accounting & cash-flow checks** (extending `score` and `insights`) — **Beneish M-Score** (receivables bloat, gross-margin slip, asset quality, accruals), **3-year CFO-to-PAT cash conversion** (`< 0.7` flags profits not turning into cash), free-cash-flow burn, rising working-capital days, and interest-coverage (`ICR < 2x`) / short-term debt stress
- 💡 **Governance, smart-money & filing signals** (expanding [idea #294](https://github.com/iamsaquib8/stockwise/discussions/294)) — promoter holding drops, **promoter pledge levels and QoQ spikes**, simultaneous FII + DII exits, US Form 4 cluster insider selling ([#339](https://github.com/iamsaquib8/stockwise/issues/339)), and corporate-filing flags (auditor or CFO resignation, qualified opinion, credit-rating downgrade, related-party transactions, delayed results)
- 📋 **Mutual-fund silent bleed & drift** ([#375](https://github.com/iamsaquib8/stockwise/issues/375), expanding [idea #291](https://github.com/iamsaquib8/stockwise/discussions/291)) — detect **Regular vs Direct plan commission drag** in CAS imports (annual ₹ bleed, 10-year compounded cost, and which units are past exit load and 12-month LTCG for switching), TER hikes, category style drift, closet indexing (high R² with active TER) and small-cap AUM bloat
- 💡 **Thesis journal & drift guardrails** — store optional buy-thesis notes and numeric guardrails (`min_roe`, `max_pe`, `max_weight`, review date) per holding in local JSON, flagging automatically when reported numbers cross your own rules
**True diversification, correlation & factor math**
- 💡 **Effective Number of Bets** — Herfindahl-Hirschman Index (`HHI`) and `1 / HHI` on look-through X-Ray weights, plus covariance PCA **independent return factors** (*"18 stocks and 4 funds, behaving like 4.2 independent bets"*)
- 💡 **Tail / crash correlation vs calm days** (upgrading `pcorr`) — separate correlation matrices and upside/downside capture strictly on market-stress days (index down >1% or >2%), catching holdings that diversify in calm weeks but fall together in sell-offs
- 💡 **Macro & factor sensitivity** (`portfolio factors`) — rolling regression betas to Market, Size (Small/Mid vs Large), Interest Rates (India/US 10Y yields, [#19](https://github.com/iamsaquib8/stockwise/issues/19)), USD/INR ([#18](https://github.com/iamsaquib8/stockwise/issues/18) — exporters vs import-heavy cyclicals) and Gold/Crude ([#20](https://github.com/iamsaquib8/stockwise/issues/20))
- 💡 **Analytical reference weights & diversifier scan** — Minimum-Variance, Equal-Risk-Contribution (Risk Parity) and Maximum-Diversification math next to your target weights, plus screening universes ([#16](https://github.com/iamsaquib8/stockwise/issues/16), [#266](https://github.com/iamsaquib8/stockwise/issues/266)) for low-correlation candidates (`corr < 0.3` against your portfolio)
**Deep investor math & personal-finance calculators**
- 💡 **Real, post-tax, post-fee returns** — compute **post-tax, post-TER, inflation-adjusted real XIRR** alongside nominal XIRR so investors see true purchasing-power compounding
- 💡 **Reverse DCF & valuation bands** ([idea #368](https://github.com/iamsaquib8/stockwise/discussions/368)) — solve for the **market-implied 10-year FCF/EPS growth rate** baked into today's price at a chosen discount rate (*"At ₹3,900, the market prices in 13.4% annual growth"*), paired with a 2-stage DCF sensitivity grid and 5Y/10Y historical P/E and EV/EBITDA percentile bands
- 💡 **Tail risk & position-sizing math** (expanding [#22](https://github.com/iamsaquib8/stockwise/issues/22)) — Historical and Cornish-Fisher **CVaR / Expected Shortfall (95%/99%)**, Ulcer Index (drawdown depth × duration), ATR volatility-parity sizing, fractional Kelly bounds, and **portfolio heat** (% of capital lost if every stop-loss triggers at once)
- 💡 **Life-decision math** ([idea #372](https://github.com/iamsaquib8/stockwise/discussions/372)) (`goal` / `calc`) — **loan prepayment vs SIP** (effective post-tax interest saved vs post-LTCG SIP return distribution), **SWP vs FD** post-tax retirement cashflow math, **Regular-to-Direct switch breakeven** (tax paid today vs TER saved over N years), and **emergency-fund / Coast-FIRE / FI crossover** calculators
**Projections, goal extensions & crisis stress tests**
- 💡 **Net-worth-wide & Coast-FIRE goal extensions** (building on `0.9`'s `goal` overhaul in [#356](https://github.com/iamsaquib8/stockwise/issues/356)) — connect saved goals in `goals.json` to full household net worth (`PPF`, `EPF`, `NPS`, `FDs` in [#248](https://github.com/iamsaquib8/stockwise/issues/248)), Coast-FIRE / Financial-Independence crossover dates, and automatic monthly drift-to-goal alerts
- 💡 **Fat-tailed Monte Carlo & sequence-of-returns stress** (building on [#37](https://github.com/iamsaquib8/stockwise/issues/37)) — block-bootstrapped simulations from historical Indian and US return and inflation series (capturing fat tails and flat decades), plus **retirement SWP sequence-of-returns tests** (*"What if a 2008-style crash hits in Year 1 of retirement?"*) and dynamic withdrawal guardrails
- 💡 **Historical crisis replay on *your* portfolio** ([idea #367](https://github.com/iamsaquib8/stockwise/discussions/367)) (`portfolio stress`) — run 2008 GFC, 2013 taper tantrum, March 2020 COVID, 2022 rate-hike drawdown or custom multi-factor shocks (`NIFTY -25%, INR -8%, yields +150 bps`) against today's holdings to estimate rupee drawdown, biggest contributors and historical recovery time
- 📋 **Passive income, dividend calendar & cashflow runway** ([#403](https://github.com/iamsaquib8/stockwise/issues/403)) — 12-month trailing & forward monthly cashflow calendar across dividends, ETFs, REITs/InvITs, SGB 2.5% interest, bond coupons and FDs (`stockwise income`), Yield-on-Cost (`YoC`), FCF payout safety checks, and **% of monthly expenses covered by passive income**
**Market regime, valuation percentiles & implied probabilities (base rates, not crystal balls)**
- 💡 **Valuation thermometer & forward base rates** — NIFTY 50 / Midcap 150 / Smallcap 250 / S&P 500 P/E, P/B, dividend yield and **Equity Risk Premium** (earnings yield minus 10Y sovereign yield) ranked as 10Y/20Y percentiles, paired with the **historical 1Y, 3Y and 5Y forward return distributions** recorded from similar starting valuation deciles
- 💡 **Explainable market regime model** (expanding [idea #290](https://github.com/iamsaquib8/stockwise/discussions/290)) — combine trend breadth (% of universe above 50/200 DMA, 52W highs vs lows), volatility regime (India VIX / VIX percentile and term slope), yield-curve slope (`bonds` 10Y − 91D) and rolling FII/DII cash & F&O flows into transparent regime labels (*Broad Uptrend*, *Narrow/Stretched*, *High-Vol Risk-Off*, *Capitulation*) with every weight and percentile visible
- 💡 **Options-implied probability cones & event moves** — derive 1σ/2σ forward price cones (7/30/90 days) from live option-chain IV skew, and compare **options-implied ±% earnings moves** against each stock's actual 8-quarter post-earnings history (`events`)
- 💡 **Sector earnings & margin cycle breadth** (building on [#359](https://github.com/iamsaquib8/stockwise/issues/359) and [#361](https://github.com/iamsaquib8/stockwise/issues/361)) — track revenue acceleration, operating-margin expansion/compression and institutional accumulation across sectors quarter over quarter
**Behavioral guardrails & AI co-pilot playbooks**
- 💡 **Decision journal & behavioural audit** (`portfolio journal`) — measure your own historical timing: 6M/12M performance of stocks after you sold them, panic-selling during index drawdowns, annual turnover, and cumulative tax + fee drag
- 📋 **Agentic `ask --auto`, MCP tools & playbooks** ([#378](https://github.com/iamsaquib8/stockwise/issues/378), expanding [#53](https://github.com/iamsaquib8/stockwise/issues/53) and [#261](https://github.com/iamsaquib8/stockwise/issues/261)) — multi-turn profile-aware `ask -i` / `--auto` with reversible workspace actions (`stockwise undo`), plus `checkup`, `redflags`, `fit`, `stress`, `pme`, `factors` and playbooks (*"Audit my portfolio for early red flags"*, *"Where does my ₹50k SIP close the biggest gap this month?"*, *"Stress-test my retirement corpus against a 2008 crash"*)
## Non-goals
- Becoming a brokerage or holding custody of funds
- Promising profits or providing personalized financial advice (see
[DISCLAIMER.md](https://iamsaquib8.github.io/stockwise/disclaimer.html)), stock tips or recommended baskets
- High-frequency / co-located trading
- Scraping or redistributing licensed data (consensus estimates, paid screener datasets); the
NSE/BSE website provider stays opt-in because the exchanges' terms restrict automated
collection
- A licensed or scraped IPO grey-market premium feed, and Account Aggregator integration (needs an
RBI licence); `stockwise ipo` only shows GMP figures that news headlines report, as unofficial
- AI that trades on its own — any order an AI tool proposes goes through the same preview and
typed confirmation as a human's
---
# Changelog
All notable changes to this project are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.8.1] - 2026-11-07
Fixes the wrong numbers and import data loss listed under "Known issues" in 0.8.0, and replaces the BUY/SELL verdict with neutral pillar scores.
### Added
- `score` fetches only the statements it needs, and `verdict`, `insights` and the bare-symbol snapshot skip the news and filing sources, so they answer faster. Every NSE request now times out after 3 seconds instead of stalling the command ([#222](https://github.com/iamsaquib8/stockwise/issues/222)).
- `score` fetches only the statements it needs, and `verdict`, `insights` and the bare-symbol snapshot skip the news and filing sources, so they answer faster. Every NSE request now times out after 3 seconds instead of stalling the command ([#222](https://github.com/iamsaquib8/stockwise/issues/222)).
### Changed
- `verdict`, `deep`, `analyze` and `stockwise SYMBOL` no longer print a STRONG BUY / SELL label, an entry zone, a stop-loss, a target price or risk/reward. They show six pillar scores (Strong / Moderate / Weak), a 0-100 composite and a "not a recommendation" note. In `--json`, `action`, `entry_zone`, `stop_loss`, `target_price`, `upside_pct`, `risk_pct`, `risk_reward`, the time-horizon texts and each pillar's `grade` are gone, and each pillar has a `band`. The `decision`, `signal` and `buysell` aliases are removed ([#188](https://github.com/iamsaquib8/stockwise/issues/188)).
- `verdict`: a pillar with no data now shows "no data" and is left out of the composite instead of scoring 50 or 55, and crypto, FX and index symbols get only the technical pillar and no composite ([#211](https://github.com/iamsaquib8/stockwise/issues/211)).
- `verdict --explain` now lists the real weights (valuation 20, growth 25, balance sheet 15, smart money 15, analyst 10, technicals 15), taken from the scores themselves, and the data sources that failed are listed under the score ([#211](https://github.com/iamsaquib8/stockwise/issues/211)).
- `verdict`: the fixed "Confidence: 85%" is replaced by `data_coverage` (JSON) / "Data coverage" (text), the share of the score's inputs that had data ([#77](https://github.com/iamsaquib8/stockwise/issues/77)).
- `insights`: analyst cards say "mean rating x/5" instead of "Strong Buy", and the 52-week-low card no longer calls it a "value entry".
- The `verdict` tool in the MCP server and in `stockwise ask` is now called "6-pillar scorecard", returns only pillar scores, data coverage and the facts behind them, and its description says it is not a recommendation; the buy/sell action, entry zone and stop-loss are no longer sent to the model ([#112](https://github.com/iamsaquib8/stockwise/issues/112)).
- `ipo`: the board and `ipo SYMBOL` no longer print APPLY / AVOID / SUBSCRIBE labels or the family-PAN "Retail Split Strategy". They show demand labels (strong, moderate, mixed or weak demand, book building), the allotment odds by application tier and, with `--capital`, the odds at that amount. In `--json`, `listing_verdict`, `long_term_verdict`, `timing_guidance`, `capital_plan` and `golden_rules` are replaced by `listing_demand`, `long_term_profile`, `timeline`, `capital_odds` and `allotment_rules` ([#189](https://github.com/iamsaquib8/stockwise/issues/189)).
- `picks` is now "Highest Scores in the Large-Cap Universe" and lists the factors that raised each score instead of "reasons" such as "Analyst buy" ([#189](https://github.com/iamsaquib8/stockwise/issues/189)).
- `intraday` is a signal scanner: it no longer gives entries, stop-losses, targets or position sizes for your capital, and shows the strategies behind the strongest signals instead. The `CAPITAL` and `TARGET%` arguments are accepted and ignored ([#189](https://github.com/iamsaquib8/stockwise/issues/189)).
- The web dashboard's Research tab shows the pillar scores and data coverage instead of the buy/sell label, entry zone and stop-loss.
- The crates.io and PyPI description and keywords now mention portfolio, tax and the MCP server.
- `verdict`, `deep`, `analyze` and `stockwise SYMBOL` no longer print a STRONG BUY / SELL label, an entry zone, a stop-loss, a target price or risk/reward. They show six pillar scores (Strong / Moderate / Weak), a 0-100 composite and a "not a recommendation" note. In `--json`, `action`, `entry_zone`, `stop_loss`, `target_price`, `upside_pct`, `risk_pct`, `risk_reward`, the time-horizon texts and each pillar's `grade` are gone, and each pillar has a `band`. The `decision`, `signal` and `buysell` aliases are removed ([#188](https://github.com/iamsaquib8/stockwise/issues/188)).
- `verdict`: a pillar with no data now shows "no data" and is left out of the composite instead of scoring 50 or 55, and crypto, FX and index symbols get only the technical pillar and no composite ([#211](https://github.com/iamsaquib8/stockwise/issues/211)).
- `verdict --explain` now lists the real weights (valuation 20, growth 25, balance sheet 15, smart money 15, analyst 10, technicals 15), taken from the scores themselves, and the data sources that failed are listed under the score ([#211](https://github.com/iamsaquib8/stockwise/issues/211)).
- `verdict`: the fixed "Confidence: 85%" is replaced by `data_coverage` (JSON) / "Data coverage" (text), the share of the score's inputs that had data ([#77](https://github.com/iamsaquib8/stockwise/issues/77)).
- `insights`: analyst cards say "mean rating x/5" instead of "Strong Buy", and the 52-week-low card no longer calls it a "value entry".
- The `verdict` tool in the MCP server and in `stockwise ask` is now called "6-pillar scorecard", returns only pillar scores, data coverage and the facts behind them, and its description says it is not a recommendation; the buy/sell action, entry zone and stop-loss are no longer sent to the model ([#112](https://github.com/iamsaquib8/stockwise/issues/112)).
- `ipo`: the board and `ipo SYMBOL` no longer print APPLY / AVOID / SUBSCRIBE labels or the family-PAN "Retail Split Strategy". They show demand labels (strong, moderate, mixed or weak demand, book building), the allotment odds by application tier and, with `--capital`, the odds at that amount. In `--json`, `listing_verdict`, `long_term_verdict`, `timing_guidance`, `capital_plan` and `golden_rules` are replaced by `listing_demand`, `long_term_profile`, `timeline`, `capital_odds` and `allotment_rules` ([#189](https://github.com/iamsaquib8/stockwise/issues/189)).
- `picks` is now "Highest Scores in the Large-Cap Universe" and lists the factors that raised each score instead of "reasons" such as "Analyst buy" ([#189](https://github.com/iamsaquib8/stockwise/issues/189)).
- `intraday` is a signal scanner: it no longer gives entries, stop-losses, targets or position sizes for your capital, and shows the strategies behind the strongest signals instead. The `CAPITAL` and `TARGET%` arguments are accepted and ignored ([#189](https://github.com/iamsaquib8/stockwise/issues/189)).
- The web dashboard's Research tab shows the pillar scores and data coverage instead of the buy/sell label, entry zone and stop-loss.
- The crates.io and PyPI description and keywords now mention portfolio, tax and the MCP server.
- `longterm` and `daemon longterm` describe scores as data, not calls: analyst lines give the mean rating and
mean price estimate, risk tiers are `Lower` / `Moderate` / `Higher`, a pillar with no data shows a dash, and growth,
SIP and simulation are labelled as an illustration ([#189](https://github.com/iamsaquib8/stockwise/issues/189)).
- `analyze`, `insider`, `deep` and `compare`: the analysts' rating is shown as "mean rating 1.8/5 (1 = most positive)" instead of Strong Buy / Buy / Hold / Sell, and the price target line is now "Analyst Mean Price Estimate".
`compare --json` and `--csv` give `analyst_mean_rating` (a number) instead of `analyst_rating` (a word). `verdict` words the same figure as an analyst mean price estimate above or below the current price.
- `analyze` and the research insight cards: the analyst lines say "mean price estimate" and give the mean rating as a number, instead of "target upside" and the Buy / Hold word.
- `ipo`: the press section counts positive and negative third-party headlines instead of "Subscribe / Avoid" mentions, and headlines that tell readers what to do are no longer repeated. In `--json`, `street_buzz.subscribe_mentions` and `avoid_mentions` are now `positive_headlines` and `negative_headlines`.
- `daemon intraday` and `sim`: paper positions are shown and sent in notifications as "long" / "short" with "level 1", "level 2" and "exit level" instead of BUY / SELL, ENTRY, T1, T2 and SL. Existing `daemon_state.json` and simulator history files still load.
- `technical` (and the `technical` MCP and `ask` tool): the RSI signal now reads "RSI above 70 (overbought zone)" or "RSI below 30 (oversold zone)" instead of "Consider selling" / "Consider buying" / "Strong buy signal". It describes the reading and no longer gives a call.
- The docs site is organised around goals: a five-minute quickstart, a "What do
you want to do?" page and eight task-based guides (import trades, Indian
capital-gains tax, quick lookups, rebalancing, Claude and Cursor over MCP,
alerts and the daemon, backtests and practice, scripting). Each command
reference section links to its guide.
### Fixed
- `import` of an `.xlsx` workbook with several sheets: only one sheet is read (the first whose header fits the importer, in tab order), so a mutual-fund or summary tab is no longer imported as bogus holdings. The sheets used and skipped are listed, and `--sheet NAME` picks another ([#101](https://github.com/iamsaquib8/stockwise/issues/101)).
- `import`: a tradebook with Mindtree trades (now recorded as LTIM) no longer stops with a 404 at the split check. If Yahoo has no price history for a symbol, the check is skipped for it, a note says so, and the rest of the file is imported ([#316](https://github.com/iamsaquib8/stockwise/issues/316)).
- `portfolio xray`: Indian stocks are now called large cap from ₹1,00,000 crore and mid cap from ₹33,000 crore (AMFI's list of December 2024), not ₹20,000 and ₹5,000 crore, so a ₹25,000 crore company shows as small cap and the market-cap split is no longer tilted to large caps ([#100](https://github.com/iamsaquib8/stockwise/issues/100)).
- `import groww` and `import upstox`: a holdings file whose ISIN column holds text such as `₹12,34,567` no longer crashes the import; the cell is treated as not an ISIN ([#102](https://github.com/iamsaquib8/stockwise/issues/102)).
- `import cas`: schemes you have fully redeemed are no longer skipped. When the statement covers every purchase, their redemptions are recorded as realized sales for `tax in`, `tax export in` and `tax advance`, and no holding is left. A redeemed scheme with units bought before the statement is listed as skipped, with the reason ([#90](https://github.com/iamsaquib8/stockwise/issues/90)).
- `import`: a second tradebook for the same broker is merged with the first instead of replacing its lots and sales, so yearly Zerodha Console files can be imported in any order and re-importing a file changes nothing ([#83](https://github.com/iamsaquib8/stockwise/issues/83)).
- `import` and `trade sync`: a buy and sell of the same share on one day are netted as an intraday sale before any older lot is touched, and `tax in`, `tax export in` and `tax advance` report intraday results separately instead of as 111A/112A gains from the oldest lot ([#84](https://github.com/iamsaquib8/stockwise/issues/84)).
- `import` and `trade holdings`: a holdings file from a second broker adds to the shares of the first instead of overwriting them. Lots and sales now carry the demat account (`zerodha`, `groww`, ...), FIFO, imports and syncs work per account, `portfolio lots` shows it, and old portfolio files load unchanged ([#85](https://github.com/iamsaquib8/stockwise/issues/85)).
- `import` and `portfolio add`: dated HDFC and Mindtree trades from before their mergers are now recorded as HDFCBANK (42 for 25) and LTIM (73 for 100) shares at the same total cost and date, instead of the old quantity at the old price ([#86](https://github.com/iamsaquib8/stockwise/issues/86)).
- `tax in`, `tax export in`, `tax advance` and `harvest`: gold, silver, liquid, debt and international ETFs (such as `GOLDBEES`, `LIQUIDBEES`, `MON100`) are no longer taxed as listed shares under Sec. 111A/112A; `tax in` lists them under "taxed under other rules" and `harvest` no longer offers them for the ₹1.25 lakh exemption ([#87](https://github.com/iamsaquib8/stockwise/issues/87)).
- `portfolio`, `import`, `trade sync`, `trade holdings` and `wealth`: commands that change your portfolio or wealth history at the same time no longer overwrite each other's changes. Each takes a lock, reloads the file and applies only its own change; a second command waits up to 30 seconds, then says so ([#93](https://github.com/iamsaquib8/stockwise/issues/93)).
- `deep`: no longer panics (`index out of bounds`) on NSE stocks when the latest bar has a missing close ([#306](https://github.com/iamsaquib8/stockwise/issues/306)).
- Yahoo fetch errors no longer include the request URL, so the session crumb cannot appear in error text.
- `portfolio xray` treats Indian ETFs (such as `NIFTYBEES`, `GOLDBEES`, `LIQUIDBEES`) as funds, also when Yahoo returns no holdings for them, instead of flagging them as one stock with too much weight. Gold, silver and liquid ETFs count as commodity and cash ([#220](https://github.com/iamsaquib8/stockwise/issues/220)).
- `longterm`: the SIP calculator now grows each monthly instalment only for the time it stays invested (₹15,000 a month at 12% gives ₹12.37 L after 5 years, not ₹15.86 L), the "SIP for" target reads `$1M` for US stocks, and the "Suggested SIP Allocation" block is gone ([#67](https://github.com/iamsaquib8/stockwise/issues/67), [#189](https://github.com/iamsaquib8/stockwise/issues/189)).
- Yahoo Finance: commands no longer open the Yahoo session at start-up, so `history`, `backtest` and a provider-backed `quote` still work when Yahoo refuses the session token, and commands that never touch Yahoo send no request at all ([#154](https://github.com/iamsaquib8/stockwise/issues/154)).
- `quote`, `verdict`, `deep`, `score` and `insights`: a symbol Yahoo does not know, such as the BSE code `500325`, now gives a "not found" error with exit code 1 instead of an empty card or a verdict on a company with no price ([#206](https://github.com/iamsaquib8/stockwise/issues/206)).
- `score`, `analyze`, `verdict` and `deep`: companies that file statements in US dollars but trade in rupees, such as Infosys, now have their statement amounts converted at the live USD/INR rate before ratios and revenue are shown, so INFY's Altman Z drops from 426 to about 8 and its revenue reads ₹47.33K Cr, not ₹504 Cr ([#207](https://github.com/iamsaquib8/stockwise/issues/207)).
- `quote`, `compare`, `deep`, `dividends` and `divcal`: dividend yield no longer reads 0 for stocks such as RELIANCE, or 0.05% for INFY, when Yahoo's trailing figure is missing or computed from a dollar dividend; `dividends INFY` now shows 4.4% ([#208](https://github.com/iamsaquib8/stockwise/issues/208)).
- `peers`, `compare`, `deep` and `analyze`: sector, profit margin, debt/equity, revenue growth, beta and analyst ratings are now read from Yahoo's company summary, so `peers TCS` lists IT peers and `compare` no longer shows rows of N/A. Rows and sections with no data at all are left out ([#209](https://github.com/iamsaquib8/stockwise/issues/209)).
- `deep` and `analyze`: banks and lenders no longer show an Altman Z "Distress Zone" next to a strong score; they show "n/a for banks and lenders", and `score` says the same. The doubled brackets in the zone label are gone ([#212](https://github.com/iamsaquib8/stockwise/issues/212)).
- `forecast`: the default history is now 6 months instead of one, so `forecast TCS` prints a projection instead of "Not enough price data". The error now says how many days it found, and the `SYMBOL` argument has a help line ([#215](https://github.com/iamsaquib8/stockwise/issues/215)).
- `watch add`, `portfolio add` and `alert add` now refuse a ticker Yahoo does not know (for example `RELIANC`) and name the likely one, instead of saving it silently; `--force` saves it anyway, and funds, SGBs and offline use are not blocked. `technical`, `history`, `risk` and the other chart commands say "No price history for ZZQQXX.NS" instead of "HTTP 404" ([#216](https://github.com/iamsaquib8/stockwise/issues/216)).
- `portfolio` and `portfolio --json` now work offline: they list every holding with shares, average cost and cost basis, show prices as N/A (`price: null` and `unpriced` in JSON) and say "Offline: prices unavailable" instead of failing. `tax`, `harvest`, `rebalance` and `xray` still need a connection ([#221](https://github.com/iamsaquib8/stockwise/issues/221)).
- `import` of an `.xlsx` workbook with several sheets: only one sheet is read (the first whose header fits the importer, in tab order), so a mutual-fund or summary tab is no longer imported as bogus holdings. The sheets used and skipped are listed, and `--sheet NAME` picks another ([#101](https://github.com/iamsaquib8/stockwise/issues/101)).
- `import`: a tradebook with Mindtree trades (now recorded as LTIM) no longer stops with a 404 at the split check. If Yahoo has no price history for a symbol, the check is skipped for it, a note says so, and the rest of the file is imported ([#316](https://github.com/iamsaquib8/stockwise/issues/316)).
- `portfolio xray`: Indian stocks are now called large cap from ₹1,00,000 crore and mid cap from ₹33,000 crore (AMFI's list of December 2024), not ₹20,000 and ₹5,000 crore, so a ₹25,000 crore company shows as small cap and the market-cap split is no longer tilted to large caps ([#100](https://github.com/iamsaquib8/stockwise/issues/100)).
- `import groww` and `import upstox`: a holdings file whose ISIN column holds text such as `₹12,34,567` no longer crashes the import; the cell is treated as not an ISIN ([#102](https://github.com/iamsaquib8/stockwise/issues/102)).
- `import cas`: schemes you have fully redeemed are no longer skipped. When the statement covers every purchase, their redemptions are recorded as realized sales for `tax in`, `tax export in` and `tax advance`, and no holding is left. A redeemed scheme with units bought before the statement is listed as skipped, with the reason ([#90](https://github.com/iamsaquib8/stockwise/issues/90)).
- `import`: a second tradebook for the same broker is merged with the first instead of replacing its lots and sales, so yearly Zerodha Console files can be imported in any order and re-importing a file changes nothing ([#83](https://github.com/iamsaquib8/stockwise/issues/83)).
- `import` and `trade sync`: a buy and sell of the same share on one day are netted as an intraday sale before any older lot is touched, and `tax in`, `tax export in` and `tax advance` report intraday results separately instead of as 111A/112A gains from the oldest lot ([#84](https://github.com/iamsaquib8/stockwise/issues/84)).
- `import` and `trade holdings`: a holdings file from a second broker adds to the shares of the first instead of overwriting them. Lots and sales now carry the demat account (`zerodha`, `groww`, ...), FIFO, imports and syncs work per account, `portfolio lots` shows it, and old portfolio files load unchanged ([#85](https://github.com/iamsaquib8/stockwise/issues/85)).
- `import` and `portfolio add`: dated HDFC and Mindtree trades from before their mergers are now recorded as HDFCBANK (42 for 25) and LTIM (73 for 100) shares at the same total cost and date, instead of the old quantity at the old price ([#86](https://github.com/iamsaquib8/stockwise/issues/86)).
- `tax in`, `tax export in`, `tax advance` and `harvest`: gold, silver, liquid, debt and international ETFs (such as `GOLDBEES`, `LIQUIDBEES`, `MON100`) are no longer taxed as listed shares under Sec. 111A/112A; `tax in` lists them under "taxed under other rules" and `harvest` no longer offers them for the ₹1.25 lakh exemption ([#87](https://github.com/iamsaquib8/stockwise/issues/87)).
- `portfolio`, `import`, `trade sync`, `trade holdings` and `wealth`: commands that change your portfolio or wealth history at the same time no longer overwrite each other's changes. Each takes a lock, reloads the file and applies only its own change; a second command waits up to 30 seconds, then says so ([#93](https://github.com/iamsaquib8/stockwise/issues/93)).
- `deep`: no longer panics (`index out of bounds`) on NSE stocks when the latest bar has a missing close ([#306](https://github.com/iamsaquib8/stockwise/issues/306)).
- Yahoo fetch errors no longer include the request URL, so the session crumb cannot appear in error text.
- `portfolio xray` treats Indian ETFs (such as `NIFTYBEES`, `GOLDBEES`, `LIQUIDBEES`) as funds, also when Yahoo returns no holdings for them, instead of flagging them as one stock with too much weight. Gold, silver and liquid ETFs count as commodity and cash ([#220](https://github.com/iamsaquib8/stockwise/issues/220)).
- `longterm`: the SIP calculator now grows each monthly instalment only for the time it stays invested (₹15,000 a month at 12% gives ₹12.37 L after 5 years, not ₹15.86 L), the "SIP for" target reads `$1M` for US stocks, and the "Suggested SIP Allocation" block is gone ([#67](https://github.com/iamsaquib8/stockwise/issues/67), [#189](https://github.com/iamsaquib8/stockwise/issues/189)).
- Yahoo Finance: commands no longer open the Yahoo session at start-up, so `history`, `backtest` and a provider-backed `quote` still work when Yahoo refuses the session token, and commands that never touch Yahoo send no request at all ([#154](https://github.com/iamsaquib8/stockwise/issues/154)).
- `quote`, `verdict`, `deep`, `score` and `insights`: a symbol Yahoo does not know, such as the BSE code `500325`, now gives a "not found" error with exit code 1 instead of an empty card or a verdict on a company with no price ([#206](https://github.com/iamsaquib8/stockwise/issues/206)).
- `score`, `analyze`, `verdict` and `deep`: companies that file statements in US dollars but trade in rupees, such as Infosys, now have their statement amounts converted at the live USD/INR rate before ratios and revenue are shown, so INFY's Altman Z drops from 426 to about 8 and its revenue reads ₹47.33K Cr, not ₹504 Cr ([#207](https://github.com/iamsaquib8/stockwise/issues/207)).
- `quote`, `compare`, `deep`, `dividends` and `divcal`: dividend yield no longer reads 0 for stocks such as RELIANCE, or 0.05% for INFY, when Yahoo's trailing figure is missing or computed from a dollar dividend; `dividends INFY` now shows 4.4% ([#208](https://github.com/iamsaquib8/stockwise/issues/208)).
- `peers`, `compare`, `deep` and `analyze`: sector, profit margin, debt/equity, revenue growth, beta and analyst ratings are now read from Yahoo's company summary, so `peers TCS` lists IT peers and `compare` no longer shows rows of N/A. Rows and sections with no data at all are left out ([#209](https://github.com/iamsaquib8/stockwise/issues/209)).
- `deep` and `analyze`: banks and lenders no longer show an Altman Z "Distress Zone" next to a strong score; they show "n/a for banks and lenders", and `score` says the same. The doubled brackets in the zone label are gone ([#212](https://github.com/iamsaquib8/stockwise/issues/212)).
- `forecast`: the default history is now 6 months instead of one, so `forecast TCS` prints a projection instead of "Not enough price data". The error now says how many days it found, and the `SYMBOL` argument has a help line ([#215](https://github.com/iamsaquib8/stockwise/issues/215)).
- `watch add`, `portfolio add` and `alert add` now refuse a ticker Yahoo does not know (for example `RELIANC`) and name the likely one, instead of saving it silently; `--force` saves it anyway, and funds, SGBs and offline use are not blocked. `technical`, `history`, `risk` and the other chart commands say "No price history for ZZQQXX.NS" instead of "HTTP 404" ([#216](https://github.com/iamsaquib8/stockwise/issues/216)).
- `portfolio` and `portfolio --json` now work offline: they list every holding with shares, average cost and cost basis, show prices as N/A (`price: null` and `unpriced` in JSON) and say "Offline: prices unavailable" instead of failing. `tax`, `harvest`, `rebalance` and `xray` still need a connection ([#221](https://github.com/iamsaquib8/stockwise/issues/221)).
- `alert watch` and `alert check`: `change-above`, `change-below` and `volume-spike` alerts no longer
repeat the last session's move on weekends, holidays and before the open (for example Friday's fall
announced again on Saturday, Sunday and Monday morning by `alert watch --once` in cron). They give
no reading until today's session has traded and re-arm on the quote's own session date, so a crypto
alert does not fire twice across midnight in your market's time zone
([#103](https://github.com/iamsaquib8/stockwise/issues/103)).
- `sim replay end` now scores and clears a replay whose price file (`sim_replay_tape.json`) was
deleted, instead of asking you to run `sim replay end` and leaving the replay stuck
([#106](https://github.com/iamsaquib8/stockwise/issues/106)).
- `sim history` and `sim status` no longer mix markets: history totals, best and worst day, win rate,
streaks and the chart cover only the `-m` market (rupees and dollars were added together), and `sim
status` shows each session in its own currency. `sim start` is no longer blocked by an open session
of another market the same day ([#107](https://github.com/iamsaquib8/stockwise/issues/107)).
- `sim` practice score: the drawdown part is now judged against what the benchmark would have lost at
your average exposure, so holding mostly cash through a crash no longer earns drawdown points that
the same picks fully invested would not. Scores of runs with under full exposure can drop by up to
about 14 points ([#119](https://github.com/iamsaquib8/stockwise/issues/119)).
- `trade buy`, `trade sell`, `trade limit` and `trade gtt` with `--yes` are now refused, before any
broker login, unless `max_order_value` is set in the broker's config file, since `--yes` removes the
only other guard against a wrong quantity. Scripts that use `--yes` need `"max_order_value": 50000`
(or your own cap) added; `--dry-run` and typing `yes` at the prompt work as before. The preview now
notes when no cap is set ([#125](https://github.com/iamsaquib8/stockwise/issues/125)).
- MCP `order_preview`: the `stockwise trade` command it returns now quotes symbols with `&`, such as
`'M&M'`, so pasting it into bash, zsh or PowerShell no longer splits at the `&`
([#126](https://github.com/iamsaquib8/stockwise/issues/126)).
- `ask`: a failing tool no longer sends the full error to the model. Local file paths (a corrupt
`portfolio.json` includes your home directory) and the Yahoo session `crumb` are removed, as
`stockwise mcp` already did; `--verbose` still prints the full error on your terminal. The notice
before the first cloud request now says that your portfolio may be sent if a tool reads it, not only
market data ([#127](https://github.com/iamsaquib8/stockwise/issues/127)).
- `correlate` and `pcorr` pair returns by date instead of by position, so symbols with different holiday calendars (NSE and NYSE) or a seven-day crypto week no longer give a correlation near zero. Two symbols with fewer than 20 shared returns show `N/A`, and `pcorr` no longer prints `0.000` for them ([#72](https://github.com/iamsaquib8/stockwise/issues/72)).
- `risk` and the volatility in `timeframes` annualize with the right number of bars for the asset: 365
a year for crypto, 260 for FX, 252 for stocks, and 288 five-minute bars a day for crypto. `returns`
annualizes by calendar time and shows `—` (null in JSON) for spans under a year, so a +100% year
reads +100% for crypto and for stocks. Calmar annualizes over the number of returns
([#73](https://github.com/iamsaquib8/stockwise/issues/73)).
- Pivot points in `support`, `intraday`/`bot` and the stock report come from the last completed daily session, not today's live bar, a weekly bar or the last five minutes, so the levels no longer move with every tick or change with `-p`. `support` names the session they come from ([#74](https://github.com/iamsaquib8/stockwise/issues/74)).
- `options oi` reads put build-up for the underlying: put buying (premium and open interest up) is now bearish and put writing is bullish, and the per-strike tags are coloured that way. `--json` gains `calls_sentiment` and `puts_sentiment` ([#75](https://github.com/iamsaquib8/stockwise/issues/75)).
- `longterm`, `verdict` and `insights` compare market cap, cash and insider trades with cut-offs
written in US dollars after converting from the quote currency (at rough fixed rates), so the same
size scores the same for a rupee and a dollar stock: a ₹1.5 lakh insider buy no longer earns the
insider-buying bonus, and ₹5,000 crore is a small cap
([#76](https://github.com/iamsaquib8/stockwise/issues/76)).
- `tax in`, `tax export in`, `tax advance` and `harvest` now decide in one place which sales count as
listed shares or equity funds (Sec. 111A/112A). Debt funds, SGBs, crypto and gold, silver, liquid
and international ETFs are left out of all four, so a tax-free SGB redemption no longer raises
advance tax and a crypto loss no longer reduces STCG tax
([#88](https://github.com/iamsaquib8/stockwise/issues/88)).
- `tax advance` no longer shows past instalments as `Paid` when no payment is recorded: they are `Overdue`. `--paid` takes dated payments (`--paid 2025-06-14:3000`, repeated) so Sec. 234C interest follows what was paid by each due date, on shortfalls rounded down to ₹100 ([#89](https://github.com/iamsaquib8/stockwise/issues/89)).
- `harvest` offers losses as the FIFO sale you would actually make: a losing lot behind older lots is
sold with them, and a holding whose sale books a net gain is listed as FIFO-blocked instead of a
loss. Tax saved comes from the tax estimate with this year's gains, the shared ₹1.25 lakh exemption
is split between candidates, and `Total P&L` shows its real sign
([#91](https://github.com/iamsaquib8/stockwise/issues/91)).
- `harvest` no longer counts US holdings as rupees in the Indian harvest: they are listed under `other_currency`, as `tax in` does ([#92](https://github.com/iamsaquib8/stockwise/issues/92)).
- `tax in`, `tax export in`, `harvest` and `tax advance` now include the 4% health and education cess in India estimates, so totals and advance-tax instalments are no longer about 4% too low. Surcharge is still left out ([#98](https://github.com/iamsaquib8/stockwise/issues/98)).
- `tax export in` applies the rates and exemption that were in force on each sale date: sales before 23 Jul 2024 are taxed at 15% (STCG) and 10% (LTCG), and the LTCG exemption is ₹1 lakh up to FY 2023-24. A mixed FY 2024-25 shows how much of the taxable gain came from before 23 Jul ([#99](https://github.com/iamsaquib8/stockwise/issues/99)).
- `daemon intraday` keeps running after it settles a session and waits for the next trading day (weekends and exchange holidays skipped), so one process covers every day and `stockwise_daemon_up` stays 1. Use the new `--once` flag for one session and exit ([#117](https://github.com/iamsaquib8/stockwise/issues/117)).
- `/metrics` no longer reports a finished day's daemon positions as open: a settled state shows `stockwise_daemon_open_positions 0`. The money gauges carry a `currency` label (`INR`, `USD`) and `stockwise_daemon_last_update_timestamp_seconds` is new ([#120](https://github.com/iamsaquib8/stockwise/issues/120)).
- `fibs` always reads a year of daily bars and reports the last 21, 63 and 126 trading days, so its 1, 3 and 6 month sections no longer show the same levels; `-p` is now ignored ([#137](https://github.com/iamsaquib8/stockwise/issues/137)).
- `stockwise C -m us` and the other single-capital-letter US tickers (`T`, `D`, `A`, `W`) are read as symbols instead of failing with "unrecognized subcommand"; the lowercase aliases such as `c` for `compare` still work, and `stockwise quote C` is the explicit form ([#147](https://github.com/iamsaquib8/stockwise/issues/147)).
- `serve`, `tui` and `mcp` get a new Yahoo session when Yahoo answers HTTP 401 to a quote request, instead of failing until restarted: one fresh handshake (at most once a minute, shared by concurrent requests), then the request is retried once. Yahoo's cookies are now kept and refreshed by the HTTP client rather than copied by hand ([#155](https://github.com/iamsaquib8/stockwise/issues/155)).
- `serve`, `tui`, `mcp` and the daemons keep using Alpha Vantage, Massive or Twelve Data after their free-tier request allowance runs out or a request fails: the allowance refills each window (a minute, or 4 h 48 min for Alpha Vantage) and a pause after an error ends after a few minutes, with one notice per pause ([#157](https://github.com/iamsaquib8/stockwise/issues/157)).
- With Alpha Vantage, Twelve Data or Massive, a quote keeps the provider's trade date instead of Yahoo's time of today, so alert checks no longer treat yesterday's close as a bar for today ([#158](https://github.com/iamsaquib8/stockwise/issues/158)).
- `-m ca` closes the market on New Year's Day, Good Friday and the other TSX holidays in 2027 (provisional until TMX publishes its list), and the NSE 2027 list includes Good Friday, 26 March ([#164](https://github.com/iamsaquib8/stockwise/issues/164)).
- `mf` commands and `MF:` holdings use the saved AMFI file, and say which day it was saved, when AMFI answers with a maintenance page or an empty body instead of the NAV data ([#166](https://github.com/iamsaquib8/stockwise/issues/166)).
- `risk`, `backtest` and `practice` compute the Sortino ratio with downside deviation taken over all periods instead of only the losing ones, and measure shortfall against the risk-free rate, so strategies with few down days are no longer scored too low ([#28](https://github.com/iamsaquib8/stockwise/issues/28)).
- Terminal charts print "Chart dimensions too small" instead of panicking when the terminal width or height is zero or too small to draw them ([#30](https://github.com/iamsaquib8/stockwise/issues/30)).
- Importing a spreadsheet with a corrupt cell reference (a column past `XFD`) skips that cell instead of allocating gigabytes or crashing ([#33](https://github.com/iamsaquib8/stockwise/issues/33)).
- `import` of a demat CAS puts BSE scrip codes such as `500325` on `.BO` instead of `.NS`, and no longer doubles a `.NS` or `.BO` suffix that casparser already wrote ([#34](https://github.com/iamsaquib8/stockwise/issues/34)).
- Cached pages and reference files are written through a temp file with its own name per write, so two requests saving the same page at once can no longer corrupt it or fail the rename; a failed write leaves no temp file behind ([#36](https://github.com/iamsaquib8/stockwise/issues/36)).
- Several NSE or BSE requests that meet an expired session at once now share one session refresh, so the exchange is no longer treated as having refused us and skipped for 15 minutes ([#40](https://github.com/iamsaquib8/stockwise/issues/40)).
- `risk` shows no Calmar ratio, rather than `NaN` or `inf`, when a price series starts at zero or ends at or below zero, and the maximum drawdown ignores prices until the series first turns positive ([#43](https://github.com/iamsaquib8/stockwise/issues/43)).
- `sim history` draws a one-cell bar for a breakeven day instead of a full-width bar when the best and worst day are both zero ([#44](https://github.com/iamsaquib8/stockwise/issues/44)).
- `daemon intraday` reports a position's return from its total profit, booked half included, over what it cost, so a position that booked Target 1 no longer shows a wrong or negative percentage and `sim history` stores the right figure ([#56](https://github.com/iamsaquib8/stockwise/issues/56)).
- `score`: when a company reports no net income, the Piotroski net income is estimated from trailing EPS
times shares, or from profit margin times revenue, and marked "(est.)". It was profit margin times market
cap, which overstated it by the price-to-sales ratio and could cost the cash-flow-above-earnings point
([#68](https://github.com/iamsaquib8/stockwise/issues/68)).
- `score`: the Altman Z-score needs all five ratios. With any missing, `score` shows "n/a", names the
missing factors and gives no zone, instead of summing the ratios it had. Guessed inputs (a fixed share
of assets as current assets, 70% of equity as retained earnings, ROA in place of EBIT) are removed, and
Z'' is still given when only the sales ratio is missing
([#69](https://github.com/iamsaquib8/stockwise/issues/69)).
- `score`: the non-manufacturing Altman Z'' now uses book equity over total liabilities for its fourth
ratio, as the model defines it, and shows its own factor table. It used market cap, which put high
price-to-book stocks in the Safe zone whatever their other ratios said
([#70](https://github.com/iamsaquib8/stockwise/issues/70)).
- `score`: the Piotroski F-Score counts only the signals that have data. A missing share count, prior year
or margin shows "n/a", earns no point and is left out of the count (for example 5/7 evaluated), and a
rating is given only when all nine signals ran. Gross margin is compared with gross margin, or operating
margin with operating margin, for both years. `--json` adds `evaluated`; `rating` is null when not rated
([#71](https://github.com/iamsaquib8/stockwise/issues/71)).
- `tax advance --paid` ignores a payment dated before the financial year began, since it was made for an earlier year, and says so in the output.
- `tax advance --fy` for a year before 2024-25 uses the rates and exemption that applied to each sale date, as `tax export in` does: STCG of Rs 1,00,000 in FY 2023-24 is now Rs 15,600 with cess, not Rs 20,800.
- `tax advance` no longer charges Sec. 234C interest on the last instalment for gains made after 15 March when their tax is paid by 31 March; the 15 March requirement now covers only gains up to that day.
- Alpha Vantage: StockWise now sends at most 4 requests per 4 h 48 min window instead of 5, because
two windows can sit back to back and 5 could put 29 requests into one day against the free plan's
25.
- `levels` and `alert`: a crypto bar stamped after 21:00 UTC is no longer dated the next day, so the
daily bar and the prior-session pivots for crypto no longer shift by a day in the evening.
- `daemon intraday`: Ctrl+C or SIGTERM on a day that is already settled no longer closes its leftover open rows at stale prices or adds made-up P&L; `--once` on a settled day exits at once instead of waiting for the next session; and a saved state with open positions but no entry flag no longer loops printing "Session complete".
- `tax in` no longer treats a sale as intraday just because a lot with an estimated purchase date (from a holdings snapshot or sync) is dated the same day; those sales stay in the 111A/112A figures.
- `harvest` values funds you sold in full like `tax in` does, so their gains use up the Rs 1.25 lakh exemption it reports instead of being dropped.
- `import` of a file with dated buys and no sells no longer silently deletes the sales and kept trades an earlier tradebook of the same broker recorded for that symbol; it skips the symbol and asks for `--replace`.
- `trade`: a quote of 0 (a halted stock) or an invalid price no longer counts as a price, so a market order cannot pass `max_order_value` as worth nothing. With a cap set and no usable price the order is refused with "Cannot estimate the order value".
- MCP `order_preview`: a symbol that StockWise's quote lookup rewrites (such as `HDFC` or `L&T`) is refused with the NSE ticker to use (`HDFCBANK`, `LT`), so the preview always prices the instrument the `trade` command will send. For quoted symbols it also tells Windows `cmd.exe` users to use double quotes.
- `import` and `portfolio add` no longer apply HDFC Bank's 2019 split a second time to HDFC (or Mindtree) lots restated after the merger: 25 HDFC bought in 2018 stay 42 HDFCBANK shares at the same total cost, and only splits and bonuses after the merger date are applied.
- `mf nav` and `mf history`: when a download fails, a saved copy is used only if it is at most 7 days
old (older is an error that says so), and the output says it is a saved copy and from which day,
with a `stale` entry in `--json`. Before, any age was accepted and only a line on stderr said so.
- `research` and `verdict`: the NSE shareholding read asks for at most 8 filings, even when many of the
listed ones do not load, instead of working through the whole list of dozens.
- `research` and `verdict`: the NSE shareholding read now times out per request (3 seconds each, 8
seconds in all) and keeps the quarters it has read, saying how many in a warning, instead of dropping
all of them when a cold start is slow. The verdict no longer loses its shareholding pillar for that.
- `levels`, `deep`, `technical` and `intraday`: a symbol's own market now decides whether today's
session is still running and how many bars make a year, instead of the market you ran the command
with (India by default). `levels BTC-USD` or `levels ^GSPC` on a weekday evening no longer builds
pivots from today's half-finished bar and calls them the prior session.
- `research`, `analyze` and `score`: the newest quarter and the balance sheet that come from Yahoo's
fundamentals summary are now converted to the share's currency like the other statements, so Infosys
(reported in US dollars, quoted in rupees) no longer shows about -99% quarterly revenue growth or
wrong margins. With no exchange rate, those amounts are left out.
- `research`, `score` and `verdict`: total debt from the balance sheet now adds the current portion to
long-term debt, so a company with nothing due this year but long-term loans no longer looks debt-free.
Zero counts as real only when both are reported as zero.
- Reading an expense-ratio spreadsheet no longer reserves memory from the file's own size claim, and refuses a sheet that inflates past 64 MiB, so a crafted `.xlsx` cannot crash the program.
- Yahoo Finance: when the cookie and crumb handshake fails (for example while Yahoo is rate limiting you),
StockWise now remembers the failure for 45 seconds instead of repeating up to six requests and their
waits for every later call, so a command that makes many fundamentals calls fails fast.
- Prices in Swiss francs, South Korean won, New Taiwan dollars, Chinese yuan and
Brazilian reais are shown with `CHF `, `₩`, `NT$`, `CN¥` and `R$` instead of
`$` ([#17](https://github.com/iamsaquib8/stockwise/issues/17), thanks
@O1sumitkumar).
- `movers` lists only rising stocks under gainers and only falling stocks under
losers, and says so when a side is empty
([#210](https://github.com/iamsaquib8/stockwise/issues/210), thanks @ShahadatTest).
### Performance
- `ask`: when the model asks for several tools in one turn, up to 4 of them now run at the same time
instead of one after another, and the results still go back in the model's order. The per-turn tool-call
limit counts each call as before ([#113](https://github.com/iamsaquib8/stockwise/issues/113)).
- Release builds strip symbols and use one codegen unit, so the downloaded binary is about 28% smaller
(20.5 MB to 14.7 MB on macOS arm64) ([#177](https://github.com/iamsaquib8/stockwise/issues/177)).
### Security
- `ask`, `report` and `--ai`: plain `http://` to an Ollama server on another machine is refused unless `STOCKWISE_ALLOW_REMOTE_OLLAMA=1` is set, because the prompts carry holdings and tax figures. `ask` also prints a notice whenever the Ollama server is not on this computer, as it already did for cloud providers.
## [0.8.0] - 2026-11-03
### Added
- `portfolio xray` shows your real exposure to each company across direct holdings, mutual funds
and ETFs, flags any company above 10% of the portfolio, and splits the portfolio by asset class,
market cap and sector ([#51](https://github.com/iamsaquib8/stockwise/issues/51)).
- `score` shows the Piotroski F-score and the Altman Z-score with every input and formula, and
notes when a model does not suit a bank ([#50](https://github.com/iamsaquib8/stockwise/issues/50)).
- `options oi` classifies open-interest changes as long build-up, short covering, short build-up
or long unwinding, with PCR and max pain; `options chain` shows the same tag per strike ([#49](https://github.com/iamsaquib8/stockwise/issues/49)).
- `tax advance` lists the four advance-tax instalments with what is due, and estimates Sec. 234C
interest on shortfalls ([#46](https://github.com/iamsaquib8/stockwise/issues/46)).
- `portfolio target` sets target weights by stock or sector. `rebalance --inflow` puts new money
only into underweight holdings, and `--drift` ignores small deviations ([#47](https://github.com/iamsaquib8/stockwise/issues/47)).
- `ipo` shows open, upcoming and recently listed IPOs from NSE, and for one issue: subscription by
category, allotment odds, anchor and lead managers, and a scored summary. Grey-market premiums
are shown only when news headlines report them, labelled unofficial.
- Faster everyday use ([#54](https://github.com/iamsaquib8/stockwise/issues/54), [#55](https://github.com/iamsaquib8/stockwise/issues/55)):
- `stockwise SYMBOL` with no command prints a one-screen overview, and bare `stockwise` prints a quick start.
- Short names: `q` (quote), `d` (dashboard), `a` (analyze), `t` (technical), `c` (compare), `p` (portfolio), `w` (watch).
- Common names resolve to NSE symbols (`SBI` → `SBIN`, `L&T` → `LT`, and index names), and `quote` suggests close matches for a mistyped symbol.
- `--explain` on `verdict`, `technical`, `score` and `options oi` adds plain-English notes for beginners ([#52](https://github.com/iamsaquib8/stockwise/issues/52)).
- `harvest --json` / `--csv` and lot-level loss and gain candidates ([#21](https://github.com/iamsaquib8/stockwise/issues/21)).
- Broker `.xlsx` exports import directly, reading every sheet and Excel date cells ([#45](https://github.com/iamsaquib8/stockwise/issues/45)).
- Eight more read-only MCP and `ask` tools: `verdict`, `scorecard`, `insights`, `events`, `options_oi`, `tax_advance`, `harvest` and `rebalance`.
- Web dashboard: a research workbench, a portfolio switcher and keyboard shortcuts. TUI: a stock inspector and a sort key.
- `--base` accepts any three-letter currency code.
### Changed
- MACD is computed in one pass instead of recomputing the EMAs for every bar, so long histories no longer slow it down ([#29](https://github.com/iamsaquib8/stockwise/issues/29)). Thanks [@immconcon](https://github.com/immconcon).
### Fixed
- Guard against zero or unavailable price in `verdict` upside percentage calculation, preventing `+inf%` output and invalid JSON serialization on unpriced stocks ([#35](https://github.com/iamsaquib8/stockwise/issues/35)).
- Portfolio totals in a base currency other than USD or INR used the wrong side of the FX pair. Conversions now follow Yahoo's quotation direction ([#32](https://github.com/iamsaquib8/stockwise/issues/32)). Thanks [@AndreIsraelAO](https://github.com/AndreIsraelAO).
- The EMA no longer divides by zero when asked for a period of 0.
- Percent returns in `markets`, technicals and risk metrics no longer show `+inf%` when the first price is zero or negative ([#59](https://github.com/iamsaquib8/stockwise/issues/59)). Thanks [@ShahadatTest](https://github.com/ShahadatTest).
### Known issues
Fixed in 0.8.1. Check the linked issues before relying on the numbers involved.
- Importing a second tradebook or holdings file from a broker can replace earlier lots ([#83](https://github.com/iamsaquib8/stockwise/issues/83), [#85](https://github.com/iamsaquib8/stockwise/issues/85)), and same-day trades are booked as capital gains ([#84](https://github.com/iamsaquib8/stockwise/issues/84)). Back up `portfolio.json` before importing.
- `tax` treats gold, silver, liquid and international ETFs as listed shares ([#87](https://github.com/iamsaquib8/stockwise/issues/87)).
- The SIP projection in `longterm` overstates the corpus ([#67](https://github.com/iamsaquib8/stockwise/issues/67)).
- Statements reported in USD (for example INFY) are shown in ₹ in `research` scorecards ([#207](https://github.com/iamsaquib8/stockwise/issues/207)).
## [0.7.0] - 2026-11-01
### Added
- `events ` (`ev`): a timeline of company announcements from NSE and news feeds, with
figures pulled from the text, the market's reaction against NIFTY 50 or the S&P 500, and
upcoming events.
- `insights `: positive, negative and neutral findings across nine areas (ownership,
growth, margins, valuation, balance sheet, analysts, momentum, dividends, news), with NSE
shareholding data for India and SEC insider and institutional data for the US.
- `verdict `: a 0–100 score from six weighted pillars with a BUY-to-SELL label, entry
zone, stop-loss and target. These labels are being replaced with neutral scores ([#188](https://github.com/iamsaquib8/stockwise/issues/188)).
### Fixed
- Windows main-thread stack overflow (`thread 'main' has overflowed its stack`) by running the CLI entrypoint and Tokio worker threads on an explicit 8 MiB stack and heap-pinning the command dispatch state machine.
## [0.6.0] - 2026-10-26
Not released separately: these changes first shipped in 0.7.0.
### Added
- A documentation site at with
search, a one-page [cheatsheet](https://iamsaquib8.github.io/stockwise/cheatsheet.html) (a test checks that every
command in it parses) and an honest [comparison](https://iamsaquib8.github.io/stockwise/comparison.html) with
other tools.
- `stockwise mcp` — a [Model Context Protocol](https://modelcontextprotocol.io)
server for Claude Desktop, Claude Code, Cursor and other AI assistants, on
the official Rust SDK (`rmcp`). 24 read-only tools: `quote`, `history`,
`technical`, `risk`, `search`, `markets`, `mf_search`, `mf_nav`, `mf_sip`,
`options_chain`, `options_pcr`, `options_maxpain`, `options_payoff`,
`screen`, `backtest`, `portfolio_summary`, `holdings_lots`, `tax_estimate`,
`calendar`, `goal_sip`, `goal_retire`, `goal_education`, `alerts_list` and
`watchlist`, each returning the same data as the command's `--json`, with a
"not financial advice" note and results kept small. Resources: the
portfolio, watchlist and alerts. Prompts: `review_portfolio`,
`tax_harvest_check` and `explain_option_chain`. Stdio by default;
`--http 127.0.0.1:8788` serves Streamable HTTP at `/mcp` (loopback only,
or `--allow-remote` with a `STOCKWISE_API_TOKEN` bearer token, like
`serve`). `--list-tools` prints the tools and their JSON schemas. Behind
the default-on `mcp` cargo feature.
- No orders from AI assistants: the MCP server never places, changes or
cancels orders and never reads broker credentials. `--allow-orders` only
adds `order_preview`, a dry run that checks an order against the last price
and returns the `stockwise trade` command for you to run (with its own
preview, value cap and typed `yes`).
- `stockwise ask "question"` — the configured model (local Ollama by default;
OpenAI, Anthropic or Gemini when `STOCKWISE_AI_PROVIDER` selects one)
calls the same tools through each provider's tool calling and answers in
plain English. Model requests, tool calls (12) and tokens are capped
(`--max-steps`, `--max-tokens`); the last request runs without tools.
`--verbose` shows each tool call, `--json` prints the transcript. A model
without tool calling gets an error naming one that has it.
- Findable by AI assistants: the docs site publishes `llms.txt` (the pages
and the cheatsheet's commands), `llms-full.txt` and a Markdown copy of each
page; `skills/stockwise/SKILL.md` is an Agent Skill for Claude Code and
other agents; `server.json` describes the server for the official MCP
registry (`io.github.iamsaquib8/stockwise`: crates.io, PyPI, GHCR), and
`packaging/mcpb/` builds a Claude Desktop extension (`.mcpb`).
- **`pipx install stockwise` / `uvx stockwise`** — PyPI wheels that carry the
prebuilt binary (maturin `bindings = "bin"`, no Python code or
dependencies) for manylinux and musllinux x86_64/aarch64, macOS x86_64 and
arm64 and Windows x86_64, plus an sdist that builds from source. Built on
every tag; uploaded with PyPI trusted publishing once the maintainer
enables it.
- **`.deb` and `.rpm` release assets** for x86_64 and arm64
(`stockwise_amd64.deb`, `stockwise-x86_64.rpm`, ...), with man pages, bash/
zsh/fish completions and docs, and `.sha256` files. Version-less names, so
`releases/latest/download/...` links stay stable; `sudo dnf install `
works directly.
- **Termux on Android**: an `aarch64-linux-android` release archive (NDK, API
24), picked by `install.sh` on Termux (installs to `$PREFIX/bin`) and by
`cargo binstall`; and a ready-to-submit termux-packages recipe in
`packaging/termux/`.
- **nixpkgs**: a ready-to-submit `packaging/nixpkgs/package.nix`
(`buildRustPackage` with `cargoHash`, man pages, completions, version check,
update script), built in CI with the flake's nixpkgs.
- **Docker Hub mirror**: release tags copy the multi-arch GHCR image to
`docker.io/iamsaquib8/stockwise` with its description, when Docker Hub
credentials are configured. Both registries now carry an OCI description
on the multi-arch index.
- **Download stats without telemetry**: a nightly workflow archives crates.io,
GitHub release (per asset), GHCR, Docker Hub, PyPI, Homebrew, AUR and GitHub
traffic/referrer numbers to the `stats` branch;
`scripts/download-stats.py summary` prints them. Nothing is collected from
users.
- An [Install](https://iamsaquib8.github.io/stockwise/install.html) page listing every install path, what it
includes and how it updates.
### Changed
- Mutual funds and Sovereign Gold Bonds are valued in `portfolio` (table,
`--json`, `--csv`), `dashboard`, `tui`, `serve` / the web dashboard,
`wealth`, `rebalance`, `harvest`, `tax`, `report portfolio` and the long-term
daemon. `MF:` and `MF:` (growth/payout or IDCW-reinvestment
ISIN) use AMFI's latest end-of-day NAV, shown as "NAV as of <date>"; SGBs (NSE
symbol or tranche ISIN) use NSE's last price, or one gram of gold at spot
labelled "estimated" when NSE refuses (the 2.5% interest is left out, and
said so). An unknown code stays N/A with a note, never 0. `portfolio --json`
rows gain `name`, `price_source` and `nav_date`, and the report a `notes`
list. `watch` and `alert` see fund NAVs too, `portfolio sell` defaults to
the NAV, and `MF:` symbols are no longer sent to Yahoo.
- `tax in` counts equity-oriented mutual funds (by AMFI category) at the
listed-share rates and names them; debt and other funds and SGBs are left
out of the estimate with the rules that apply to them (`equity_funds` and
`other_rules` in `--json`), instead of silently getting equity rates.
- AI sections (`report`, `--ai`) give neutral, educational analysis — signals,
risks, what to watch — instead of BUY/HOLD/SELL verdicts, picks or
allocation advice, in line with SEBI's adviser, research-analyst and
finfluencer rules. Section titles say "notes" and "comparison" rather than
"verdict", "memo" or "brief", and every AI section ends with a one-line
disclaimer. Cache keys include a prompt version, so answers cached under the
old wording are not shown again.
### Fixed
- `portfolio add MF:120503`, `watch add` and `alert add` stored `MF:120503.NS`
(`-USD`, `.L`, ... in other markets), which nothing could price. `PREFIX:ID`
symbols are now kept as typed in every market, and symbols stored that way
by older versions are repaired when the portfolio, watchlist and alerts load
(a holding that then appears twice is merged).
- `MF:` alerts follow Indian market hours instead of US ones.
- `portfolio actions` skips funds and SGBs instead of asking Yahoo for them;
`portfolio benchmark` and `tax export` handle funds stored as `MF:`;
`portfolio sell` without a price refuses an SGB valued only by its gold
estimate.
- `report portfolio` no longer gives the AI a price of 0 for holdings without
a live price, and `export portfolio` / `export watchlist` leave a missing
price empty instead of writing 0.
- The rule-based insights in `analyze` were titled "AI Insights"; they are not
AI and are now titled "Insights".
- The Docker image carries the `io.modelcontextprotocol.server.name` label the
MCP registry checks.
- Prebuilt Linux binaries (archives, `install.sh`, `cargo binstall`, Homebrew
on Linux, AUR) required glibc 2.39 and failed on Ubuntu 22.04, Debian 12 and
RHEL 9 with "version `GLIBC_2.39' not found". They are now built on Ubuntu
22.04 and need glibc 2.34 or newer.
## [0.5.0] - 2026-10-25
Planning, reporting and more markets: tax exports, benchmarks and goal
planning, technical alerts and a results calendar, NSE/BSE data, more
exchanges and commodities, a web dashboard, a simulator you can play,
broker sync with a read-only self-test, and WASM strategy plugins. The
minimum Rust version is now 1.97. Command-line forms from 0.4 keep working.
### Added
- `stockwise tax export [in|us] [--fy 2025-26] [--out FILE] [--json]` — the
capital-gains schedule of a tax year's realized sales. India: one row per
lot sold, laid out for ITR Schedule CG / Schedule 112A (section 111A/112A,
ISIN from NSE's equity list or AMFI, scrip, quantity, sale value, cost, days
held, gain), then the summary — STCG, LTCG, set-off, the ₹1.25 lakh
exemption, estimated tax, carry-forward. US: a Form 8949 worksheet (Part I /
Part II) with Schedule D totals and the $3,000 loss deduction. Labelled an
estimate to verify against your broker's tax P&L; flags grandfathered
(pre-Feb-2018) shares, pre-23-Jul-2024 sales and estimated purchase dates.
- `stockwise portfolio benchmark [NIFTY|SENSEX|SP500|symbol] [-p 1y]` — the
portfolio's time-weighted return against an index, rebuilt from the lots held
at each close; alpha, beta, tracking error, information ratio and correlation
from daily returns; attribution by holding and by sector (Carino-linked, so
contributions add up to the totals); a growth-of-100 chart; other currencies
converted at daily exchange rates; holdings with partial history flagged.
`--json` / `--csv`.
- `stockwise goal sip|retire|education` — goal planner: the monthly SIP for a
target corpus (with a yearly step-up and existing savings), the retirement
corpus for today's monthly expenses and the SIP that builds it, and an
education cost grown by inflation. Amounts in Indian units (`50k`, `25L`,
`1cr`), results in lakhs and crores; `--json`. New **Planning** help group.
- `stockwise trade selftest [--broker NAME] [--redact-amounts] [--json]` — a
strictly read-only check of a broker integration against your own account:
login / session, profile, holdings, positions, order book, trade book, GTT
list, exact instrument lookup for RELIANCE, TCS and NIFTYBEES, a 1-share
LIMIT buy run through every order check without being sent, and
response-shape checks (missing and new field names, never values). Prints a
redacted Markdown report for a GitHub issue: no credentials or tokens, the
account id cut to its last 2 characters, e-mails and long numbers masked in
error messages, amounts hidden with `--redact-amounts`. The session is
wrapped so that every order, GTT or cancel call fails before reaching the
broker. See [Help verify a broker](https://iamsaquib8.github.io/stockwise/brokers.html#help-verify-a-broker).
- Community testing kit: a "Broker test report" issue template, a
verification-status table per broker in docs/brokers.md, and the criteria for
dropping the experimental label (3 independent self-test reports plus one
small live order a tester chose to place themselves).
- `stockwise trade sync [--since DATE] [--apply] [--notify] [--json]` — pulls
the broker's executed trades into the lot-based portfolio: delivery buys
become dated lots (fill date, average fill price, broker order id), sells
come out FIFO as realized sales. Previews by default; `--apply` writes.
Idempotent: applied fills are remembered by broker, order and trade id, so re-running,
or syncing the rest of a partly filled order, never double-counts. Intraday,
margin and F&O fills are skipped; sells wait while a split or bonus is not
yet applied to the lots; open and partly filled orders are listed;
quantities are compared with the broker's holdings. Dhan reads older fills
with `--since`; the other brokers return today's trades only.
- `trade gtt list` shows what changed since the last look (triggered,
cancelled, expired, no longer listed), with `--notify` to send the changes
to your notification channels and `--json`. `trade sync --apply` tracks the
same changes.
- `Broker` trait: `profile`, `trades` (trade book), raw read-only `rows` and
per-endpoint response shapes for all five brokers; order-book rows now carry
filled quantity, average price and product. Experimental on Zerodha, Upstox,
Dhan and Fyers like the rest of those integrations — the new endpoints were
built from the brokers' public docs and tested against fixtures only.
- Portfolio lots and realized sales record the broker order id they came
from (`order_id`); the portfolio file keeps the list of synced fills
(`broker_fills`). Older files load unchanged.
- Practice mode for the paper-trading simulator ([docs/simulator.md](https://iamsaquib8.github.io/stockwise/simulator.html)):
- `sim replay`: trade a past market day by day with the future hidden. Six
built-in scenarios (`covid-crash-2020`, `gfc-2008`, `adani-2023`,
`dotcom-2000`, `bull-run-2021`, `rate-hikes-2022`), your own window
(`--from --to --symbols`) or a scenario file. Daily history is fetched once
and every view, `--json` included, stops at the replay date. Orders fill at
the next open with the backtest cost models; stops and targets are checked
on each day's range.
- `sim challenge`: goal-based challenges with hard and soft rules — beat the
index, keep drawdown under 10%, no position above 20%, always set a
stop-loss, SIP discipline vs timing, survive the crash — over a replay or
over live paper sessions. Rules are data (TOML).
- A 0–100 score and grade from risk-adjusted return against the benchmark
(Sharpe, Sortino), drawdown control, discipline and diversification —
never raw return alone — plus badges and streaks (`sim profile`).
- `sim card [--svg FILE]`: a shareable text or SVG result card. Paper
money only; every scorecard and card carries the disclaimer.
- Web dashboard at `/` of `stockwise serve`: indices, watchlist, portfolio
(P&L, XIRR, allocation, INR/USD totals, sortable columns), alerts, symbol
search and a price chart with period buttons and a keyboard-readable
crosshair. Refreshes every 30 seconds while visible; works at phone width;
follows the system light/dark theme; amounts in lakhs and crores for INR.
Plain HTML/CSS/JavaScript compiled into the binary — no build step or CDN.
Read-only. See [docs/server.md](https://iamsaquib8.github.io/stockwise/server.html#web-dashboard).
- `stockwise serve --open` opens the dashboard in your browser; the startup
log prints its address.
- Expense ratios for Indian mutual funds from AMFI's monthly TER disclosure:
`mf nav` and `mf history` show the plan's total expense ratio (and the
other plan's), `mf screen` adds a TER column, `--sort ter` (lowest first)
and `--max-ter PERCENT`. The disclosure is downloaded once a day (~3 MB
spreadsheet export) and cached in `cache/amfi_ter.json`; `--json` adds a
`ter` object with the April 2026 breakdown (base expense ratio, brokerage,
transaction costs, statutory levies). When AMFI can't be reached, NAVs and
returns still print with a note.
- BSE options: `options chain|pcr|maxpain SENSEX` (also `BANKEX`, `SENSEX50`,
`^BSESN`) and BSE stock options as `SYMBOL.BO`, from BSE's public
derivatives API with one session, spaced calls, a one-minute cache and a
clear error when BSE refuses scripted requests. NSE indices and stocks stay
on NSE, `-m us` on Yahoo.
- Lot sizes in option chains: inferred from NSE/BSE order-book quantities
(always whole lots), 100 for US contracts; `options chain` shows them and
adds `lot_size` to `--json`.
- `options payoff SYMBOL` — a strategy builder on the live chain. Legs as
`--leg "buy call 24500" --leg "sell call 24800"` (`atm±N` strikes and
`@PRICE` overrides too) or presets `--strategy bull-call|bear-put|straddle|
strangle|iron-condor|butterfly|covered-call --width W`. Shows each leg's
price (bid/ask mid, else last trade, flagged), net debit or credit, max
profit and loss (or unlimited), breakevens, probability of profit from IV
(lognormal), position greeks, and a braille P/L chart at expiry with an
optional `--today` curve (Black-Scholes). Sized by `--lots` and the lot
size; `--json` for scripts. Read-only — it never places orders.
- Technical alerts: `alert add SYMBOL rsi-below|rsi-above LEVEL [--period N]`,
`cross-above-sma|cross-below-sma|cross-above-ema|cross-below-ema DAYS`,
`breakout-high|breakdown-low DAYS`, `change-above|change-below PCT`,
`volume-spike MULTIPLE [--period N]` and `results-in DAYS`. They are
evaluated on daily bars (one chart request per symbol per check, updated
with the live quote) by `alert check`, `alert watch`, `dashboard`, the
`tui`, `serve` (`/api/alerts`) and `daemon longterm`; crosses fire only on
the crossing session, and `alert watch` notifies once per trading day.
`alert list` shows each condition in words. See
[docs/commands.md](https://iamsaquib8.github.io/stockwise/commands.html#alert-conditions).
- `stockwise calendar [SYMBOLS] [--days 30] [--holdings|--watchlist]` —
upcoming results, ex-dividend and payment dates, splits and bonus issues
for holdings and the watchlist, sorted by date with today and the next
7 days marked; `--json` / `--csv`. Yahoo Finance supplies earnings and
dividend dates for US and Indian stocks; for `.NS` / `.BO` stocks NSE board
meetings and corporate actions add filed results dates, splits and bonus
issues (one polite session per run, cached for a day, Yahoo-only when NSE
refuses).
- Data providers that read the exchanges' own websites, no API key needed:
`STOCKWISE_DATA_PROVIDER=nse` (or `data_provider = "nse"`), `bse`, and
`exchange` (NSE for `.NS` and NSE indices, BSE for `.BO`, Yahoo for the
rest). NSE serves quotes with circuit limits and delivery %, NSE index
quotes, today's intraday bars, up to five years of daily bars and search;
BSE serves quotes, intraday and daily bars and search by scrip id, code or
name. These are unofficial website endpoints: StockWise keeps one session per
site, at most three requests a second with jitter, retries timeouts and 5xx
with backoff, caches answers, and stops asking (with one notice) as soon as a
site refuses — Yahoo answers instead. See
[docs/data-providers.md](https://iamsaquib8.github.io/stockwise/data-providers.html#nse-and-bse) and
[docs/exchange-responses.md](https://iamsaquib8.github.io/stockwise/exchange-responses.html).
- `quote` shows **Circuit Limits** and **Delivery** when NSE answered;
`--json` adds `deliveryPercent`, `lowerCircuit`, `upperCircuit`, `--csv`
adds `delivery_percent`, `lower_circuit`, `upper_circuit` (empty otherwise).
- `movers`, `heatmap` and `screen` take `--universe nifty50|niftynext50|nifty100|banknifty|niftyit`
to scan an NSE index's current members (from NSE, whichever provider is set)
instead of the built-in 30 large caps, which stay as the offline fallback.
- `doctor` checks NSE and BSE separately in `exchange` mode.
- Global exchanges from one market table: `-m uk`, `de`, `jp`, `hk`, `sg`,
`ca`, `au` resolve bare tickers with the exchange's Yahoo suffix (`-m uk
VOD` → `VOD.L`, `-m jp 7203` → `7203.T`, `-m hk 5` → `0005.HK`). Each
exchange brings its currency (London prices in pence print as `118.20p`;
`£`, `€`, `¥`, `HK$`, `S$`, `C$`, `A$`), benchmark indices, sector indices or
ETFs where Yahoo has them (Japan, Hong Kong, Canada, Australia), a large-cap
universe for `movers`/`screen`, trading hours with lunch breaks and half
days, and 2026–27 holiday tables from the exchanges' own calendars (weekday
fallback with a warning for years not bundled). `tax` names listings it has
no rules for (rules exist for India, the US and the UK). See
[docs/markets.md](https://iamsaquib8.github.io/stockwise/markets.html).
- `markets --global`: one row per exchange's main index — NIFTY 50, S&P 500,
FTSE 100, DAX, Nikkei 225, Hang Seng, Straits Times, S&P/TSX, S&P/ASX 200 —
with whether it is open now and its local time.
- `markets` shows the India 10-year G-sec yield (CCIL, RBI fallback; `IN10Y`
in `--json`/`--csv`).
- `stockwise commodity [spot|futures|etfs|sgb]` and `-m commodity`: gold and
silver in ₹ per g / 10 g / kg and USD per ounce (COMEX front month at
USD/INR, before import duty and GST — clearly noted), COMEX futures with the
front contract month (MCX has no free feed), NSE gold and silver ETFs, and
Sovereign Gold Bonds with price, premium to gold spot, 2.5% interest yield
and maturity from NSE's SGB list — or, when NSE refuses, the outstanding
tranches without prices. With `-m commodity`, `GOLD`/`SILVER` resolve to
`GC=F`/`SI=F` for `history`, `chart`, `technical` and alerts. `--json`/`--csv`.
- `stockwise bonds [yields|list|calc]`: T-bill, G-sec and SDL benchmark yields
from CCIL's tenor-wise indicative yields (RBI's benchmarks as fallback;
cached 6 hours), RBI's list of outstanding Government of India securities,
and a calculator for price ↔ yield to maturity, accrued interest, dirty
price, duration and T-bill discount yields, looking securities up by NSE
symbol (`718GS2033`), name or ISIN. Individual bond prices are not shown:
no public source publishes them. See
[docs/commands.md](https://iamsaquib8.github.io/stockwise/commands.html#commodities--bonds).
- **WASM strategy plugins** (experimental, cargo feature `wasm-plugins`, off by
default): `backtest SYMBOL -s wasm:path/to/strategy.wasm` runs a
WebAssembly strategy in single, portfolio and walk-forward backtests, with
`-P name=value` and grid tuning, and `intraday -s wasm:...` uses it as an
extra vote. A plugin exports a small versioned ABI (`stockwise_abi_version`,
`stockwise_on_bar`, optional `stockwise_meta` / `stockwise_init`) and runs
in the wasmi interpreter with no imports (no WASI, files or network), a
64 MB memory cap and a fuel budget per call: a trap, an endless loop or bad
output is an error naming the bar, never a hang or a crash.
`backtest strategies` lists `.wasm` plugins in the strategies folder with
their name and ABI version. Example plugin (RSI mean reversion, identical
to the built-in `rsi`) in `examples/wasm-strategy-rsi/`; ABI and limits in
docs/backtesting.md ("WASM plugins").
### Changed
- Minimum supported Rust version is now **1.97** (was 1.88). StockWise
supports the latest stable Rust and the two releases before it. Prebuilt
binaries, Docker and Nix are unaffected.
- `alert list --json/--csv` adds `period` and `description`; `alert check
--json/--csv` adds `period`, `description`, `value`, `reference` and the
status `no_data` (new CSV columns come after the old ones). Existing
`alerts.json` files load unchanged. Quotes carry Yahoo's
`regularMarketTime`.
- In the Indian market a bare BSE scrip code resolves to BSE: `quote 500325`
is `500325.BO` (it used to become the invalid `500325.NS`).
### Security
- `serve` sends a strict Content-Security-Policy (no inline code, same-origin
only, Trusted Types), `X-Frame-Options: DENY`, `Referrer-Policy:
no-referrer` and same-origin COOP/CORP headers on every response, and
answers 403 to `/api/*` and `/metrics` requests a browser marks as
cross-origin (`Sec-Fetch-Site`). There is still no CORS. With
`STOCKWISE_API_TOKEN` set, the dashboard asks for the token and keeps it in
session storage, sending it only as an `Authorization` header.
## [0.4.0] - 2026-10-21
Not released separately: these changes first shipped in 0.5.0.
Everything on the 0.3 roadmap: new markets and asset classes, a lot-based
portfolio with Indian tax rules, more brokers, a backtesting engine, a
terminal dashboard and a local API, packaging for every platform, and
Hindi. Command-line forms from 0.3 keep working.
### Added
- `stockwise tui` — a full-screen, read-only live dashboard: indices,
watchlist, portfolio P&L, triggered alerts and a chart of the selected
symbol (periods 1d–5y), with symbol search to add to the watchlist. Refresh
interval with `-i` (default 30s); works at 80×24 and degrades on smaller
terminals; honours `NO_COLOR`. See [docs/tui.md](https://iamsaquib8.github.io/stockwise/tui.html).
- `stockwise serve` — a read-only JSON API (`/api/quote`, `/api/history/{symbol}`,
`/api/markets`, `/api/search`, `/api/portfolio`, `/api/watchlist`,
`/api/alerts`), `/healthz`, and Prometheus `/metrics` (request counts and
latency, Yahoo errors, cache hits, and the intraday daemon's open paper
positions, day P&L and kill switch). Loopback only unless `--allow-remote`
with a `STOCKWISE_API_TOKEN` bearer token. See [docs/server.md](https://iamsaquib8.github.io/stockwise/server.html).
- Cargo features `tui` and `server` (both default); `--no-default-features`
builds without ratatui and axum.
- `--json` on 17 more commands: `portfolio`, `watch`,
`alert list`/`check`, `technical`, `risk`, `backtest`, `screen`, `movers`,
`sectors`, `compare`, `correlate`, `sip`, `whatif`, `returns`, `dividends`,
`tax` and `sim history`, alongside `quote`, `history`, `markets` and
`search`. Keys are snake_case; shapes are documented in docs/commands.md.
- `--csv` (RFC 4180, header row first) for tables: `portfolio`, `watch`,
`alert list`/`check`, `movers`, `screen`, `sectors`, `history` bars,
`backtest` trades, `compare`, `correlate`, `returns`, `tax`, `quote`,
`markets` and `search`. `--json` and `--csv` are mutually exclusive.
- Config file (`config.toml` in the platform config directory, or
`STOCKWISE_CONFIG`) for `market`, `ai`, `model`, `ollama_host`,
`redis_url`, `home`, and a default `-p` per command under `[periods]`.
Precedence: flag > environment variable > config file > default, and
`--help` shows defaults from the file.
- `stockwise config [show]`, `config path` and `config init [--force]`.
- `--no-ai` turns the AI section off when `STOCKWISE_AI` or the config file
turn it on.
- `doctor` reports the config file and whether it parsed.
- **More brokers (experimental — untested against a live account):** Zerodha
Kite Connect, Upstox, Dhan and Fyers via `trade --broker ` (or
`STOCKWISE_BROKER`): holdings (synced into `portfolio`), positions, order
book, and orders. Built from each broker's public API docs and tested against
a local mock server. They stay read-only until you add
`"enable_orders": true` to the broker's config file. `trade login` handles the
daily browser login (Kite, Upstox, Fyers) or a pasted Dhan token.
- **Stop-loss and GTT orders:** `trade buy|sell ... --trigger T [--limit P]`
places SL-M / SL orders; `trade gtt buy|sell|list|cancel` manages single-leg
GTTs (Angel One, Kite, Dhan Forever Orders, Fyers). Triggers are checked
against the side, the limit and the last price before anything is sent.
These are experimental on Angel One too and need `"enable_orders": true`.
- **OS keyring for broker secrets** (macOS Keychain, Windows Credential
Manager, Secret Service). Secrets in an existing `angel_config.json` move
into the keyring automatically; `trade config --keyring` does it now. Without
a keyring (headless Linux) the owner-only file is used; `STOCKWISE_KEYRING=off`
forces that. `doctor` shows where each broker's secrets live.
- `import groww`, `import upstox`, and `import cas` (CAMS / KFintech / NSDL /
CDSL statements as casparser JSON or CSV). Mutual funds are stored as
`MF:`. Groww's ISIN-only exports are mapped to NSE tickers.
- **Trading costs in backtests**, on by default: `--costs in-delivery`
(brokerage, STT, exchange and SEBI fees, GST, stamp duty, DP charge),
`in-intraday`, `us`, `crypto`, `fx`, or `none`, plus `--slippage-bps` and
`--brokerage`.
Reports show net and gross returns and the money paid in costs.
- `backtest portfolio SYMBOLS...` — one strategy across several stocks with
`--weights equal|inverse-vol|0.5,0.3,...` and `--rebalance monthly|quarterly`;
combined equity curve, CAGR, drawdown, Sharpe/Sortino, exposure and
per-symbol contribution.
- `backtest walkforward SYMBOL --train 1y --test 3mo` — tunes parameters on
rolling train windows, reports only out-of-sample results, and flags
likely overfitting.
- **Strategy files**: entry/exit rules in TOML over the built-in indicators
(`and`/`or`/`not`, crossovers, arithmetic, stop-loss and take-profit,
parameters and a walk-forward grid). Load with `-s NAME` from a
`strategies/` folder next to the config file, or `-s file:PATH`; override parameters
with `-P name=value`. The six built-ins ship as files in
`examples/strategies/`, with two more examples.
- `backtest strategies` lists and checks strategy files.
- `intraday -s STRATEGY` adds a strategy file as an extra signal.
- `--json` on every form of `backtest` (and `--csv` for their trades); the
`backtest --json` shape changed with the new engine (see
docs/backtesting.md).
- Docs: [docs/backtesting.md](https://iamsaquib8.github.io/stockwise/backtesting.html).
- `stockwise man` prints the `stockwise.1` man page; `stockwise man --dir DIR`
writes it plus one `stockwise-.1` page per command. The pages are
generated from the CLI definition and ship in every release archive under
`man/`.
- Docker image `ghcr.io/iamsaquib8/stockwise` for linux/amd64 and linux/arm64,
published on every release. It runs as a non-root user and keeps data in the
`/data` volume.
- Nix flake: `nix run github:iamsaquib8/stockwise`, `nix profile install`, and
`nix develop` for a dev shell. Installs man pages and shell completions.
- Package manifests in `packaging/` for Homebrew, Scoop, winget and the AUR
(`stockwise-bin`), filled in from each release's checksums by
`packaging/render.sh`. Releases push the formula to the
`iamsaquib8/homebrew-tap` tap when a `HOMEBREW_TAP_TOKEN` secret is set, and
attach `stockwise.json` so `scoop install ` works.
- `mf` — Indian mutual funds from AMFI's public NAV file: `mf search`, `mf nav`,
`mf history` (NAV chart, 1M–10Y trailing returns, since-inception CAGR, max
drawdown) and `mf sip` (monthly SIP with XIRR on the actual purchase dates).
NAV history comes from api.mfapi.in; both are cached in the data directory
for 12 hours. `--json` on every subcommand.
- `mf screen` ranks a category's growth-option schemes by 1Y/3Y/5Y return
(bare `mf screen` lists categories). Expense ratios are not in AMFI's feed,
so they are not shown for Indian schemes.
- `mf overlap A B` — shared top holdings, holdings and sector overlap, and
expense ratios for funds Yahoo Finance has holdings for (mainly US ETFs and
mutual funds). Indian schemes and NSE ETFs report "no holdings data".
- `options chain` (OI, change in OI, volume, IV, delta, gamma, theta, vega,
rho), `options pcr` (put/call ratio and open-interest walls) and
`options maxpain`. Indian symbols use NSE's option chain (index options and
F&O stocks), US symbols (`-m us`) use Yahoo Finance. Black-Scholes greeks
with an implied-volatility solver, valued at the data timestamp.
- `stockwise alert watch [--interval 60] [--once]` — checks alerts while
each alert's market is open and announces every trigger once (re-arms when
the price moves back, at most once a day; remembered across restarts).
- Notifications for `alert watch` and the intraday daemon: Telegram, Slack
and Discord webhooks, email over SMTP (rustls), and desktop pop-ups with
the `desktop-notify` build feature. Configured with `STOCKWISE_*`
environment variables; `stockwise notify` shows them and `stockwise notify
test` sends a test message. Secrets are never printed or sent anywhere but
their own endpoint.
- Exchange clock: IST for NSE and US Eastern (with daylight saving) for
NYSE, with the NSE 2026 and NYSE 2026–2028 holiday calendars and NYSE
early closes.
- `sim settle --now` settles today's session before the close.
- **Crypto and forex markets.** `-m crypto` resolves bare coins (`BTC` →
`BTC-USD`; `--quote-currency inr` / `STOCKWISE_QUOTE_CURRENCY` for `BTC-INR`)
and `-m fx` resolves pairs (`USDINR`, `usd/inr` → `USDINR=X`). Prices show
4 decimals for FX and up to 8 for low-priced coins; `quote` notes 24/7
(crypto) and weekday-only (FX) trading. `markets --crypto --fx` adds top
coins and major pairs. Session-, sector- and fundamentals-based commands
(`sectors`, `screen`, `intraday`, `daemon`, ...) explain that they need a
stock market instead of running on coins.
- **Data providers.** `STOCKWISE_DATA_PROVIDER=alphavantage|massive|twelvedata`
with the provider's API key routes quotes, daily history and search through
Alpha Vantage, Massive (formerly Polygon.io) or Twelve Data. Yahoo fills the
fields and symbols a provider lacks and takes over on errors or exhausted
free tiers. `doctor` shows which provider answered; `quote` names it, and
`quote --json` adds `dataSource`. See docs/data-providers.md.
- **Cloud AI, opt-in.** `STOCKWISE_AI_PROVIDER=openai|anthropic|gemini` with
`OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `GEMINI_API_KEY` uses a hosted model
for `report` and `--ai` (same prompts as Ollama, which stays the default).
The first request of a run prints a notice that data is being sent;
`doctor` shows provider and model, never the key. `STOCKWISE_MODEL`
overrides the default model (`gpt-6.1-sol`, `claude-opus-5-5`,
`gemini-3.8-flash`). See docs/ai.md.
- **Hindi (partial).** `--lang hi`, `STOCKWISE_LANG=hi`, or a `hi_*` locale
translates help group titles, the main command descriptions, `quote`,
`markets`, `dashboard`, `portfolio`, `search`, `doctor` and common errors.
Lakh/crore amounts read `₹16.27 लाख करोड़`. `--json` output is unchanged.
- `doctor` shows the active language and where it came from.
- **Lot-level holdings.** Every purchase is a lot with its own quantity, price,
date and source. `portfolio add ... --date YYYY-MM-DD` records the real
purchase date; `portfolio lots SYMBOL` shows a holding's lots, sales and
dividends, and `--lot N --date` corrects a purchase date.
- `portfolio sell SYMBOL SHARES [PRICE] [--date]` sells oldest lots first
(FIFO) and keeps the realized P&L for `tax`.
- Portfolio XIRR: `portfolio` shows a money-weighted annual return per holding
and for the whole portfolio (including closed positions and dividends).
- `portfolio actions [SYMBOL] [--apply]` lists splits, bonus issues and
dividends since you bought (from Yahoo) and, with `--apply`, adjusts lots and
records dividends. Idempotent; nothing changes without `--apply`. `divcal`
shows the dividends received.
- Named portfolios: global `--portfolio NAME` / `STOCKWISE_PORTFOLIO`, stored as
`portfolios/NAME.json` with their own wealth history; `portfolio list`.
- Mixed-currency portfolios: INR and USD holdings are totalled in one currency
at the live `USDINR=X` rate (`portfolio --base inr|usd`; default: the
market's currency). Also in `dashboard`, `wealth`, `rebalance` and the
long-term daemon.
- `import` reads a purchase/trade date column and a buy/sell column, so a lot
list or a tradebook (e.g. Zerodha Console) imports with real dates, realized
sales and splits applied as they happened. A holding that already has lots
from elsewhere is only rebuilt with `import --replace`.
### Changed
- `backtest` with too little history is an error (exit 1) instead of a
message with exit 0.
- `movers` leaves out stocks without a change today instead of ranking them
at 0%; `correlate` shows N/A instead of 0 when two series can't be compared.
- `watch` lists symbols that have no live quote (as N/A) instead of hiding
them; `dividends` errors when there is no live price instead of dividing by
zero.
- An invalid config file stops every command except `config`, `doctor` and
`completions`, with an error naming the file and the key.
- `quote`, `history` and `markets` gather their data through a shared layer
(`src/views.rs`) that the TUI and the server also use, so `--json` and the
HTTP API return the same shapes.
- `portfolio --json/--csv` and `tax --json/--csv` follow the lot-based
portfolio: holdings carry `xirr_percent`, totals are in the base currency
(`--base`), and holdings without an exchange rate are listed in
`unconverted`. `tax --json` reports this tax year's sales, each open
holding's short- and long-term gain, and the estimate with and without
selling everything today.
- The config file also covers `lang`, `quote_currency`, `portfolio`,
`ai_provider`, `data_provider` and a `[notify]` table for notification
channels (API keys stay in environment variables). `config show` and
`doctor` warn when the file holds secrets and other users can read it.
- `options` is now a command group. `stockwise options SYMBOL` still shows
expected moves, also available as `options moves SYMBOL`. `mf` and
`options` are listed under a new "Funds & F&O" help group.
- `quote`, `markets` and `dashboard` show `N/A` for a missing price or range
instead of `0.00`.
- AI section titles name the provider (`AI Analysis (Ollama)`) instead of
always saying Ollama.
- EUR, GBP and JPY amounts show `€`, `£` and `¥` instead of `$`.
- `sim settle` replays each session's own 5-minute bars (falling back to
hourly, then daily bars for old sessions): the stop or target that was hit
first decides each trade, otherwise it exits at the 15:15 bar. It used to
settle every session, from any date, at today's price. Today's session is
refused until the market closes.
- `sim start` refuses to open a session when the market is closed for the
day (weekend, holiday, or after the close).
- `daemon intraday` follows the exchange clock instead of the machine's time
zone, skips exchange holidays, and with `-m us` trades NYSE hours.
- Intraday plans (`intraday`, `sim start`, the daemon) never need more than
the capital in total, and the daemon's 30% per-position limit is applied.
- `tax in|us|uk` classifies each lot by its own purchase date, shows realized
gains (sales this tax year) separately from unrealized ones, and applies the
set-off rules: in India a short-term loss offsets STCG then LTCG, a long-term
loss only LTCG, before the ₹1.25 lakh exemption; losses left over are shown
as carry forward. Holdings in another currency are listed but not included.
UK CGT uses the 24% higher rate (was 20%).
- `portfolio.json` has a new format (version 2). Older files are upgraded on
first load — each holding becomes one lot dated when it was added, flagged as
an estimated date — and the original is kept once as `portfolio.json.bak`.
StockWise 0.3.0 can still read the new file.
- `import` without dates and `trade holdings` keep existing lots and only adjust
the quantity, instead of overwriting the average cost.
- `portfolio list` now lists your portfolios; use `portfolio` or
`portfolio show` for holdings.
- `import` exits with an error when a file has no usable rows, an unrecognized
date, or nothing it could apply.
- `portfolio sell`, `import` and `trade holdings` refuse to match shares while
a split or bonus isn't applied to your lots yet, and point to
`portfolio actions --apply`.
- `wealth` snapshots are always in the market's currency.
- Every order type and broker goes through one safety flow
(`place_live_order`): exact instrument, price checks, preview,
`max_order_value`, `--dry-run`, typed `yes` / `--yes`. Angel One MARKET and
LIMIT orders behave as in 0.3.
- `trade` tables show N/A instead of ₹0.00 when the broker sends no price.
- `docs/angel-one.md` is now `docs/brokers.md`.
### Fixed
- `options` (expected moves) treated a missing price as 0; it now reports an
error.
- `daemon intraday` exited on the first network error, losing the day's
paper positions. Errors are now logged and retried with backoff; the state
is saved to `daemon_state.json` (`daemon_state_us.json` for `-m us`) every
tick and resumed after a restart.
- Ctrl+C in `daemon intraday` now squares off at the last prices, records
the day in the simulator history and exits 0, as the banner promised.
- Positions closed at the second target were not counted by the 3% daily
loss kill switch; the kill switch also no longer re-fires every tick.
- The daemon kept waiting forever after a day with no qualified trades.
- Daemon P&L right after a first-target partial booking counted the booked
half at the current price instead of the target price.
- Intraday plans could put a long's stop above the entry (support above the
price) or a short's below it; such levels are now ignored.
- The daemon monitors positions from the moment it enters them, not only
from 15 minutes after the open, and ignores zero quotes.
- Tax holding periods used the day a holding was added to StockWise, and adding
shares kept the first purchase's date for all of them.
- Portfolio totals added INR and USD amounts together.
- `portfolio sell`, `lots`, `remove` and `actions` find a holding saved as typed
(e.g. `AAPL` while the market is India).
- Angel One P&L and position quantities read the documented fields
(`profitandloss`, `netqty`); they showed 0 before.
- **Backtests fill at the next bar's open** instead of the signal bar's close,
which used a price not yet known when the signal fired. `--fill close`
restores the old behaviour.
- Backtests trade whole shares from a starting capital (`--capital`, default
100,000), and Sharpe/Sortino are annualised from daily returns instead of
a per-trade ratio. Max drawdown uses the daily equity curve.
- Backtest bars with any missing price are dropped together; before, a gap in
one series could misalign opens, highs, lows and closes.
## [0.3.0] - 2026-10-21
Usability and safety release. Command-line forms from 0.2 keep working.
### Added
- `stockwise doctor` — checks the data directory, Yahoo reachability, Redis,
the Ollama model, and broker config in one screen.
- `stockwise completions ` for bash, zsh, fish, PowerShell and elvish.
- `--json` output for `quote`, `history`, `markets` and `search`.
- Grouped top-level `--help` with examples.
- `trade buy/sell/limit --dry-run` and `--yes`; optional `max_order_value` cap.
- Environment variables: `STOCKWISE_MARKET`, `STOCKWISE_HOME`, `STOCKWISE_AI`,
`STOCKWISE_REDIS_URL`, and the standard `OLLAMA_HOST`.
- `install.sh` one-line installer, `cargo binstall` support, SHA-256 checksums
and a Linux ARM64 binary on every release.
- Dashboard first-run hints when you have no portfolio, watchlist or alerts.
### Changed
- **Live orders need confirmation.** Every order resolves the exact NSE `-EQ`
instrument, prints a preview, and requires typing `yes` (or `--yes`).
- **Local AI is opt-in** (`--ai` / `STOCKWISE_AI=1`). It made analysis commands
take 40–60 seconds. `report` still always uses AI.
- Actions are real subcommands and value lists (`portfolio`, `watch`, `alert`,
`sim`, `trade`, `screen`, `backtest -s`, `tax`, `import`, `export`,
`daemon`): typos are rejected with suggestions, and help documents every
argument. `portfolio`, `watch` and `alert` show their list when run bare.
- Errors go to stderr with exit code 1 (unknown symbol, no data, bad file);
invalid arguments exit 2. Previously many printed a message and exited 0.
- Not-found errors name the symbol and suggest `-m us` for US tickers.
- `forecast` and `stoploss` accept `-p` like the other commands.
- `sim reset` asks for confirmation and keeps a timestamped backup.
- Local data files are written atomically, and parse errors name the file.
- Redis has 300/500 ms timeouts and no longer prints a banner on every run.
### Fixed
- `M&M` and other symbols/searches containing `&` were cut off (`M&M` showed
Macy's); all request URLs are now percent-encoded.
- `risk`, `forecast`, `options` and `deep` annualized weekly or monthly bars as
if they were daily (volatility and Sharpe about 2.2x too high with `-p 1y`).
- SIP "annualized" return was total return divided by years; it is now XIRR.
- A missing quote was valued at 0: portfolio showed -100%, `tax` booked a full
loss, `harvest` suggested selling, `wealth` saved a fake crash, and alerts
fired. These holdings now show N/A and are left out.
- Re-running `import` or `trade holdings` doubled positions and reset dates;
broker symbols like `RELIANCE-EQ` became `RELIANCE-EQ.NS`.
- Angel One credentials were saved world-readable; they are now owner-only
(0600) and existing files are tightened. `trade setup` no longer suggests
passing secrets as command-line arguments.
- Broker holdings/positions/order-book failures showed "No holdings found"
instead of an error; broker calls now have timeouts.
- Index, FX and crypto symbols (`^NSEI`, `USDINR=X`, `BTC-USD`) got `.NS`
appended in the Indian market.
- The Yahoo session token is validated; a rate-limit page used to be stored
as the token and break every later request. Retries honour `Retry-After`.
- Volume bars drew the same glyph on every row instead of stacking.
- `--help` said the default market was US (it is India).
- `screen bluechip` used a $100B threshold for INR market caps.
- `dashboard` labelled indices by position, not symbol.
- `tax` printed a fixed "FY 2025-26"; it now shows the current tax year.
- `TATAMOTORS.NS` (no longer quoted) replaced with `TMPV.NS` in the universe.
- Piping output into `head` no longer panics.
## [0.2.0] - 2026-06-24
First public release — published to crates.io with prebuilt binaries.
### Added
- **Packaging & distribution**: published to crates.io (`cargo install stockwise`),
prebuilt binaries for macOS (x86_64/arm64), Linux, and Windows on each release.
- **CI**: GitHub Actions running `rustfmt`, `clippy -D warnings`, and the full test
suite (213 tests) on Linux/macOS/Windows plus an MSRV (1.88) job.
- **Project docs**: `LICENSE` (MIT), `DISCLAIMER.md`, `ROADMAP.md`, `CHANGELOG.md`,
`CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, and issue/PR templates.
- Multi-source news collation for `news`, `insider`, `ipo`, and `sentiment`.
### Changed
- Migrated to Rust edition 2024; MSRV is now 1.88 (the codebase uses let-chains).
- Codebase is `rustfmt`- and `clippy`-clean (enforced in CI).
- `risk` now defaults to a `1y` period so it has enough data to run.
- Backtest Sharpe is now a per-trade ratio (no longer annualized from a
trade-level equity curve, which produced meaningless values).
### Fixed
- Corrected README counts (57 commands, 213 tests; was "89 tests / 50+").
- **Short trades**: `sim` status/settle and the daemon now compute P&L and
WIN/STOP/TARGET verdicts correctly for SELL positions (previously inverted).
- **Tax**: LTCG/STCG classification now compares real dates instead of
year+month, so the day-of-month no longer flips ~11-month holds to long-term.
- **Daemon risk**: realized P&L from the booked half at T1 is now tracked, so
the daily-loss kill switch and EOD totals are no longer understated; wind-down
stop tightening and breakeven moves now handle short positions.
- **Panics**: `vwap`/`atr` no longer index out of bounds on ragged OHLCV data;
every `partial_cmp().unwrap()` sort is now NaN-safe.
- **Division by zero / NaN**: guarded in `daily_returns`, `value_at_risk`,
backtest returns, `sip`, `stoploss`, `rotation`, dividend/harvest P&L, and
swing-point detection.
- `markets` now matches index labels to quotes by symbol (Yahoo can reorder
results); `whatif` pairs timestamps with closes so the buy price stays aligned.
- Live order placement warns instead of going silent when no order id is
returned, preventing accidental resubmits; `triggerprice` is only sent on
stop-loss order types.
- `strip_think_tags` (AI output) no longer corrupts text when a stray closing
tag precedes the opening one.
## [0.1.0] - Unreleased
Initial development. Not published. Highlights that landed during this phase:
### Added
- 57 commands spanning analysis, screening, trading bots, live trading, and
portfolio management for US and Indian (NSE/BSE) markets.
- Daemon workers: continuous intraday bot (9 strategies, Kelly sizing, risk
controls) and a daily long-term worker (6-pillar scoring + Monte Carlo).
- Paper-trading simulator with confidence rating.
- Live trading via Angel One; broker import for Zerodha Kite, INDmoney, and CSV.
- Local AI integration via Ollama, auto-injected into 6 commands.
- Redis response caching with in-memory fallback.
- Rate limiting, retry-with-backoff, and response caching for the Yahoo Finance
data source.
- Multi-timeframe and braille terminal charts; `-p/--period` flag on chart commands.
- `deep` command — full report on a single stock in one shot.
[Unreleased]: https://github.com/iamsaquib8/stockwise/compare/v0.8.1...HEAD
[0.8.1]: https://github.com/iamsaquib8/stockwise/compare/v0.8.0...v0.8.1
[0.8.0]: https://github.com/iamsaquib8/stockwise/compare/v0.7.0...v0.8.0
[0.7.0]: https://github.com/iamsaquib8/stockwise/compare/v0.5.0...v0.7.0
[0.6.0]: https://github.com/iamsaquib8/stockwise/compare/v0.5.0...d9f52f2
[0.5.0]: https://github.com/iamsaquib8/stockwise/compare/v0.3.0...v0.5.0
[0.4.0]: https://github.com/iamsaquib8/stockwise/compare/v0.3.0...c2d75af
[0.3.0]: https://github.com/iamsaquib8/stockwise/compare/5bbf8ec...v0.3.0
[0.2.0]: https://github.com/iamsaquib8/stockwise/tree/5bbf8ec
[0.1.0]: https://github.com/iamsaquib8/stockwise/commits/5bbf8ec
---
# Contributing to StockWise
Thanks for your interest! Contributions of all sizes are welcome — bug reports,
docs, new commands, broker integrations, and strategy ideas.
## Ground rules
- Be kind. See the [Code of Conduct](https://github.com/iamsaquib8/stockwise/blob/main/CODE_OF_CONDUCT.md).
- StockWise is a financial tool. Read the [Disclaimer](https://iamsaquib8.github.io/stockwise/disclaimer.html) — never
hard-code real broker credentials, API keys, or personal portfolio data into
the repo, tests, or fixtures.
## Where to start
Issues labelled [good first issue](https://github.com/iamsaquib8/stockwise/labels/good%20first%20issue) are small and
self-contained: each says what to change, links the code, and lists what
"done" looks like. Comment on one to claim it so two people don't work on the
same thing. Larger ideas are on the [roadmap](https://iamsaquib8.github.io/stockwise/roadmap.html); open a discussion or
issue before starting one.
## Getting set up
```bash
git clone https://github.com/iamsaquib8/stockwise
cd stockwise
cargo build
cargo test
```
Requires Rust **1.97+** (edition 2024). We support the latest stable release and the two before it. Optional at runtime: a local
[Ollama](https://ollama.com) server for AI features and Redis for caching — both
degrade gracefully when absent.
Try your build without touching your real portfolio by pointing StockWise at a
scratch data directory:
```bash
export STOCKWISE_HOME=$(mktemp -d)
cargo run -- doctor
cargo run -- quote RELIANCE TCS
cargo run -- portfolio add TCS 10 3500 && cargo run -- portfolio
```
## Finding your way around
[docs/architecture.md](https://iamsaquib8.github.io/stockwise/architecture.html) maps the source tree. In short:
`src/main.rs` defines the CLI, `src/commands/` holds one file per command area
(`overview`, `technicals`, `markets`, `holdings`, `trade`, ...), and the
analysis engines (`technical.rs`, `tax.rs`, `rebalance.rs`, `verdict.rs`,
`backtest.rs`, `intraday.rs`, `longterm.rs`) are pure functions with unit tests.
The tools that `stockwise mcp` and `stockwise ask` give to AI assistants are in
`src/tools/`, the MCP server is `src/mcp.rs` and the `ask` loop is `src/ask.rs`.
Other folders: `docs/` (the documentation pages), `site/` and `site/build.py`
(the docs site), `changelog.d/` and `scripts/changelog.py` (the changelog),
`packaging/` (installers and package recipes), `skills/` (the Agent Skill).
## Before you open a PR
Run the same checks CI does — all of them must pass:
```bash
cargo fmt --all # format
cargo clippy --all-targets -- -D warnings # lint (must be clean)
cargo clippy --all-targets --no-default-features -- -D warnings # minimal build (no tui/serve/mcp)
cargo clippy --all-targets --features wasm-plugins -- -D warnings # WASM strategy plugins
cargo test # all tests green
cargo test --features wasm-plugins
python3 scripts/changelog.py check # changelog fragments are valid
```
If you change anything under `docs/`, `site/` or the top-level Markdown pages,
also run `python3 site/build.py` (it needs [mdBook](https://rust-lang.github.io/mdBook/))
and fix every warning and broken link it reports.
- Keep PRs focused: one issue per PR, ideally under about 400 changed lines, with
`Closes #N` in the description. Split larger work into a series.
- Review your own diff before asking for review, and never merge with failing CI.
- Add tests for new behavior — CLI parsing tests live in `src/main.rs`, pure logic tests next to the code.
- Update `docs/` and `README.md`, and add a changelog fragment (see "Changelog entries"), when behavior
changes.
- New commands: add the variant to `Commands` and to the `GROUPS` help table in
`src/main.rs` (a test checks every command is grouped), write `cmd_` in
the matching `src/commands/*.rs` file, and document it in `docs/commands.md`.
Every `stockwise ...` command you write in backticks in `docs/cheatsheet.md`
and `skills/stockwise/SKILL.md` must parse: a test checks them.
- New AI tools: a tool is read-only and calls the function behind a command's
`--json` output. Add it to `src/tools/`, list it in
`packaging/mcpb/manifest.json` (a test compares the two lists) and in the tool
table in `docs/ai.md`. The tool count (`stockwise mcp --list-tools`) is written in
`README.md`, `docs/commands.md`, `docs/ai.md`, `docs/architecture.md` and
`packaging/pypi/README.md`: update it.
- Errors: return `anyhow` errors (`bail!`) instead of printing and returning
`Ok(())`, so scripts see a non-zero exit code. Empty states (nothing in the
portfolio yet) are not errors.
- Anything that can place a live order or GTT must go through `place_live_order`
in `src/commands/trade.rs` — never call `Broker::place_order` or
`Broker::gtt_create` directly.
## Good first contributions
- `--json` / `--csv` output for another command (`analyze`, `peers`, `news`,
`picks`, ...) — build rows once and render them with `src/output.rs`; see
`cmd_portfolio_show` or `cmd_risk` for the pattern, and add the shape to
`docs/commands.md`.
- More symbols in the India/US universes in `src/market.rs`, or a refresh when
a ticker changes.
- Unit tests for an indicator in `src/technical.rs` against a known reference.
- Docs: a worked example for a command you use.
## Adding a data source or broker
Network access goes through `src/api.rs` (rate limiting, retry, caching). Broker
integrations implement the `Broker` trait in `src/brokers/` — see `zerodha.rs`
for a compact example. Keep secrets in `secrets::Secret` fields (stored in the OS
keyring via `src/brokers/secrets.rs`), never in source, logs or error messages.
Test against `brokers::mock::MockServer` with fixtures from the broker's docs, and
label anything not yet run against a live account "experimental". Give each read
endpoint a `Shape` (the fields the parser reads and the documented ones) and check
your fixtures with `mock::assert_fixture_shapes`, so `trade selftest` can tell
testers when a live response differs. Anything that only reads from a real account
(`trade selftest`, `trade sync`) must run on a `brokers::readonly::ReadOnly` session.
## Changelog entries
Do not edit `CHANGELOG.md` in a pull request: add one small file to
`changelog.d/` per user-visible change, so pull requests never conflict. Name it
`..md` with a type of `added`, `changed`, `deprecated`,
`removed`, `fixed`, `performance` or `security`, and put one bullet in it:
```markdown
- `tax in` counts gold ETFs as non-equity, so they are no longer taxed as listed
shares ([#87](https://github.com/iamsaquib8/stockwise/issues/87)).
```
- One to three lines, in plain language, saying what the user can now do or what
was wrong. Start with the command name in backticks.
- Link the issue.
- No marketing words ("powerful", "2.0", "institutional-grade"), no formulas and
no nested lists. Details belong in `docs/`.
- Credit outside contributors: `Thanks [@name](https://github.com/name).`
`python3 scripts/changelog.py check` validates the files (CI runs it). Maintainers
cut a release with `python3 scripts/changelog.py release X.Y.Z`; see
[changelog.d/README.md](https://github.com/iamsaquib8/stockwise/blob/main/changelog.d/README.md).
## Commit messages
Use a type and an optional scope, then a short imperative summary under about 72
characters: `fix(tax): treat gold ETFs as non-equity`. Types: `feat`, `fix`,
`docs`, `test`, `refactor`, `perf`, `build`, `ci`, `chore`. Explain the why in the
body when it isn't obvious.
## How this project is built
StockWise is maintained by one person with help from AI coding assistants
(commits made with one carry a `Co-Authored-By` line). The same rules apply to
every change, however it was written: a human reads the diff, CI must pass, new
behavior needs tests, and numbers that affect money (tax, XIRR, imports) are
checked against a hand calculation or a fixture. If you use an assistant for a
contribution, that's fine — you are responsible for understanding and testing
what you submit.
## Reporting bugs / requesting features
Use the [issue templates](https://github.com/iamsaquib8/stockwise/issues/new/choose).
For open-ended ideas, start a
[Discussion](https://github.com/iamsaquib8/stockwise/discussions).
---
# Architecture
## Source Layout
```
src/
main.rs CLI definition (clap): 76 top-level commands, the grouped `--help` table, parse tests
commands/ Command handlers — one file per area, each `cmd_*` re-exported
mod.rs Shared imports and helpers (period → interval, symbol resolution)
overview.rs quote, markets, dashboard, search, doctor
setup.rs config path/show/init
fundamentals.rs analyze, compare, peers, dividends, insider, deep, score (`fundamental score`)
research.rs events, insights, verdict (printing; the data is in `research.rs`, `events.rs`, `verdict.rs`)
technicals.rs technical, history, chart, timeframes, patterns, support, fibs, volume, gaps, stoploss
risk.rs risk, correlate, returns, forecast, options moves
funds.rs mf search, nav, history, sip, screen, overlap (TER in nav/history/screen)
fno.rs options chain, pcr, maxpain, oi, payoff
commodities.rs commodity spot, futures, etfs, sgb
bonds.rs bonds yields, list, calc
markets.rs movers, sectors, news, sentiment, screen, picks, earnings, rotation, matrix, surprise, heatmap, sectorcmp
ipo.rs ipo (live board, one issue's analysis)
calendar.rs calendar (results, dividends and splits for holdings and watchlist)
backtests.rs backtest (single, portfolio, walkforward, strategies)
strategies.rs sip, whatif, intraday, longterm, report
portfolio.rs portfolio (show, add, sell, lots, actions, list, remove, target, xray) and rebalance;
valuation in a base currency
(`value_holdings`: market quotes + `offmarket` fund NAVs and SGB prices)
planning.rs tax export, tax advance, portfolio benchmark (history, FX, sectors), goal sip/retire/education
holdings.rs watch, alert, import (Kite, INDmoney, generic CSV and `.xlsx`, tradebooks), export, wealth,
harvest, tax, divcal, pcorr
imports.rs Groww / Upstox and CAS (casparser JSON/CSV) import parsers
sim.rs paper-trading simulator (settle replays intraday bars)
practice.rs sim replay / challenge / profile / card (practice mode)
worker.rs daemon intraday / longterm loops
alerting.rs alert watch loop, notify status/test
trade.rs Live orders for every broker: place_live_order (validation, preview, cap, confirmation)
broker_selftest.rs trade selftest: read-only checks + redacted report for broker test reports
broker_sync.rs trade sync (trade book → lots/sales, idempotent) and trade gtt list status changes
interfaces.rs tui and serve (behind the `tui` / `server` cargo features)
assistant.rs ask and mcp (the MCP server behind the `mcp` cargo feature)
views.rs Data behind quote/history/markets/portfolio/watchlist/alerts as structs,
shared by the CLI printers, the TUI and the server; `DataSource` trait
tui/ `stockwise tui`: app.rs (state + key handling, no I/O), ui.rs (ratatui
drawing), mod.rs (event loop, fetches)
server/ `stockwise serve`: axum routes, bearer auth, Prometheus metrics; web.rs serves
the dashboard (web/, embedded with include_str!) and sets CSP/security headers
daemon_status.rs Reads the intraday daemon's daemon_state.json for /metrics
tools/ The 35 read-only tools for AI agents, shared by `mcp` and `ask`: mod.rs (Tool
definitions, JSON schemas from typed Param lists, the result envelope with
the "not financial advice" note and size limit), args.rs (argument checks),
handlers.rs (each tool calls the function behind the command's --json),
tests.rs (also checks that the Claude Desktop manifest lists every tool)
mcp.rs, mcp/ `stockwise mcp`: rmcp ServerHandler (tools, resources, prompts), stdio and
Streamable HTTP transports (`mcp` feature); mcp/tests.rs
ask.rs, ask/ `stockwise ask`: the tool-calling loop in each provider's dialect (Ollama
/api/chat, OpenAI Responses, Anthropic Messages, Gemini generateContent);
ask/tests.rs
store.rs Atomic JSON persistence, data directory (STOCKWISE_HOME); `update` locks a file
(`.lock`) around load-change-save — do network work first, then update
config.rs Optional config.toml: defaults for market, AI, cache, data dir, periods
output.rs --json / --csv rendering (Format, CsvRow, RFC 4180 quoting)
portfolio.rs Lots, FIFO sales, splits/dividends, XIRR cash flows, FX, named portfolios, import
rules (kept tradebook trades, merging, `--replace`)
tax.rs Capital-gains rules: holding periods, tax years, set-off, cess, sale-date rates,
advance tax and Sec. 234C (India/US/UK)
tax_export.rs ITR Schedule CG / 112A and Form 8949 schedules from realized sales, as CSV/JSON
rebalance.rs Target weights, drift bands and buy-only (`--inflow`) rebalancing
xray.rs Portfolio X-Ray: look-through overlap, asset-class, market-cap and sector split
benchmark.rs Daily time-weighted return vs an index, alpha/beta/tracking error, Carino attribution
goals.rs SIP / step-up, retirement and education math; Indian amounts (1cr, 25L, 50k)
api.rs Yahoo Finance client (session, quotes, charts, search, cache, retries); routes to a provider
providers/ MarketData trait + opt-in Alpha Vantage, Massive (Polygon), Twelve Data; Yahoo fills gaps
nse.rs, bse.rs NSE / BSE website JSON (quotes, intraday + daily bars, search, NIFTY index members)
session.rs Polite exchange HTTP: one cookie session per site, ≤3 req/s with jitter, retries, refusal = stop
i18n.rs Typed English/Hindi message catalog, --lang / STOCKWISE_LANG / locale detection
brokers/ Broker trait + clients: angel (SmartAPI, TOTP), zerodha, upstox, dhan, fyers
(experimental), secrets.rs (OS keyring / owner-only file), mock.rs (test HTTP server),
readonly.rs (session wrapper that refuses every order/GTT/cancel), gtt_watch.rs
(GTT status changes between looks), per-endpoint response shapes for selftest
funds.rs AMFI NAV file + api.mfapi.in history (cached), AMFI TER disclosure (cached a day),
returns, SIP, ETF holdings overlap
xlsx.rs Minimal .xlsx reader (zip via flate2 + sheet cells, tab-ordered sheets) for AMFI's TER export and broker imports
derivatives.rs Option chains from NSE / BSE / Yahoo, lot sizes, PCR, max pain, per-contract IV and greeks
greeks.rs Black-Scholes pricing, greeks, implied-volatility solver
payoff.rs Multi-leg option payoffs: presets, max profit/loss, breakevens, probability of profit
commodity.rs Gold/silver spot in ₹ from COMEX × USDINR, futures and ETF lists, SGB tranches
and NSE's SGB list (fixture-tested parser; `nse_sgb_list` fetches it)
offmarket.rs Prices for holdings Yahoo doesn't quote: `MF:` funds at AMFI's NAV, SGBs from
NSE's list or estimated from gold spot; used by the shared valuation
bonds.rs Bond math: price ↔ YTM (half-yearly, 30/360), accrued interest, duration, T-bills
gsec.rs CCIL tenor-wise yields, RBI benchmarks and outstanding G-sec list (HTML parsers)
pagecache.rs Cached fetch of public pages (CCIL, RBI, NSE SGB list) with a stale-copy fallback
ai.rs AI: Ollama (default) or opt-in OpenAI / Anthropic / Gemini; educational prompts
(`ai::prompts`, versioned cache keys), disclaimer, opt-in flag, model checks
clock.rs Exchange clock: IST / US Eastern / European / Sydney time, NSE, NYSE and global
exchange holidays and half days, lunch breaks, session phases
daemon.rs Worker state: positions, risk manager, daemon_state.json, shutdown, retry
(`daemon intraday --once` stops after one session)
notify.rs Notifications: Telegram, Slack, Discord, SMTP email, desktop
technical.rs Indicators and statistics (SMA, EMA, RSI, MACD, BB, ATR, VWAP, OBV, VaR, Sharpe, XIRR, ...)
backtest/ Backtesting engine (see docs/backtesting.md)
mod.rs Daily bars from Yahoo charts
rules.rs TOML strategy rules: parser, validation, signals; built-ins from examples/strategies/
indicators.rs Whole-series indicators matching technical.rs
costs.rs Indian delivery/intraday and US cost models, slippage
engine.rs Simulator (single symbol or portfolio, fills, stops, rebalancing) and metrics
walkforward.rs Rolling train/test optimisation and the overfitting verdict
plugin.rs WASM strategy plugins: wasmi sandbox, ABI v1 (`wasm-plugins` feature)
intraday.rs Intraday bot (9 strategies, regime detection, Kelly sizing)
longterm.rs Long-term scores (6-pillar scoring, Monte Carlo, DRIP)
simulator.rs Paper-trading sessions and confidence rating
practice/ Practice mode (see docs/simulator.md): scenarios/*.toml and challenges.toml
(embedded), scenario.rs, tape.rs (cached daily bars; reads stop at the replay
date), replay.rs (next-open fills with backtest costs, stops, marks),
challenge.rs + live.rs (rules and goals), score.rs (0–100 score, badges),
profile.rs (streaks, best scores), card.rs (text and SVG cards)
insights.rs Rule-based plain-English insights
research.rs One research snapshot per stock (Yahoo, NSE/XBRL filings, news) for events, insights, verdict
events.rs Key-events timeline: de-duplicated, categorised, with the price reaction
verdict.rs 6-pillar score from fixed rules; pillars without data are left out, `data_coverage`
scorecard.rs Piotroski F-score and Altman Z (Z'') with every criterion and formula
ipo.rs Indian IPO board, subscription, SEBI allotment odds and 6-pillar demand score
charts.rs Braille line charts, option payoff chart, volume bars, gauges
display.rs Currency- and asset-aware formatting (₹/$/€/£/¥, FX and crypto decimals), width-aware padding
market.rs Market enum (in/us/crypto/fx/commodity + global exchanges), the MarketInfo table
(uk/de/jp/hk/sg/ca/au: suffix, currency, indices, sectors, hours, holidays, tax),
asset classes, symbol resolution, index/sector/coin/pair lists
alerts.rs Alert conditions (price, RSI, MA crosses, breakouts, day change, volume, results)
and their evaluation on quotes / daily bars; persisted via store.rs
calendar.rs Results, dividend and split dates: Yahoo calendarEvents + NSE board meetings and
corporate actions (one rate-limited session), day cache in calendar_cache.json
watchlist.rs, wealth.rs Data models persisted via store.rs
```
Adding a command: define it in `Commands` in `main.rs` (and in the `GROUPS`
help table — a test enforces this), write `cmd_` in the matching
`commands/*.rs` file, and dispatch to it in `main()`. For machine-readable
output, flatten `TableOutput` (`--json`/`--csv`) or `JsonOutput` (`--json`)
into the command, build the rows once as `Serialize` structs, and branch on
`output::Format`; shape tests live in `commands/output_tests.rs`.
## Configuration
`main()` loads the config file before parsing arguments (`config::load`) and
turns its `market`, `ai` and `[periods]` values into clap defaults, so flags and
env vars still win and `--help` shows the effective default. Other settings
are read through `config::setting(env_var, ...)`: env var first, then the file,
then the caller's built-in default. Precedence is always flag > env var >
config file > default.
## Data Flow
```
User Input → clap CLI → commands/* → views.rs → api.rs → Yahoo Finance
tui/, server/ ───────────↗
→ funds.rs → AMFI NAVAll.txt + TER disclosure, api.mfapi.in
→ derivatives.rs → NSE / BSE option chains (India), Yahoo (US)
→ gsec.rs → CCIL yields, RBI benchmarks and G-sec list (pagecache.rs)
→ commodity.rs ← NSE SGB list (providers/nse session)
→ providers/* → Alpha Vantage / Massive / Twelve Data / NSE / BSE (opt-in)
→ brokers/* → Angel One, Kite, Upstox, Dhan, Fyers
→ ai.rs → Local Ollama (or opt-in OpenAI / Anthropic / Gemini)
→ technical.rs (indicators)
→ intraday.rs / longterm.rs (bots)
→ charts.rs (visualization)
→ display.rs (formatting)
→ store.rs (portfolio, watchlist, alerts, history)
```
## Caching
Hybrid cache: Redis (primary) + in-memory (fallback).
```
Request → Check Redis → Check Memory → Fetch Yahoo → Store Redis + Memory → Return
```
| Data | TTL | Why |
|------|-----|-----|
| Quotes | 30s | Live prices, short freshness |
| Charts (1d/5d) | 60s | Intraday data |
| Charts (1mo/3mo) | 5 min | Historical, moderate |
| Charts (1y+) | 10 min | Rarely changes |
| Search | 2 min | Static-ish |
Redis keys use `stockwise:` prefix. Cache persists across CLI invocations — run `stockwise deep RELIANCE`, then `stockwise technical RELIANCE` and chart data comes from Redis.
```bash
brew services start redis # Enable Redis caching
redis-cli keys "stockwise:*" # See cached entries
redis-cli --scan --pattern 'stockwise:*' | xargs redis-cli del # Clear only StockWise keys
```
Falls back to an in-memory cache if Redis isn't running. Connection and response timeouts
(300/500 ms) keep a missing or hung Redis from slowing commands. Point at another server with
`STOCKWISE_REDIS_URL` (or `redis_url` in the config file), or set it to `off`.
Rate limiting: 200ms minimum between requests (5 req/sec). Retry: 3 attempts with exponential backoff; HTTP 429 honours `Retry-After` (capped at 10s) and never sleeps after the last attempt.
## Data Storage
Location: `~/Library/Application Support/stockwise/` (macOS), `~/.local/share/stockwise/` (Linux), or
`%LOCALAPPDATA%\stockwise\` (Windows). Set `STOCKWISE_HOME` (or `home` in the config file) to use another directory;
`stockwise doctor` prints the active path. Files are written atomically (temp file + rename), and
broker config files are readable only by you (0600 on Unix). Broker secrets move into
the OS keyring when one is available (`STOCKWISE_KEYRING=off` keeps them in the file).
| File | Purpose |
|------|---------|
| `portfolio.json` | Default portfolio: holdings with lots (qty, price, purchase date, source), realized sales, dividends. Format version 2; older files are upgraded on load and kept once as `portfolio.json.bak` |
| `portfolios/NAME.json` | Named portfolios (`--portfolio NAME`), same format |
| `portfolios/NAME.wealth_history.json` | Wealth history of a named portfolio |
| `watchlist.json` | Watched symbols |
| `alerts.json` | Price alerts (symbol, condition, target) |
| `wealth_history.json` | Daily value snapshots of the default portfolio |
| `sim_history.json` | Paper trading sessions and results |
| `sim_replay.json` / `sim_replay_tape.json` | The running historical replay and its cached daily prices (removed by `sim replay end`) |
| `sim_challenge.json` | A challenge running over live paper sessions |
| `sim_profile.json` | Practice badges, streaks, best scores and recent runs |
| `daemon_state.json` / `daemon_state_us.json` | The intraday daemon's current day (positions, risk), saved every tick |
| `alert_watch.json` | Which alerts `alert watch` already announced |
| `angel_config.json` | Angel One API credentials (secrets in the OS keyring when available) |
| `zerodha_config.json`, `upstox_config.json`, `dhan_config.json`, `fyers_config.json` | Experimental broker apps and daily sessions (secrets in the keyring when available) |
| `cache/` | AMFI NAV file and per-scheme NAV history, re-downloaded after 12 hours; CCIL/RBI pages (6–24 hours) and NSE's SGB list (5 minutes) (safe to delete) |
## Dependencies
| Crate | Purpose |
|-------|---------|
| `clap` | CLI argument parsing (derive macros) |
| `clap_complete` / `clap_mangen` | Shell completions and man pages generated from the CLI definition |
| `reqwest` | HTTP client (Yahoo Finance, brokers, Ollama) |
| `keyring` | OS keyring for broker secrets |
| `async-trait` | `dyn Broker` with async methods |
| `sha2` / `flate2` | Kite/Fyers login checksums; gzipped instrument lists |
| `rpassword` | Hidden prompt for pasted tokens |
| `redis` | Redis caching (optional, falls back to in-memory) |
| `tokio` | Async runtime |
| `serde` / `serde_json` | JSON serialization |
| `colored` | Terminal colors |
| `chrono` | Date handling |
| `dirs` | Platform-specific data directories |
| `anyhow` | Error handling |
| `lettre` | SMTP email notifications (rustls) |
| `notify-rust` | Desktop notifications (optional `desktop-notify` feature) |
| `ratatui` | Terminal UI for `tui` (re-exports crossterm; `tui` feature) |
| `axum` | HTTP server for `serve` (`server` feature) |
| `wasmi` | WebAssembly interpreter for strategy plugins (optional `wasm-plugins` feature) |
| `rmcp` | Official Model Context Protocol SDK for `stockwise mcp`, without its macros (`mcp` feature) |
`tui`, `server` and `mcp` are on by default (`mcp` needs `server`);
`cargo build --no-default-features` leaves them out (`ask` still works).
`wasm-plugins` is off by default. CI-style checks should run clippy for all
three builds.
## Releases and packaging
Pushing a `v*` tag runs `.github/workflows/release.yml`: it builds the six release
archives (binary, docs, and `man/*.1` from `stockwise man --dir`; Linux, macOS, Windows
and Android for Termux), the `.deb`/`.rpm` packages (`packaging/linux/build-packages.sh`,
metadata in `Cargo.toml`) and the PyPI wheels (`pyproject.toml`, maturin `bin`),
publishes the crate, renders the Homebrew, Scoop, winget and AUR manifests in
`packaging/` with the release's checksums, updates the Homebrew tap and uploads to PyPI
and Docker Hub when their credentials are configured, and pushes the multi-arch
`ghcr.io/iamsaquib8/stockwise` image built from `Dockerfile`. `flake.nix` builds the
same crate with Nix; `packaging/nixpkgs/` and `packaging/termux/` hold the nixpkgs and
termux-packages recipes. `.github/workflows/stats.yml` archives download counts nightly
(`scripts/download-stats.py`). See [packaging/README.md](https://github.com/iamsaquib8/stockwise/blob/main/packaging/README.md).
## Testing
```bash
cargo test # unit tests, CLI parsing tests and tests/
cargo test technical # only tests whose name matches
cargo test --features wasm-plugins # also the WASM strategy plugin tests
```
Unit tests sit next to the code they test, and the pure engines (`technical`,
`tax`, `rebalance`, `verdict`, `backtest`) are tested without the network.
Parsers of exchange and broker pages run against fixtures in `fixtures/`
directories, and the HTTP clients against `testutil::MockServer`, so no test
reaches a real service. `tests/` holds the integration tests that run the
built binary against a scratch `STOCKWISE_HOME`. Two tests read the docs: they
parse every `stockwise ...` command written in backticks in
`docs/cheatsheet.md` and `skills/stockwise/SKILL.md`, so a renamed flag breaks
the build until the docs follow.
CI (`.github/workflows/ci.yml`) runs `scripts/changelog.py check`, the tests on
Linux, macOS and Windows, `cargo fmt --check`, clippy for the default and the
`--no-default-features` builds, a check on Rust 1.97, the `wasm-plugins`
feature and `shellcheck` on `install.sh`.
## Docs and changelog
- `docs/*.md` are the documentation pages. `python3 site/build.py` (needs
[mdBook](https://rust-lang.github.io/mdBook/)) copies them with `site/index.md`
and `site/SUMMARY.md` into `target/site-src`, builds `target/site`, and writes
`llms.txt`, `llms-full.txt` and a Markdown copy of every page.
`.github/workflows/docs.yml` publishes it to GitHub Pages on every push to `main`.
- `changelog.d/` holds one small file per user-visible change.
`scripts/changelog.py` checks the files and folds them into `CHANGELOG.md` at
release time; see [CONTRIBUTING.md](https://iamsaquib8.github.io/stockwise/contributing.html#changelog-entries).
---
# NSE / BSE responses and test fixtures
The `nse`, `bse` and `exchange` data providers ([data-providers.md](https://iamsaquib8.github.io/stockwise/data-providers.html#nse-and-bse))
read the JSON endpoints behind nseindia.com and bseindia.com. Those endpoints
are undocumented and change without notice, so StockWise pins down what it
expects in two places:
- **This page** lists every endpoint and the fields StockWise reads. Fields
not listed are ignored, so new fields never break anything.
- **Fixtures** in [`src/providers/fixtures/`](https://github.com/iamsaquib8/stockwise/tree/main/src/providers/fixtures/) hold
one recorded-style response per endpoint. Parser tests run against them;
no test touches the network.
## Fixture format
A fixture is the response body exactly as the endpoint sends it (JSON, or CSV
for the downloads), **trimmed and anonymised**:
- Keep the fields StockWise reads (below) and a few neighbours, so the
fixture still looks like the real thing. Drop long arrays down to two or
three entries.
- Replace real-time values with plausible made-up ones. Fixtures are test
data, not market data: never commit a full live response (the exchanges'
terms restrict redistributing their data), and never commit cookies,
headers or anything tied to a session.
- Keep the quirks: numbers sent as strings (`"lowerCP": "1307.20"`, BSE's
`"1,451.65"`), the misspelt `grapthData`, BSE's JSON-inside-a-string `Data`,
NSE's IST-as-UTC chart stamps. Those are what the parsers must survive.
- Name it `_.json` (or `.csv`) and load it with
`include_str!` in the parser's tests.
The fixtures shipped with the first release of these providers are modelled
on the shapes these endpoints have long returned (and that open-source NSE/BSE
clients parse); they were not captured from a live session by the code in this
repository. When a live response differs, update the fixture and the parser
together and note the date here.
### Recording a fresh response
1. Open the page that shows the data in your browser — for example
`https://www.nseindia.com/get-quotes/equity?symbol=RELIANCE` — with the
developer tools' Network tab open, filtered to `api`.
2. Click the request (`quote-equity?symbol=RELIANCE`) and copy the response
body.
3. Trim and anonymise it as above, save it under `src/providers/fixtures/`,
and run `cargo test providers`.
Use your browser for this, as a person visiting the site would; don't script
requests around the exchanges' bot protection.
## NSE (`https://www.nseindia.com`)
Every API call follows one load of the home page (`/`) for session cookies.
| Endpoint | Fixture | Fields read |
|----------|---------|-------------|
| `/api/quote-equity?symbol=RELIANCE` | `nse_quote_equity.json` | `info.companyName`; `priceInfo.lastPrice`, `change`, `pChange`, `previousClose`, `open`, `intraDayHighLow.min`/`max`, `weekHighLow.min`/`max`, `lowerCP`/`upperCP` (strings). `{}` (or only `msg`/`info`) = unknown symbol; no `priceInfo` next to other keys = format change |
| `/api/quote-equity?symbol=RELIANCE§ion=trade_info` | `nse_trade_info.json` | `marketDeptOrderBook.tradeInfo.totalTradedVolume` (lakh shares), `.totalMarketCap` (₹ crore); `securityWiseDP.deliveryToTradedQuantity` (%) |
| `/api/allIndices` | `nse_all_indices.json` | `data[]`: `indexSymbol` (or `index`), `last`, `variation`, `percentChange`, `open`, `high`, `low`, `previousClose`, `yearHigh`, `yearLow` |
| `/api/equity-stockIndices?index=NIFTY%2050` | `nse_equity_stock_indices.json` | `name`; `data[]`: `priority` (1 = the index's own row), `symbol`, `lastPrice`, `change`, `pChange`, `open`, `dayHigh`, `dayLow`, `previousClose`, `yearHigh`, `yearLow`, `totalTradedVolume` (shares), `meta.companyName` |
| `/api/chart-databyindex?index=RELIANCEEQN` (`?index=NIFTY%2050&indices=true` for an index) | `nse_chart_databyindex.json` | `grapthData`: `[epoch ms, price]` pairs, pre-open points tagged `"PO"`. The stamps are IST wall-clock time written as UTC; StockWise detects and corrects that |
| `/api/historical/cm/equity?symbol=RELIANCE&series=["EQ","BE"]&from=24-10-2024&to=24-10-2025` | `nse_historical_equity.json` | `data[]`: `CH_TIMESTAMP` (`2025-10-24`, or `mTIMESTAMP` `24-Oct-2025`), `CH_SERIES`, `CH_OPENING_PRICE`, `CH_TRADE_HIGH_PRICE`, `CH_TRADE_LOW_PRICE`, `CH_CLOSING_PRICE`, `CH_TOT_TRADED_QTY`. At most a year per request. On HTTP 404 StockWise tries `/api/historicalOR/generateSecurityWiseHistoricalData` (same fields) |
| `/api/historical/indicesHistory?indexType=NIFTY%2050&from=...&to=...` | `nse_indices_history.json` | `data.indexCloseOnlineRecords[]`: `EOD_TIMESTAMP` (`24-OCT-2025`), `EOD_OPEN_INDEX_VAL`, `EOD_HIGH_INDEX_VAL`, `EOD_LOW_INDEX_VAL`, `EOD_CLOSE_INDEX_VAL`; `data.indexTurnoverRecords[]`: `HIT_TIMESTAMP`, `HIT_TRADED_QTY` |
| `/api/search/autocomplete?q=reliance` | `nse_autocomplete.json` | `symbols[]`: `symbol`, `symbol_info`, `type` (only `equity` kept) |
| `https://nsearchives.nseindia.com/content/equities/EQUITY_L.csv` | inline in the tests | Columns `SYMBOL` and `NAME OF COMPANY`; search fallback, kept on disk for a day |
Prices of 0 (or `"-"`) mean "none" and are read as missing, never as ₹0.
## BSE (`https://api.bseindia.com/BseIndiaAPI/api`)
No cookies; calls carry `Referer` and `Origin` of `https://www.bseindia.com`.
| Endpoint | Fixture | Fields read |
|----------|---------|-------------|
| `ListofScripData/w?Group=&Scripcode=&industry=&segment=Equity&status=Active` | `bse_scrip_list.json` | A list (older answers: `{"Table": [...]}`) of `SCRIP_CD` (string or number), `scrip_id`, `Scrip_Name` (or `Issuer_Name`). Kept on disk for a day; maps `RELIANCE.BO` to `500325` and serves search |
| `getScripHeaderData/w?Debtflag=&scripcode=500325&seriesid=` | `bse_header.json` | `CurrRate.LTP`, `.Chg`, `.PcChg`; `Header.PrevClose`, `.Open`, `.High`, `.Low`, `.LTP`; `Cmpname.FullN`. Numbers are strings, sometimes with thousands separators. Neither `Header` nor `CurrRate` = format change |
| `StockReachGraph/w?scripcode=500325&flag=0&fromdate=&todate=&seriesid=` | `bse_stock_reach_graph.json` | `Data`: a JSON list sent as a string (or a list) of `{dttm: "Fri Oct 24 2025 09:15:00" (IST), vale1: price}` |
| `StockPriceCSVDownload/w?pageType=0&rbType=D&Scode=500325&FDates=24/10/2024&TDates=24/10/2025` | `bse_stock_prices.csv` | CSV columns found by name: `Date` (`24-October-2025`), `Open Price`, `High Price`, `Low Price`, `Close Price`, `No.of Shares` |
---
# Disclaimer
**StockWise is for educational and informational purposes only. It is not
financial, investment, trading, legal, or tax advice.**
- Nothing produced by StockWise — quotes, analysis, technical signals,
backtests, "AI verdicts", "picks", forecasts, or daemon decisions — is a
recommendation to buy, sell, or hold any security. Markets are risky and you
can lose money, including more than you invest.
- All data is sourced from third-party providers (e.g. Yahoo Finance) on an
"as available" basis. It may be delayed, inaccurate, incomplete, or wrong.
Do not rely on it for real decisions without independent verification.
- **Live trading places real orders with real money.** The `trade buy`, `sell`
and `limit` commands send orders to your connected broker (e.g. Angel One)
after showing a preview and asking you to confirm — or immediately when you
pass `--yes`. The `daemon` workers paper-trade only and do not send orders.
You are solely responsible for every order placed, for the configuration of
risk controls, and for any financial loss. Test with paper trading (`sim`)
and `trade ... --dry-run` first.
- Backtests and simulations describe the past or a hypothetical. **Past
performance does not guarantee future results.** Simulated results have
inherent limitations and do not represent actual trading.
- The software is provided "AS IS", without warranty of any kind, under the
terms of the [MIT License](https://github.com/iamsaquib8/stockwise/tree/main/LICENSE). The authors accept no liability for any
loss or damage arising from its use.
- StockWise is an independent open-source project. It is **not affiliated with,
endorsed by, or sponsored by** Yahoo, Angel One, Zerodha, the NSE, the BSE, or
any exchange, broker, or data provider.
Consult a licensed financial advisor before making investment decisions. Use of
this software constitutes acceptance of this disclaimer. If you do not agree, do
not use it.