TradingView UDF Overview
Backend contract between the Bullhouse NEPSE API and TradingView's Charting Library
The Bullhouse NEPSE API exposes a TradingView UDF (Universal DataFeed) surface at /api/nepse/tradingview/*. It implements the spec that TradingView's Charting Library consumes so a browser-side new Datafeeds.UDFCompatibleDatafeed(base) call can render a fully functional chart against NEPSE data without any adapter code on the frontend.
UDF is a frozen third-party spec. Response shapes are dictated by TradingView's JS library — root-level JSON, plain-text /time, bare-array /search. These are non-negotiable.
What this module owns
- 5 UDF HTTP endpoints —
/config,/time,/symbols,/search,/history - Symbol namespace — stocks (
NABIL), the 4 top-level indices, and 13 sector sub-indices (^NEPSE,^BANKING, …) all served through the same endpoints - Response shape conformance to TradingView's UDF spec (root-level, flat supports, integer volumes)
- Query routing between rolling intraday and permanent daily tables, across 3 kinds (stock / index / sub-index)
- Symbol metadata — session string with day-of-week mask, timezone, price scale
- Aggregation of daily bars into weekly and monthly candles (Mon-start buckets) for every kind
- Scroll-back hint via
nextTimeso the library knows when to stop fetching older ranges - Validation + UDF-shaped errors —
{"s":"error","errmsg":"..."} - Caching —
sync.Oncebytes for/config, Redis for/symbols
What this module does not own
- The NEPSE ingester that pulls from
mdpapi.smartwealthpro.comand writes to Postgres — separate module. - Live-tick fan-out via WebSocket. Deferred to Phase 6 (see
docs/TRADINGVIEW_UDF_PHASE6.md); until then TV's default datafeed polls/historyevery ~10s and the chart updates at poll cadence rather than tick cadence./historyserves only persisted bars. - Portfolio, watchlist, auth — completely separate concerns.
Endpoint summary
| Path | Method | Purpose | Content-Type |
|---|---|---|---|
/config | GET | Server capabilities and supported resolutions | application/json |
/time | GET | Server time in Unix seconds | text/plain |
/symbols?symbol=X | GET | Resolve a ticker into full symbol metadata | application/json |
/search?query=X&limit=N | GET | Autocomplete symbol search | application/json (bare array) |
/history?symbol=X&resolution=R&from=T1&to=T2 | GET | OHLCV bars for a time range | application/json |
Every endpoint is rate-limited at 60 requests per minute per IP and wrapped by a TV-scoped panic recoverer that emits UDF-shaped JSON on panic (never plain text or HTML).
NEPSE-specific decisions
Monday–Friday, 11:00–15:00 NPT.
UDF session string: 1100-1500:23456
The day mask uses UDF's 1=Sunday … 7=Saturday encoding, so 23456 = Mon–Fri. Timezone is fixed to Asia/Kathmandu.
Intraday candles live in a rolling 3-trading-day window. Requests with from older than that window skip the intraday table entirely and return no_data with a nextTime hint pointing at the newest intraday tick we still have (if any).
Daily, weekly, and monthly history is permanent.
pricescale: 100, minmov: 1 — NEPSE quotes are in NPR with 2 decimal places.
volume_precision: 0 — NEPSE volume is whole shares. The /history response serializes v as JSON integers (no decimal point).
Quick start
BASE="https://api.ranjanyadav.com.np/api/nepse/tradingview"
# Capabilities
curl -s "$BASE/config" | jq .
# Resolve a symbol
curl -s "$BASE/symbols?symbol=NABIL" | jq .
# A week of daily NABIL bars
curl -s "$BASE/history?symbol=NABIL&resolution=1D&from=1735689600&to=1736294400" | jq .
# NEPSE index — same endpoints, `^` prefix for the index namespace
curl -s "$BASE/symbols?symbol=^NEPSE" | jq '.type, .description'
curl -s "$BASE/history?symbol=^BANKING&resolution=1W&from=1704067200&to=1736294400" | jq '.t | length'const datafeed = new Datafeeds.UDFCompatibleDatafeed(
'https://api.ranjanyadav.com.np/api/nepse/tradingview',
);
new TradingView.widget({
container: 'tv-chart',
symbol: 'NEPSE:NABIL',
interval: 'D',
datafeed,
library_path: '/charting_library/',
locale: 'en',
timezone: 'Asia/Kathmandu',
});See Integration for the full frontend guide including subscribeBars.
Implementation layout
internal/modules/nepse/tradingview/
├── handler.go # HTTP handlers — Config, Time, Symbols, Search, History
├── service.go # Business logic — resolution routing, cache, sqlc calls
├── types.go # UDF DTOs — ConfigResponse, SymbolResponse, HistoryResponse, SearchResult
├── write.go # Envelope bypass — writeJSON, writeUDFError
├── cache.go # sync.Once bytes cache for /config
├── recovery.go # TV-scoped JSON panic recoverer
├── handler_test.go
├── service_test.go
├── cache_test.go
└── recovery_test.goSQL queries live in internal/platform/database/queries/ (companies.sql, charts.sql) and are regenerated with sqlc generate.
Where to go next
- Architecture — request lifecycle + system diagram
- Data flow — how NEPSE ticks reach the chart
- History endpoint — the most complex route
- Integration — wiring TradingView's Charting Library to this backend