Shop It Docs
TradingView UDF

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 nextTime so the library knows when to stop fetching older ranges
  • Validation + UDF-shaped errors{"s":"error","errmsg":"..."}
  • Cachingsync.Once bytes for /config, Redis for /symbols

What this module does not own

  • The NEPSE ingester that pulls from mdpapi.smartwealthpro.com and 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 /history every ~10s and the chart updates at poll cadence rather than tick cadence. /history serves only persisted bars.
  • Portfolio, watchlist, auth — completely separate concerns.

Endpoint summary

PathMethodPurposeContent-Type
/configGETServer capabilities and supported resolutionsapplication/json
/timeGETServer time in Unix secondstext/plain
/symbols?symbol=XGETResolve a ticker into full symbol metadataapplication/json
/search?query=X&limit=NGETAutocomplete symbol searchapplication/json (bare array)
/history?symbol=X&resolution=R&from=T1&to=T2GETOHLCV bars for a time rangeapplication/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.go

SQL queries live in internal/platform/database/queries/ (companies.sql, charts.sql) and are regenerated with sqlc generate.

Where to go next