TradingView UDF
Reference Tables
Compact reference for all TV endpoints, fields, errors, cache keys, and database tables
| Path | Method | Query params | Content-Type | Rate-limited | gzipped | Cached |
|---|
/config | GET | — | application/json | ✓ (60/min) | ✓ | In-process + nginx 1h |
/time | GET | — | text/plain | ✓ | ✓ | Never |
/symbols | GET | symbol | application/json | ✓ | ✓ | Redis 5m + nginx 5m |
/search | GET | query, limit, type?, exchange? | application/json (bare array) | ✓ | ✓ | Nginx 1m |
/history | GET | symbol, resolution, from, to, countback? | application/json | ✓ | ✓ | Nginx 5m |
Base path: /api/nepse/tradingview. All endpoints are GET-only.
| Kind | Prefix | type | Resolver | Table family |
|---|
| Stock | — | stock | FindCompanyBySymbol → company_id | nepse_price_history, nepse_intraday_prices |
| Index | ^ | index | FindIndexByTVSymbol → index_id | nepse_index_history, nepse_intraday_indices |
| Sector sub-index | ^ | index | FindSubIndexByTVSymbol → sub_index_id | nepse_sub_index_history, nepse_intraday_sub_indices |
Symbol resolution strips an optional NEPSE: exchange prefix, case-normalizes, then:
- If the remaining symbol starts with
^, look up nepse_indices.tv_symbol = <rest>; on miss, fall back to nepse_sub_indices.tv_symbol = <rest>.
- Otherwise look up
nepse_companies.symbol = <symbol>.
The master-table column tv_symbol is NOT NULL UNIQUE and backfilled by migration 000018_add_tv_symbol.
Same 8 resolutions apply to every kind; only the underlying table changes.
| Resolution | Path | Stock table | Index table | Sub-index table | Aggregation | t convention |
|---|
1 | intraday | nepse_intraday_prices | nepse_intraday_indices | nepse_intraday_sub_indices | SQL bucket 60s | polled_at |
5 | intraday | same | same | same | SQL bucket 300s | polled_at |
15 | intraday | same | same | same | SQL bucket 900s | polled_at |
30 | intraday | same | same | same | SQL bucket 1800s | polled_at |
60 | intraday | same | same | same | SQL bucket 3600s | polled_at |
1D | daily | nepse_price_history | nepse_index_history | nepse_sub_index_history | none (1 row per day) | 11:00 NPT |
1W | weekly | same | same | same | date_trunc('week', …) (Mon-start) | 00:00 NPT Monday |
1M | monthly | same | same | same | date_trunc('month', …) | 00:00 NPT 1st |
| JSON | Go | Description |
|---|
supported_resolutions | []string | ["1","5","15","30","60","1D","1W","1M"] |
supports_search | bool | true |
supports_group_request | bool | false |
supports_marks | bool | false |
supports_timescale_marks | bool | false |
supports_time | bool | true |
exchanges | []Exchange | [{value, name, desc}] — NEPSE only |
symbols_types | []SymbolType | [{name,value}] — All types + Stock + Index |
| JSON | Go | Example |
|---|
name | string | NABIL |
ticker | string | NABIL |
full_name | string | NEPSE:NABIL |
description | string | Nabil Bank Limited |
type | string | stock or index (same endpoints, kind-dependent) |
session | string | 1100-1500:23456 |
timezone | string | Asia/Kathmandu |
exchange | string | NEPSE |
listed_exchange | string | NEPSE |
minmov | int | 1 |
pricescale | int | 100 |
has_intraday | bool | true |
intraday_multipliers | []string | ["1","5","15","30","60"] |
has_daily | bool | true |
has_weekly_and_monthly | bool | true |
supported_resolutions | []string | same 8 |
currency_code | string | NPR |
data_status | string | streaming |
volume_precision | int | 0 |
| JSON | Go | Example |
|---|
symbol | string | NABIL / ^NEPSE (indices keep the ^) |
full_name | string | NEPSE:NABIL / NEPSE:^NEPSE |
description | string | Nabil Bank Limited / NEPSE Index / Banking |
exchange | string | NEPSE |
ticker | string | same as symbol |
type | string | stock or index |
Response is a bare array: [SearchResult, SearchResult, …]. Empty → [].
| JSON | Go | Notes |
|---|
s | string | ok / no_data / error |
errmsg | string | only on s=error, omitempty |
t | []int64 | Unix seconds; only on s=ok, omitempty |
o | []float64 | only on s=ok, omitempty |
h | []float64 | only on s=ok, omitempty |
l | []float64 | only on s=ok, omitempty |
c | []float64 | only on s=ok, omitempty |
v | []int64 | JSON integers (no decimal point); only on s=ok |
nextTime | *int64 | only on s=no_data when older data exists; omitempty |
| HTTP | s | errmsg | Source | Notes |
|---|
| 400 | error | symbol is required | /symbols, /history | Missing required param |
| 400 | error | invalid resolution | /history | Not in whitelist |
| 400 | error | from and to are required | /history | |
| 400 | error | from must be a unix timestamp | /history | Parse failure |
| 400 | error | to must be a unix timestamp | /history | Parse failure |
| 400 | error | from must be <= to | /history | |
| 400 | error | timestamps out of range | /history | from < 2000-01-01 or to > now+48h |
| 404 | error | unknown_symbol | /symbols | Not in nepse_companies / nepse_indices / nepse_sub_indices, or status!='A' |
| 429 | — | (envelope) | rate limiter | Not UDF-shaped — standard error envelope |
| 500 | error | internal_error | any | DB error or recovered panic |
| 504 | error | timeout | /symbols, /history | 5s context deadline exceeded |
| Key pattern | TTL | Layer | Notes |
|---|
nepse:tv:symbol:<SYMBOL> | 5m | Redis | Resolved SymbolResponse. 404s never cached. |
| (none — in-process) | process lifetime | Go sync.Once | /config marshaled bytes |
Constants:
cache.PrefixNepseTVSymbol // "nepse:tv:symbol:"
cache.NepseTVSymbolKey(sym) // "nepse:tv:symbol:<SYMBOL>"
cache.TTLNepseTVSymbol() // 5 * time.Minute
| Table | Endpoints | Reads | Writes |
|---|
nepse_companies | /symbols, /search | FindCompanyBySymbol, SearchForTV | — (scheduler/admin only) |
nepse_indices | /symbols, /search | FindIndexByTVSymbol, SearchForTV | — (poller only) |
nepse_sub_indices | /symbols, /search | FindSubIndexByTVSymbol, SearchForTV | — (poller only) |
nepse_price_history | /history stock 1D/1W/1M | GetStockPriceHistory, GetStockWeeklyHistory, GetStockMonthlyHistory, GetNextDailyBarBefore | — (EOD cron only) |
nepse_index_history | /history index 1D/1W/1M | GetIndexHistory, GetIndexWeeklyHistory, GetIndexMonthlyHistory, GetNextIndexDailyBarBefore | — (EOD cron only) |
nepse_sub_index_history | /history sub-index 1D/1W/1M | GetSubIndexHistory, GetSubIndexWeeklyHistory, GetSubIndexMonthlyHistory, GetNextSubIndexDailyBarBefore | — (EOD cron only) |
nepse_intraday_prices | /history stock 1/5/15/30/60 | GetIntradayStockCandles, GetNextIntradayBarBefore | — (poller only) |
nepse_intraday_indices | /history index 1/5/15/30/60 | GetIntradayIndexCandles, GetNextIndexIntradayBarBefore | — (poller only) |
nepse_intraday_sub_indices | /history sub-index 1/5/15/30/60 | GetIntradaySubIndexCandles, GetNextSubIndexIntradayBarBefore | — (poller only) |
The TV module never writes to any table. It is strictly read-side.
All in internal/platform/database/queries/:
| Query | File | Returns |
|---|
FindCompanyBySymbol | companies.sql | company row |
FindIndexByTVSymbol | charts.sql | (id, tv_symbol, index_name) |
FindSubIndexByTVSymbol | charts.sql | (id, tv_symbol, name) |
SearchForTV | charts.sql | merged (tv_symbol, description, kind) across all three masters |
GetStockPriceHistory | charts.sql | stock daily OHLCV rows |
GetIndexHistory | charts.sql | index daily OHLCV rows |
GetSubIndexHistory | charts.sql | sub-index daily OHLCV rows |
GetStockWeeklyHistory / GetStockMonthlyHistory | charts.sql | stock Mon/month-bucketed OHLCV |
GetIndexWeeklyHistory / GetIndexMonthlyHistory | charts.sql | index Mon/month-bucketed OHLCV |
GetSubIndexWeeklyHistory / GetSubIndexMonthlyHistory | charts.sql | sub-index Mon/month-bucketed OHLCV |
GetIntradayStockCandles | charts.sql | stock intraday buckets |
GetIntradayIndexCandles | charts.sql | index intraday buckets |
GetIntradaySubIndexCandles | charts.sql | sub-index intraday buckets |
GetNextDailyBarBefore | charts.sql | stock — next daily trading_date before from |
GetNextIndexDailyBarBefore | charts.sql | index — next daily trade_date before from |
GetNextSubIndexDailyBarBefore | charts.sql | sub-index — next daily trade_date before from |
GetNextIntradayBarBefore | charts.sql | stock — next intraday polled_at before from |
GetNextIndexIntradayBarBefore | charts.sql | index — next intraday polled_at before from |
GetNextSubIndexIntradayBarBefore | charts.sql | sub-index — next intraday polled_at before from |
Order matters. Innermost last.
global:
chimw.RequestID
httpmiddleware.RealIP
chimw.Recoverer ← plaintext fallback
httpmiddleware.RequestLogger
cors.Handler
httpmiddleware.BodyLimit
group /api/nepse/tradingview/*:
rl60.Middleware ← 60 req/min per IP
tradingview.Recoverer() ← JSON panic → UDF error
chimw.Compress(5) ← gzip response
endpoint-specific:
tvContext(r) ← 5s timeout (history, symbols)
| Stage | Typical (warm) | P99 |
|---|
/config | < 1ms (bytes cache) | < 5ms |
/time | < 1ms | < 5ms |
/symbols (Redis hit) | < 2ms | < 10ms |
/symbols (DB) | ~5–20ms | < 100ms |
/search | ~10–50ms | < 200ms |
/history 1D (30 bars) | ~10–30ms | < 150ms |
/history 1D (1 year, ~250 bars) | ~30–80ms | < 300ms |
/history 1W (aggregation) | ~30–80ms | < 300ms |
/history intraday 1m (3 days) | ~50–150ms | < 500ms |
Hard ceiling via context.WithTimeout: 5 seconds, after which → 504 timeout.
BASE="https://api.ranjanyadav.com.np/api/nepse/tradingview"
# Envelope regression scan
for ep in "config" "symbols?symbol=NABIL" "history?symbol=NABIL&resolution=1D&from=1735689600&to=1736294400"; do
curl -s "$BASE/$ep" | jq 'if has("data") and has("message") then "LEAK" else "ok" end'
done
# Bare array check
curl -s "$BASE/search?query=NAB" | jq 'type' # "array"
# Plain text check
curl -sv "$BASE/time" 2>&1 | grep -i content-type # text/plain
# Session mask check
curl -s "$BASE/symbols?symbol=NABIL" | jq '.session' # "1100-1500:23456"
# Integer volume check
curl -s "$BASE/history?symbol=NABIL&resolution=1D&from=1735689600&to=1736294400" \
| jq '.v[0] | type' # "number" but without fractional part in the raw body
| Concern | File |
|---|
| HTTP handlers | internal/modules/nepse/tradingview/handler.go |
| Business logic | internal/modules/nepse/tradingview/service.go |
| UDF DTOs | internal/modules/nepse/tradingview/types.go |
| Envelope bypass | internal/modules/nepse/tradingview/write.go |
/config bytes cache | internal/modules/nepse/tradingview/cache.go |
| JSON panic recovery | internal/modules/nepse/tradingview/recovery.go |
| Router wiring | internal/http/router/router.go (TV group) |
| Rate limiter | internal/http/middleware/ratelimit.go |
| Redis cache helpers | internal/platform/cache/cache.go, keys.go |
| SQL queries | internal/platform/database/queries/companies.sql, charts.sql |
| sqlc-generated Go | internal/platform/database/sqlc/*.go |
| Swagger | apidocs/swagger.json |
| Deployment / nginx | docs/DEPLOYMENT.md |
| Full plan tracker | docs/TRADINGVIEW_UDF_PLAN.md |