A terminal client for Pakistan Stock Exchange market data — live quotes, a full-market screener, candlestick charts with technical indicators, risk and return analytics, company fundamentals, and intraday microstructure.
| # | Screen | What it shows |
|---|---|---|
| 1 | Dashboard | Market breadth, top gainers/losers, most active by value, scrollable sector heatmap |
| 2 | Screener | Every listed scrip — sortable and filterable by symbol, name or sector, plus valuation columns |
| 3 | Chart | Candlesticks with SMA/EMA/Bollinger/Donchian/Ichimoku, and a Volume / RSI / MACD / ATR / Stochastic / ADX / CCI / Williams %R pane |
| 4 | Analysis | Returns by window, annualized return & volatility, Sharpe, Sortino, max drawdown, beta and correlation vs KSE100 |
| 5 | Company | Business profile, key people, equity structure, annual & quarterly financials, ratios, announcements |
| 6 | Intraday | Session price with VWAP, 15-minute volume distribution, live trade tape |
| 7 | Compare | 2-8 scrips side by side — rebased performance overlay, risk table, correlation matrix |
| 8 | Seasonality | Month-by-year return grid, day-of-week effects, return distribution, streaks |
| 9 | Macro | 20 external series — energy, metals, agriculture, freight, FX, crypto — plus the SBP policy rate and business news, with correlation to the selected scrip |
Timeframes: 5D, 1M, 3M, 6M, YTD, 1Y, 2Y, 3Y, 5Y and MAX.
![]() Chart — candles, SMA/EMA overlays, volume pane |
![]() Compare — up to eight scrips rebased, with risk and correlations |
![]() Screener — every listed scrip, sortable |
![]() Macro — commodities, FX, policy rate, headlines |
Runs on Linux, macOS and Windows. No API key, no account, no configuration — just a terminal with 256-colour and Unicode support. Building from source needs Rust 1.88 or newer (the code uses let-chains); the prebuilt binaries on the releases page need nothing at all.
install.sh does the whole thing end to end: it installs a Rust toolchain if
there isn't a usable one, builds a release binary, and puts psxtui on your
PATH.
git clone https://github.com/AnnanKhan/PSXtui.git
cd PSXtui
./install.sh
psxtui| Flag | Effect |
|---|---|
| (none) | Install for the current user into ~/.cargo/bin |
--system |
Install into /usr/local/bin instead (uses sudo) |
--no-modify-path |
Never touch your shell profile |
--uninstall |
Remove the binary; the cache and watchlist stay |
It only appends to ~/.bashrc / ~/.zshrc / ~/.profile when ~/.cargo/bin
is genuinely missing from your PATH, and prints every step as it goes. Re-run it
any time to update after a git pull.
install.ps1 downloads the prebuilt psxtui.exe — no Rust, no Visual Studio,
nothing to compile — and puts it on your PATH:
git clone https://github.com/AnnanKhan/PSXtui.git
cd PSXtui
.\install.ps1
psxtuiOr skip the clone entirely: download psxtui-<version>-x86_64-pc-windows-msvc.exe
from the releases page, rename it to
psxtui.exe, and put it wherever you keep such things. It is a single
self-contained binary — SQLite is compiled in and there is nothing to install
alongside it. The .zip beside it holds the same binary plus this README, and is
what install.ps1 downloads.
Windows will warn that the binary is unsigned the first time you run it — there is
no code-signing certificate behind this project. The .sha256 file next to each
download lets you confirm you got what CI built.
| Flag | Effect |
|---|---|
| (none) | Download the released binary into %LOCALAPPDATA%\Programs\psxtui |
-FromSource |
Build it with cargo instead (needs Rust + the VS C++ build tools) |
-NoModifyPath |
Never touch the user PATH |
-Uninstall |
Remove the binary and its PATH entry; the cache stays |
Use Windows Terminal. It is what does truecolour,
mouse reporting and the box-drawing glyphs the whole UI is built from; the legacy
conhost console will look wrong. If the charts come out as empty boxes your font
has no braille — install a Nerd Font, or run with
PSXTUI_MARKER=block (see Environment).
Building from source on Windows additionally needs the Visual Studio C++ build
tools for the bundled SQLite — winget install Microsoft.VisualStudio.2022.BuildTools,
with the "Desktop development with C++" workload. Nothing else: TLS is rustls
over ring, deliberately, so there is no NASM, CMake or OpenSSL to install.
The same four steps, if you would rather run them yourself:
# 1. a toolchain (skip if `rustc --version` already reports 1.88+)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
. "$HOME/.cargo/env"
# 2. the sources
git clone https://github.com/AnnanKhan/PSXtui.git && cd PSXtui
# 3. build — first release build takes a few minutes
cargo build --release
# 4a. run it straight out of the build directory
./target/release/psxtui
# 4b. …or install it onto your PATH
cargo install --path .cargo install drops the binary in ~/.cargo/bin, which rustup already adds to
PATH. If psxtui still isn't found, add it yourself:
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.bashrc && exec $SHELLDependencies. TLS is rustls and SQLite is bundled, so there are no -dev
packages to hunt down — but compiling that bundled SQLite needs a C compiler:
build-essential on Debian/Ubuntu, gcc on Fedora, xcode-select --install on
macOS.
Uninstalling. ./install.sh --uninstall (.\install.ps1 -Uninstall on
Windows), or cargo uninstall psxtui. All of them leave your cache and
watchlist alone; delete the database below to remove those too.
psxtui --version # 0.1.0
psxtui --help # the two flags there are, plus where your data lives
| Platform | Cache and watchlist |
|---|---|
| Linux | ~/.local/share/psxtui/psx.db |
| macOS | ~/Library/Application Support/psxtui/psx.db |
| Windows | %APPDATA%\psxtui\data\psx.db |
psxtui --help prints the real path for the machine it is running on.
| Variable | Effect |
|---|---|
PSXTUI_MARKER=block |
Draw every chart with half-block glyphs instead of braille. Half the vertical resolution, but it renders in any font — the escape hatch when braille shows up as boxes. |
Press ? in the app for the full list.
| Key | Action |
|---|---|
1–9, Tab |
Switch screen |
/ |
Search by symbol, company or sector |
j/k, ↑/↓ |
Move cursor · Enter opens in the chart |
s / S |
Cycle sort column / reverse |
W / e |
Watchlist only / equities only |
w |
Add or remove the current symbol from the watchlist |
[ / ] |
Chart range · i cycles the indicator pane |
c |
Chart style: candles → line → dots → area (braille; PSXTUI_MARKER=block forces half-blocks) |
m / e / b |
Toggle SMA / EMA / Bollinger overlays |
a / x |
Compare: add a symbol (opens the picker) / remove one · c resets |
r |
Refresh · q quit |
M |
Mouse on/off (off restores terminal text selection) |
Click a tab to switch screen, a row to select it, and double-click to open it in the chart. The wheel scrolls whatever list is under the pointer, and over either chart it changes timeframe. Everything the screens draw as a control is clickable:
- Chart — range buttons, overlay toggles, the style indicator and the indicator-pane title.
- Screener — column headers sort (click the active one to reverse), the
panel title swaps in the valuation view, and the footer's
sort,watchlistandequitiesreadouts are switches. - Compare — the range buttons,
+ Add, and the symbol chips: click a chip's name to select it, its✕to drop it (see below). - Dashboard, Company, Macro — boards, the sector heatmap, tabs, announcements, series and headlines.
Mouse reporting takes over the terminal's own text selection, so M turns it
off when you want to copy something out (holding Shift also works in most
terminals).
The Compare screen is edited from the screen itself — there is no round trip through the screener to pick up a symbol first.
a,+,/or clicking+ Addopens the picker. Type to filter by ticker or company name; a symbol prefix ranks above a name match, and ties break on turnover, so the obvious answer is usually already under the cursor.Enter(or a click) toggles the row: already-compared symbols are shown ticked and the same keystroke removes them. The picker stays open so several can be added in one visit.Esc, or a click anywhere outside it, closes it.- Each chip carries its own
✕— one click, no double-click, and it drops that symbol without changing the selection.xremoves the selected symbol from the keyboard, andcresets the set back to the watchlist seed. - A symbol with no local history is fetched when you add it, so it fills in rather than sitting in the table as an empty row.
- The screen seeds itself with four — the selection, then the watchlist,
then the day's most-traded names — and leaves room to grow to eight.
cputs it back to that seed.
Up to eight symbols overlay at once — where the palette runs out of hues that stay separable on a dark background. The chip row wraps and the legend packs onto extra lines rather than hiding anything, so a full set is still readable on an 80-column terminal; the correlation matrix folds away when the pane is too narrow to hold it honestly.
PSX publishes no API, so psxtui reads the public data portal at
dps.psx.com.pk — two JSON feeds plus scraped HTML:
| Source | Used for |
|---|---|
/symbols |
Master list of instruments, sector names, ETF/debt flags |
/market-watch |
Live board — OHLC, change, volume for every scrip |
/timeseries/eod/<SYM> |
Long-run daily history (also works for indices like KSE100) |
/timeseries/int/<SYM> |
Intraday trade ticks |
POST /historical |
Whole-market OHLC for one date — the only source of true daily high/low |
/company/<SYM> |
Profile, financials, ratios, announcements |
All unauthenticated, no API keys:
| Source | Used for |
|---|---|
| Yahoo Finance chart API | Energy (Brent, WTI, gas), metals (gold, silver, copper, steel HRC, aluminium), agriculture (cotton, wheat, sugar, soybean oil), USD/PKR, S&P 500 |
| Yahoo Finance (crypto) | BTC, ETH, SOL, BNB, XRP — a retail risk-appetite gauge, not a sector driver |
Yahoo Finance (BDRY) |
Dry-bulk freight — a proxy ETF, not the Baltic Dry Index, which isn't freely available |
| Business Recorder / Dawn RSS | Business and market headlines, matched to the selected scrip |
sbp.org.pk |
SBP policy rate, which feeds the risk-free rate in Sharpe and Sortino |
Correlations against these are computed on date-aligned returns — PSX and global markets keep different holiday calendars, so the series are intersected by trading day before anything is compared.
Port throughput and trade-flow volumes were investigated and dropped: neither Karachi Port Trust nor Port Qasim publishes a machine-readable feed, and inventing a number is worse than omitting one.
Two details worth knowing:
- The EOD feed has no high or low. It returns
[timestamp, close, volume, open]only. Real intraday extremes come from the daily/historicalsnapshot, which covers every symbol in a single request. The cache merges the two and a derived range is never allowed to overwrite a true one — so ATR and candlestick wicks are honest. - Market-watch reports sector codes (
0825), not names. These are joined against/symbolsso every screen can showCOMMERCIAL BANKS.
Everything fetched is persisted to SQLite (~/.local/share/psxtui/psx.db), so
the app opens instantly on cached data, analysis runs offline, and history
accumulates over time. On first run it backfills ~120 days of true OHLC in the
background — one request per trading day, marking holidays so they are never
refetched. Backfill runs on its own task and never blocks an interactive load.
Requests are deliberately paced (one at a time, ≥350 ms apart, bounded retries) so the tool behaves like a single person browsing rather than a crawler.
src/
psx/ HTTP client + JSON feeds + HTML scrapers (one module per source)
cache/ SQLite store; merges EOD and /historical into one bar series
analysis/ indicators.rs (SMA/EMA/RSI/MACD/Bollinger/ATR/OBV/VWAP/Stochastic)
stats.rs (returns, volatility, Sharpe, Sortino, drawdown, beta)
data.rs background worker — owns every network call and cache write
app.rs all application state and key handling
ui/ one module per screen; rendering is a pure function of `App`
Two invariants the code depends on:
- Indicator alignment. Every indicator returns a
Vec<Option<f64>>the same length as its input, withNonefor the warm-up window — so overlays zip straight onto the price series with no offset bookkeeping. - No poisoned floats. PSX data is full of thin scrips, limit-locked
sessions and zero-volume days. No analysis function panics or returns
NaNor infinity; degenerate cases collapse to documented sentinels.
Charts aggregate bars into one candle per terminal column (first open, last
close, extreme high/low, summed volume) rather than dropping sessions, while
indicators stay computed on the daily series — so SMA(20) means twenty
sessions at every zoom level.
cargo test # unit tests, no network
cargo run --example live_smoke # exercises every parser against the live portal
./docs/capture.sh # regenerate the README screenshotslive_smoke is the one that catches a PSX layout change — unit tests only prove
the parsers handle markup we wrote ourselves.
capture.sh drives the real binary in a fixed-size tmux pane and photographs
every screen, so the images above are reproducible rather than hand-cropped.
They show live PSX data from the session they were captured in.
CI (.github/workflows/ci.yml) runs the tests on Linux, macOS and Windows —
development happens on Linux, so the Windows job is the only thing keeping that
support honest. release.yml builds the prebuilt binaries for every platform on
a v* tag. It publishes the Windows build twice — a bare .exe for anyone
downloading by hand, and a .zip whose name is a contract with install.ps1,
which looks for an asset ending in x86_64-pc-windows-msvc.zip. Keep the two in
step if either is renamed.
Data is sourced from the PSX data portal for personal use. PSX's terms of use restrict systematic retrieval; the client is rate-limited and caches aggressively to stay well within the behaviour of an ordinary browser, but you are responsible for how you use it. Nothing here is investment advice.










