A terminal-based quantitative trading dashboard for the Nepal Stock Exchange (NEPSE), built with Textual. Runs entirely in your terminal — no browser, no electron, no cloud dependency.
Paper trading only. This terminal simulates trades locally. It does not connect to any broker API.
⚠️ Disclaimer — read this first. This is an educational research tool, not financial advice. It does not connect to a broker and cannot place real orders. The bundled backtest figures are in-sample / historical — they come from a parameter sweep, are not corrected for the number of strategy variants tried, and the repo ships no artifact that reproduces them as forward-tested returns. Treat them as a demonstration of the tooling, not as proof of an edge. Past performance does not indicate future results. Nothing here is a recommendation to buy or sell any security. See docs/VALIDATION_METHODOLOGY.md.
- Paper Trading — full paper portfolio with buy/sell order book, P&L tracking, NAV history, and multi-account support. Seed from your MeroShare holdings CSV or start blank.
- Auto Trading Engine — assigns a quantitative strategy to each account. The engine runs in the background, generates signals every 5 trading days, and manages entries/exits automatically (holding periods, stop losses, trailing stops, regime filters).
- Backtesting — backtests on 6+ years of NEPSE price data, with a walk-forward replay harness and a statistical validation suite. The bundled C5 baseline shows a historical, in-sample +88% return (Sharpe ~2.2) vs. NEPSE +27% — a figure from a parameter sweep, not corrected for multiple testing and not a forward-performance claim (the repo ships no artifact reproducing it). Run the validation suite (deflated Sharpe, random-baseline percentile, CSCV/PBO) to see how it holds up; see docs/VALIDATION_METHODOLOGY.md.
- Market Dashboard — live quotes, 52-week highs/lows, top movers, sector heatmap, volume signals.
- Portfolio Analytics — unrealized/realized P&L, sector concentration, holding age buckets, max drawdown, alpha vs. NEPSE benchmark.
- Gold Hedge Overlay — tracks gold/silver regime (risk-on / neutral / risk-off) and adjusts capital deployment accordingly.
- AI Agent — on-demand analysis of your portfolio positions and signal shortlist. Defaults to a local Ollama model, with Gemma 4 MLX or Claude CLI available as optional backends.
- Paper Agent Graph — cleaned evidence-gated research, debate, risk, and portfolio decision workflow for paper execution only.
- Strategy Builder — create, backtest, and assign custom strategies. Each account runs its own strategy independently.
- Statistical Validation — walk-forward replay across rolling subwindows, Monte Carlo, CSCV/PBO overfitting detection, deflated Sharpe ratio, random baseline percentile. See docs/VALIDATION_METHODOLOGY.md for what each test does and does not prove.
- MeroShare Import — seed any account directly from your MeroShare "My Shares Values.csv" export.
The public agent workflow is evidence-gated, checkpointed, and restricted to paper execution. The implementation in backend/nepse_agents/ does not include live order routing, credentials, or execution integrations.
┌─────────────────────────────────────────────────────┐
│ Textual TUI │
│ dashboard_tui.py · 9 tabs · keyboard-driven │
└──────────┬──────────────────────┬───────────────────┘
│ │
┌──────▼──────┐ ┌────────▼────────┐
│ Market │ │ Trading Engine │
│ Data Layer │ │ (per account) │
│ nepse_data │ │ tui_trading_ │
│ .db │ │ engine.py │
└──────┬──────┘ └────────┬────────┘
│ │
┌──────▼──────────────────────▼────────┐
│ Signal Engine │
│ simple_backtest.py │
│ volume · quality · low_vol · │
│ mean_reversion · xsec_momentum · │
│ quarterly_fundamental · satellite │
└──────────────────────────────────────┘
| Component | File | What it does |
|---|---|---|
| TUI | apps/tui/dashboard_tui.py |
All UI — 9 tabs, keyboard shortcuts, paper order book |
| Trading Engine | backend/trading/tui_trading_engine.py |
Per-account auto-trading loop, regime filter, stop logic |
| Paper Trader | backend/trading/paper_trader.py |
Manual buy/sell execution, portfolio persistence |
| Signal Engine | backend/backtesting/simple_backtest.py |
All signal generation and backtest runner |
| Strategy Registry | backend/trading/strategy_registry.py |
Load, save, assign strategies per account |
| Market Data | backend/market/ |
Price DB queries, quote scraping, 52wk calculations |
| Validation | validation/ |
Walk-forward, Monte Carlo, CSCV, DSR, random baseline |
| Gold Hedge | backend/quant_pro/gold_hedge.py |
Gold/silver regime detection → capital deployment % |
| AI Agent | backend/agents/agent_analyst.py |
Ollama-first portfolio and signal analysis |
| Paper Agent Graph | backend/nepse_agents/ |
Evidence-gated research/debate/risk/portfolio workflow; paper-only |
| NEPSE Calendar | backend/quant_pro/nepse_calendar.py |
Trading-day week (Mon–Fri default), public holidays, trading day counter |
Each account has its own directory under data/runtime/accounts/account_N/ containing:
paper_portfolio.csv # open positions
paper_trade_log.csv # all executed trades
paper_nav_log.csv # daily NAV history
paper_state.json # cash balance + runtime state
tui_paper_* # engine auto-trade files
watchlist.json # symbols to track
Manual trading (Order tab) writes to paper_portfolio.csv.
Auto-trading (engine) writes to tui_paper_trade_log.csv and reconciles with the manual portfolio on display.
The Trade History tab merges both sources and deduplicates by (Date, Action, Symbol, Shares, Price).
Signals are generated per trading date using price + fundamental data from nepse_data.db. Each signal scores symbols 0.0–1.0. Signals are combined with regime-dependent weights:
Bull market → xsec_momentum weight ×1.1, all others ×1.0
Bear market → capital preservation mode (fewer positions)
Neutral → standard weights
Regime is detected via a 60-day rolling NEPSE return: bear below threshold, bull above 0, neutral in between.
The engine runs a 5-trading-day signal cycle — signals fire every 5 days, not daily, avoiding overtrading and matching NEPSE's lower liquidity.
| Signal | Logic |
|---|---|
volume |
Volume breakout above 20-day average with price confirmation |
quality |
ROE + debt-to-equity + earnings stability composite |
low_vol |
Low 60-day realized volatility with positive momentum |
mean_reversion |
RSI oversold + distance below 52-week high |
xsec_momentum |
Cross-sectional 6m-minus-1m momentum (skip last month) |
quarterly_fundamental |
EPS growth + revenue growth from quarterly filings |
satellite_hydro |
Hydropower generation signals from WECS rainfall data |
from backend.backtesting.simple_backtest import run_backtest
results = run_backtest(
start_date="2020-01-01", # required
end_date="2025-12-31", # required
signal_types=["volume", "quality", "low_vol", "mean_reversion",
"xsec_momentum", "quarterly_fundamental"],
holding_days=40,
max_positions=5,
stop_loss_pct=0.12,
trailing_stop_pct=0.15,
use_regime_filter=True,
initial_capital=1_000_000,
)The walk-forward phase slides a train/test window across 6+ years of history, re-runs the backtest on each rolling test window, and stitches the out-of-sample equity curves together. Note: it replays a fixed config on each window — it does not re-select or re-fit parameters per window, so it is a robustness check, not true out-of-sample model selection. See docs/VALIDATION_METHODOLOGY.md.
python -m validation.run_all --fastOutputs: stitched OOS equity curve, Sharpe, max drawdown, CSCV/PBO score, deflated Sharpe ratio, random baseline percentile.
The validation suite validates the bundled 6-signal C5 baseline (configs/long_term.py LONG_TERM_CONFIG) by default — the same strategy the auto-trading engine ships with — so the headline figure and the validated strategy are the same thing:
python -m validation.run_all # full battery, C5 baseline
python -m validation.run_all --fast # quick mode (fewer simulations)To validate a different strategy, override the config or individual parameters:
python -m validation.run_all --config legacy # old 3-signal volume/quality/low_vol config
python -m validation.run_all --signals volume quality # ad-hoc signal set
python -m validation.run_all --holding-days 60 # override one parameter on top of C5The suite prints a GO / NO-GO verdict and writes a JSON + PDF report under reports/validation/. The GO verdict is gated on a subset of the tests (base backtest, transaction costs, statistical significance, walk-forward, Monte Carlo, regime stress, sensitivity, random baseline, slippage, max drawdown). The CSCV/PBO overfitting test and the benchmark (alpha-vs-beta) comparison run and report, but do not gate the verdict — read them anyway. See docs/VALIDATION_METHODOLOGY.md for what each test does and does not prove.
When the TUI starts, one TUITradingEngine per account starts in a background daemon thread. Each engine:
- Loads its account's strategy config (signal types, holding days, stop params)
- Every 5 trading days: generates signals → ranks → buys top N symbols up to
max_positions - Every day: checks exits — trailing stop, stop loss, or holding period expiry
- Writes trades to
tui_paper_trade_log.csvfor that account - Persists state so it survives TUI restarts
Capital deployment adjusts by the gold hedge regime:
- Risk-off → 90% of capital deployed
- Neutral → 97%
- Risk-on → 100%
A strategy is a JSON config in data/strategy_registry/:
{
"id": "my_strategy",
"name": "My Strategy",
"config": {
"signal_types": ["volume", "quality", "xsec_momentum"],
"holding_days": 40,
"max_positions": 5,
"stop_loss_pct": 0.12,
"trailing_stop_pct": 0.15,
"use_regime_filter": true,
"regime_max_positions": {"bull": 5, "neutral": 4, "bear": 1},
"sector_limit": 0.35
}
}Create strategies in the Strategies tab → press N NEW, configure signals with the toggle buttons, set parameters, press SAVE. Assign to any account with → ACTIVE ACCT.
Implement a function in backend/backtesting/simple_backtest.py:
def generate_my_signal_at_date(
symbols: list[str],
date: str,
prices_df: pd.DataFrame,
) -> list[dict]:
# Return list of {"symbol": str, "score": float 0-1, "reason": str}
...Register it in the SIGNAL_MAP dict inside run_backtest() and add "my_signal" to any strategy's signal_types.
After downloading or cloning the repo, use the double-click launcher for your OS:
- macOS:
Nepse Quant Terminal.apporLaunch Quant Terminal.command - Windows:
Launch Quant Terminal.bat
The launcher creates .venv, installs dependencies, downloads the bundled market database if missing, runs preflight, and starts the TUI.
See docs/EASY_LAUNCH.md for troubleshooting notes and macOS security prompt handling.
- Python 3.10–3.13 (recommended: 3.12) — Python 3.14+ is not yet supported (numba and the nepse package both cap at
<3.14) - macOS, Linux, or native Windows PowerShell. WSL/container remains useful for scheduled production-style runs, but is not required for dashboard, backtests, or paper autopilot.
git clone https://github.com/nlethetech/nepse-quant-terminal
cd nepse-quant-terminal
pip install -r requirements.txtNative Windows PowerShell:
git clone https://github.com/nlethetech/nepse-quant-terminal
cd nepse-quant-terminal
py -3.12 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -U pip
python -m pip install -r requirements.txt
python -m scripts.ops.windows_preflightRun the setup script — it downloads the pre-built database (~13 MB) from the GitHub release automatically:
python setup_data.pyTakes under a minute. You get 456K rows of OHLCV history for all NEPSE symbols, quarterly earnings, corporate actions, and benchmark history — enough for the signal engine, backtests, and charts to work immediately.
Important: The bundled database is a snapshot. For accurate signals and backtests you must keep it up to date with fresh scraped data. The pre-built DB covers history through the release date — anything after that requires running the scraper.
Scrape fresh data yourself (to keep the snapshot current):
python setup_data.py --scrape # full historical scrape from Merolagani (~30–60 min)
python setup_data.py --scrape --days 90 # last 90 days only (~5 min)Daily incremental update — run this after market close each day to keep data current:
python scripts/ingestion/deterministic_daily_ingestion.pyThe signal engine (volume breakout, momentum, quarterly fundamental, etc.) relies on recent price history. Stale data = stale signals. Set up a daily cron job or run the ingestion script manually each evening.
python -m apps.tui.dashboard_tuiOn Windows PowerShell, run the same command after activating .venv:
python -m apps.tui.dashboard_tuiPaper strategy autopilot is broker-free. Manual orders and strategy orders use the same account-scoped paper execution service, fill from latest market quotes, write visible rejected/filled order history, and persist the canonical files under data/runtime/accounts/account_N/.
The Agents tab provides on-demand equity analysis of your signal shortlist and portfolio. Ollama is the default backend; Gemma 4 MLX and Claude CLI are optional.
Ollama runs any open-source model locally via a simple REST server. No Apple Silicon required.
Step 1 — Install Ollama
# macOS
brew install ollama
# Linux
curl -fsSL https://ollama.com/install.sh | shStep 2 — Pull a model
Pick one based on your available RAM:
| Model | RAM | Command | Notes |
|---|---|---|---|
llama3 |
8 GB | ollama pull llama3 |
Good all-rounder |
mistral |
8 GB | ollama pull mistral |
Fast, sharp reasoning |
phi3 |
4 GB | ollama pull phi3 |
Runs on low-end hardware |
qwen2 |
8 GB | ollama pull qwen2 |
Strong on structured output |
llama3:70b |
40 GB | ollama pull llama3:70b |
Best quality, high-end only |
Step 3 — Start the Ollama server
ollama serve
# Runs at http://localhost:11434 by defaultStep 4 — Optional: change the default model
The terminal defaults to:
{
"selected_preset": "ollama",
"backend": "ollama",
"model": "llama3",
"ollama_host": "http://localhost:11434"
}data/runtime/agents/active_agent.json is created automatically on first run. To use a different Ollama model, either use the Agents tab command box:
/agent ollama gemma4:e2b
or edit data/runtime/agents/active_agent.json:
{
"selected_preset": "ollama",
"backend": "ollama",
"model": "mistral",
"ollama_host": "http://localhost:11434"
}You can also keep the current backend and only change its model:
/model mistral
Step 5 — Run the terminal
python -m apps.tui.dashboard_tuiOpen the Agents tab and hit Analyze — the agent will pull your current shortlist and return a structured bull/bear breakdown per stock.
Runs Gemma 4 directly in-process via MLX. No server needed — faster response on M-series chips.
pip install mlx-vlm
# Model (~3 GB) downloads automatically on first useTo use this backend, run this in the Agents tab command box or set the same preset in data/runtime/agents/active_agent.json:
/agent gemma4_mlx
Claude is optional and never used as the default fallback.
# Install Claude Code CLI
npm install -g @anthropic-ai/claude-code # or via brew
# Authenticate
claude loginThen set active_agent.json backend to "claude", or switch from the TUI:
/agent claude
All three backends are hot-swappable without restarting the terminal. In the Agents tab chat box:
/agent
/agent list
/agent ollama gemma4:e2b
/agent gemma4_mlx
/agent claude
/model qwen2
The setting persists to data/runtime/agents/active_agent.json.
| Key | Action |
|---|---|
1–9 |
Switch tabs |
R |
Refresh market data |
B |
Buy (paper) |
S |
Sell (paper) |
N |
New account |
A |
Activate account |
W |
Sync watchlist |
H |
Help / shortcuts |
Q |
Quit |
nepse-quant-terminal/
├── apps/tui/
│ ├── dashboard_tui.py # Main TUI application
│ └── dashboard_tui.tcss # Textual CSS styles
├── backend/
│ ├── trading/
│ │ ├── paper_trader.py # Manual paper order execution
│ │ ├── live_trader.py # Portfolio persistence utilities
│ │ ├── tui_trading_engine.py # Per-account auto-trading engine
│ │ └── strategy_registry.py # Strategy load/save/assign
│ ├── backtesting/
│ │ └── simple_backtest.py # Signal engine + backtest runner
│ ├── market/ # Market data, quotes, scraping
│ ├── agents/ # AI agent (Ollama / Gemma MLX / Claude)
│ └── quant_pro/
│ ├── gold_hedge.py # Gold regime overlay
│ ├── satellite_data.py # Hydropower signal data
│ ├── regime_detection.py # Market regime classifier
│ └── paths.py # Project path utilities
├── configs/
│ └── long_term.py # Default strategy parameters (C5 baseline)
├── data/
│ └── strategy_registry/ # Strategy JSON configs
├── validation/ # Statistical validation suite
│ ├── walk_forward.py
│ ├── monte_carlo.py
│ ├── cscv_pbo.py
│ ├── statistical_tests.py
│ └── run_all.py
└── requirements.txt
The NEPSE trading calendar lives at backend/quant_pro/nepse_calendar.py.
- Paper trading only. No broker API. All trades are simulated locally.
- NEPSE trades Monday–Friday by default (it switched from Sunday–Thursday in April 2026). Set
NEPSE_TRADING_WEEK=sun_thuto use the legacy calendar. The calendar module handles public holidays. - Holding periods are in trading days, not calendar days. 40 trading days ≈ 8 NEPSE weeks.
- The backtest includes realistic transaction costs: SEBON levy, broker commission, DP charges.
- The gold hedge module uses Nepal Rastra Bank gold price data — no external API required.
The base backtest engine is intentionally simple, and these limitations inflate headline returns:
- No slippage in the base engine.
run_backtestcharges fees but fills at the open with zero slippage. Slippage is only estimated in a separate validation phase (validation/slippage.py); the +88% figure does not include it. - Circuit-locked days fill at the band. Entry prices are clamped to the ±10% circuit limit (
apply_circuit_breaker), so a day where a stock is limit-locked still "fills" at the band — a price you often could not actually transact at on a thin NEPSE name. - Survivorship bias. The universe is the set of symbols currently in the bundled database (a static snapshot). Delisted/suspended names are not reconstructed point-in-time, which biases historical results upward.
Run python -m validation.run_all and read the deflated Sharpe, random-baseline percentile, and benchmark (alpha vs. beta) outputs rather than the headline return. See docs/VALIDATION_METHODOLOGY.md.
MIT
