Nepse Decode · Chart & signals · rule-based, no black boxes
Signal Systems Methodology
Backtest numbers are produced by tools/build-signals.js; the three signal pages are built by tools/build-signal-pages.js (the sole source of truth for those pages); the Live forward record on each page is rendered from live_track_record in the signal JSON, rewritten daily after close by tools/track-live-signals.py. This page states every rule, formula, and assumption exactly as coded, so any figure can be traced and reproduced. Educational research — not investment advice. Past performance does not predict future results.
1. Data
- Source: daily OHLCV per NEPSE symbol — historical bars from the open
samirwagle/Nepse-All-Scraperdataset (MIT licence), with the most recent sessions overlaid from our own NEPSE feed. No fabricated bars. - Universe: every symbol with ≥ 250 valid sessions (316 symbols in the latest build). Symbols whose data ends more than 90 days before the newest bar are excluded as stale/delisted.
- Sample period: 2024-09-01 → 2026-10-28 (≈ 2.2 years) — the published backtest window, filled from the live data below. Individual symbols start later; signals are evaluated from each symbol's 61st bar.
- Cash dividends are not received in the simulation — a stated limitation that slightly understates high-dividend stocks.
2. Data sanitisation
The feed contains bars with impossible OHLC — e.g. NBL on 2013-05-09 printed open 532 against a 143–145 traded range (an unadjusted corporate-action open). Such bars corrupt ATR, stops, and the corporate-action detector, so:
- Bars with non-positive OHLC,
high < low, or malformed dates are dropped; dates de-duplicated. - If
openorclosefalls outside the bar's own[low, high], it is clamped into the range. Every repair is counted and published (bars_repaired).
3. Corporate-action adjustment
NEPSE enforces a ±10% daily circuit, so an overnight gap beyond ±10% is a bonus/split/rights adjustment, not a real move. On the sanitised series, if open / prevClose is outside [0.90, 1.10], all prior bars are reverse-adjusted: prices × ratio, volume ÷ ratio, turnover unchanged. No new entries are taken on the adjustment bar itself. Adjustment count is published.
4. Indicators (all causal — bar i uses data up to i only)
SMA(n): simple moving average of closes.RSI(14): Wilder's RSI.ATR(14): Wilder's smoothing of the true range.- Bollinger (20, 2):
lower = SMA20 − 2σ.
5. Entry rules
Entries are taken at the signal day's close. One position per symbol; max 10 concurrent positions. All systems require 20-day average turnover above Rs 10 lakh.
Momentum — Donchian-50 breakout + trend filter
close > SMA50andSMA20 > SMA50; close prints a 50-session highest close;55 ≤ RSI(14) ≤ 80.
Trend Relay — SMA20/SMA50 golden cross + volume
close < SMA50on ≥ 20 of the last 30 sessions;SMA20crosses aboveSMA50;volume > 1.5 × 20-day average.
Reversal — oversold bounce
RSI(14) < 30andclose < lower Bollinger Band; bullish candle (close > open) closing in the top half of its range.
6. Exits, stops, targets
- Stop-loss (fixed at entry):
entry − 2×ATR(14)(Momentum, Trend Relay),entry − 1.5×ATR(14)(Reversal). If a bar's low touches the stop, exit atmin(stop, close)— gap-through exits at the close, no fantasy fills. - Momentum trail: exit at the close when it breaks the 20-session lowest close.
- Trend Relay trail: exit on
SMA20crossing back belowSMA50, or 3 consecutive closes underSMA50. - Reversal: exit at the close when
close ≥ SMA20orRSI(14)crosses above 55. - T1/T2 targets are reference levels only — they are NOT traded. The backtest exits solely via the rules above (
targets_traded: falsein the data).
7. Position sizing and costs
- Start Rs 1 Cr; each position a fixed Rs 10 lakh notional (
qty = floor(10,00,000 / entryPrice)). Size does not compound with portfolio growth — deliberately conservative, disclosed. - 0.5% round-trip cost (broker + SEBON + DP, simplified): half at entry, half at exit.
8. How each statistic is calculated
Let V0 = Rs 1 Cr, Vf = final portfolio value (cash + open positions at last close − exit costs), T = 2.15 years (the published backtest window, filled from the live data below).
| Figure | Formula |
|---|---|
| Total return % | (Vf − V0) / V0 × 100 |
| CAGR % | (Vf / V0)^(1/T) − 1, ×100 |
| Capital multiple | Vf / V0 |
| Win rate % | winning closed trades ÷ all closed trades × 100 (win = net P&L > 0) |
| Profit factor | Σ winning Rs ÷ |Σ losing Rs| |
| Avg win / loss % | mean Rs P&L of winners (losers) ÷ mean entry notional × 100 |
| Avg holding days | mean holding days (entry day = 0) |
| Max drawdown % | deepest peak-to-trough fall of the weekly marked-to-market equity curve |
| Sharpe / Sortino | mean / downside-deviation of weekly portfolio returns × √52 |
| Benchmark CAGR | NEPSE index buy & hold over the same window |
| Alpha %/yr | system CAGR − benchmark CAGR |
The equity curve marks the full portfolio to market weekly (cash + open positions − exit costs). Alerts are positions still open at the dataset's last date. The ledger lists every closed trade with entry/exit prices, stop, targets, exit reason, Rs and % P&L, and holding days (most recent 500 in the download).
9. Known limitations
- Corporate-action factors are inferred, not official.
- Fixed Rs 10L notional understates what compounding would show.
- Cash dividends are not received.
- Entries at the signal day's close assume the close is tradable; only the 0.5% cost models friction.
- One realised history — regimes change; nothing here predicts the future.
node tools/build-signals.js --no-fetch rebuilds the published JSONs from the checked-in cache — every stat above recomputes exactly (only generated_at changes).10. Live forward record — paper trades from real alerts
Each signal page also carries a Live forward record: a forward paper-trade ledger built from real alerts, not from the backtest.
- Every live Entry alert becomes a Rs 10 lakh paper trade at the signal close.
- Exits fire on real forward OHLC — Target 1, stop loss, a ratcheting trailing stop, or a 20-session time exit. No lookahead: only bars after the entry date are used.
- P&L is net of 0.5% round-trip charges.
- Seeded from the engine's verifiable alert history: every distinct BUY alert found in git-committed signal JSONs on origin/main (2026-09-30 commits onward), each traced to its real alert record (system, symbol, signal date, published entry/stop/targets). Forward tracking started 2026-08-03; SIKLES was excluded because no verifiable alert record exists for it — never invented.
- Recomputed daily after market close by
tools/track-live-signals.py, which rewriteslive_track_recordin each signal JSON. The pages render these numbers client-side, so they stay fresh with no page rebuild. - Honesty rule: win/failure rates appear only after trades close. With zero closed trades the section says so plainly — never 0%, never the backtest rate.
- Small-sample discipline (R5): n = decided trades only — open trades are shown separately and never enter n, the win rate, or profit factor. Win rate = wins ÷ n, shown with a Wilson 95% interval; profit factor = gross wins ÷ gross losses (net of 0.5% charges). Below n=30 the too-few-trades badge shows on the record itself: the sample is too small for the win rate or profit factor to prove an edge.
Data: daily NEPSE OHLCV, corporate-action adjusted. …. Educational only — not investment advice.