Shop It Docs
TradingView UDF

GET /history

OHLCV bars in TradingView UDF format with intraday, daily, weekly, monthly routing and nextTime scroll-back hints

/history is the core endpoint — it returns OHLCV bars for a symbol in a time range. The Charting Library calls it on mount, on every zoom/pan, and as the user scrolls back.

  • Method: GET
  • Path: /api/nepse/tradingview/history
  • Content-Type: application/json

Query parameters

ParamRequiredTypeNotes
symbolstringTicker. NEPSE:NABILNABIL (prefix stripped), upper-cased. Prefix with ^ for indices (^NEPSE) and sector sub-indices (^BANKING) — same endpoints, same response shape.
resolutionstringOne of 1, 5, 15, 30, 60, 1D, 1W, 1M. Whitelist-checked.
fromintUnix seconds, inclusive. Must be ≥ 946684800 (2000-01-01 UTC).
tointUnix seconds, inclusive. Must be ≤ now + 48h.
countbackintAccepted for UDF compatibility; currently unused.

Validation happens in the handler before the service runs — see Errors.

Success response

{
  "s": "ok",
  "t": [1735689600, 1735776000, 1735862400],
  "o": [500.0, 510.0, 515.0],
  "h": [515.0, 520.0, 522.0],
  "l": [498.0, 508.0, 513.0],
  "c": [510.0, 515.0, 520.0],
  "v": [12345, 6789, 9876]
}

Note v is emitted as JSON integers (no decimal point) — consistent with volume_precision: 0.

{ "s": "no_data" }

No older data exists (or we can't look for it — e.g. the symbol was unknown on the daily path). The Charting Library marks the bar slot empty and stops scrolling back.

{
  "s": "no_data",
  "nextTime": 1776230100
}

No bars in the requested range, but older bars exist. nextTime is the Unix second of the newest bar strictly before from. The library jumps the next fetch to start around there instead of requesting ever-older ranges forever.

type HistoryResponse struct {
    S        string    `json:"s"`                    // "ok" | "no_data" | "error"
    Errmsg   string    `json:"errmsg,omitempty"`     // only when s="error"
    T        []int64   `json:"t,omitempty"`          // bar timestamps (unix s)
    O        []float64 `json:"o,omitempty"`
    H        []float64 `json:"h,omitempty"`
    L        []float64 `json:"l,omitempty"`
    C        []float64 `json:"c,omitempty"`
    V        []int64   `json:"v,omitempty"`          // int64 — whole shares
    NextTime *int64    `json:"nextTime,omitempty"`   // only when s="no_data"
}

NextTime is a pointer + omitempty so 0 (a valid Unix epoch) is distinguishable from "not set". OHLCV arrays are omitted when s != "ok".

Resolution routing

The service first resolve()s the symbol into a kindStock | kindIndex | kindSubIndex, then picks a data source based on resolution:

Kind picks the table family; resolution picks the aggregation shape. The OHLCV marshal loop is shared across kinds via generic helpers (bucketedToResponse, intradayRowsToResponse), so bar semantics stay identical across stocks and indices.

Intraday (1, 5, 15, 30, 60)

  • Tables (by kind): nepse_intraday_prices (stock), nepse_intraday_indices (index), nepse_intraday_sub_indices (sub-index) — all under the rolling 3-trading-day retention window.
  • Aggregation: SQL-side via GetIntradayStockCandles / GetIntradayIndexCandles / GetIntradaySubIndexCandles with the interval_secs parameter. For resolution 15, interval_secs = 900. Open = first polled_at's open; high = MAX; low = MIN; close = last polled_at's close; volume = SUM.
  • Retention guard: if from is outside the 3-trading-day window (timezone.IsWithinNTradingDays), the service skips the DB query entirely and returns intradayNoData(resolved, from) — which attempts to populate nextTime from the most recent surviving intraday tick using the kind-appropriate GetNext*IntradayBarBefore.
  • Bucket timestamp (t[i]) = bucket-start polled_at as Unix seconds.

Daily (1D)

  • Tables: nepse_price_history / nepse_index_history / nepse_sub_index_history.
  • Query: GetStockPriceHistory(company_id, ...), GetIndexHistory(index_id, ...), GetSubIndexHistory(sub_index_id, ...) — return rows as-is.
  • Bucket timestamp convention: 11:00 NPT of the trading date. Same across kinds so a 1D bar on NABIL and ^NEPSE lands at the same instant for the same date.

Weekly (1W)

  • Tables: same three daily tables, aggregated on the fly by GetStockWeeklyHistory, GetIndexWeeklyHistory, or GetSubIndexWeeklyHistory.
  • Bucketing: date_trunc('week', trade_date) / date_trunc('week', trading_date) — Postgres' ISO week is Monday-start, which matches NEPSE's Mon–Fri trading week.
  • OHLCV aggregation: (array_agg(open ORDER BY <date> ASC))[1] for open, MAX for high, MIN for low, (array_agg(close ORDER BY <date> DESC))[1] for close, SUM for volume.
  • Bucket timestamp: 00:00 NPT of the Monday starting the week.

Monthly (1M)

  • Identical to weekly but with date_trunc('month', ...) against the same three tables.
  • Bucket timestamp: 00:00 NPT of the 1st of the month.

The nextTime scroll-back contract

When the library asks for [from, to] and gets no_data, it doesn't know whether:

  1. There's older data it just hasn't asked for yet (keep scrolling back), or
  2. There's literally no data (stop).

UDF's nextTime field answers that question: if populated, it's a timestamp in the past where data exists. The library then issues its next request with a to near nextTime — skipping huge empty ranges efficiently.

When nextTime is populated

KindDaily/Weekly/Monthly helperIntraday helper
StockGetNextDailyBarBefore(company_id, from)GetNextIntradayBarBefore(symbol, from)
IndexGetNextIndexDailyBarBefore(index_id, from)GetNextIndexIntradayBarBefore(index_id, from)
Sub-indexGetNextSubIndexDailyBarBefore(sub_index_id, from)GetNextSubIndexIntradayBarBefore(sub_index_id, from)

Daily/weekly/monthly nextTime is returned as 11:00 NPT of the next-earlier trading date. Intraday nextTime is returned as the raw polled_at epoch. The dispatch lives in (*Service).dailyNoData and (*Service).intradayNoData, which both switch on resolvedSymbol.kind.

When nextTime is not populated

  • Symbol unknown (404-class resolve) — GetHistory returns {s: "no_data"} without even attempting the next-before lookup.
  • No daily/intraday rows exist before from at all (truly out-of-range query).
  • The "next-before" query errors out — degraded, but still returns a clean no_data.

Examples

Valid daily range

curl -s "$BASE/history?symbol=NABIL&resolution=1D&from=1735689600&to=1736294400" | jq .
{
  "s": "ok",
  "t": [1735689600, ...],
  "o": [506.5, ...],
  "h": [517, ...],
  "l": [502, ...],
  "c": [514, ...],
  "v": [29808, ...]
}

Empty daily with nextTime

Range entirely after NABIL's last bar (2026-04-15):

curl -s "$BASE/history?symbol=NABIL&resolution=1D&from=1776470400&to=1776707761" | jq .
{
  "s": "no_data",
  "nextTime": 1776230100
}

1776230100 decodes to 2026-04-15 11:00 NPT — NABIL's last daily bar, reported using the daily 11:00-NPT convention.

Weekly bucketing demo

# Same range, 1D → 21 bars, 1W → 5 bars
curl -s "$BASE/history?symbol=NABIL&resolution=1D&from=..." | jq '.t | length'
# 21
curl -s "$BASE/history?symbol=NABIL&resolution=1W&from=..." | jq '.t | length'
# 5

Each weekly t lands on a Monday at 00:00 NPT:

1773598500 → 2026-03-16 00:00 Mon
1774203300 → 2026-03-23 00:00 Mon
1774808100 → 2026-03-30 00:00 Mon
1775412900 → 2026-04-06 00:00 Mon
1776017700 → 2026-04-13 00:00 Mon

Weekly OHLCV correctness

For 2026-03-16..22 (one NEPSE week):

FieldDaily aggregation (manual)Weekly bar from /history?resolution=1W
Open530 (Monday's open)530
Highmax across days = 545.9545.9
Lowmin across days = 523523
CloseFriday's close = 542.1542.1
Volumesum of 5 days = 445053445053

Error responses

All errors follow UDF shape {"s":"error","errmsg":"..."}. See Errors for full validation rules.

ConditionStatuserrmsg
Missing symbol400symbol is required
resolution not in whitelist400invalid resolution
from/to missing400from and to are required
from/to not an integer400from must be a unix timestamp / to must be a unix timestamp
from > to400from must be <= to
from < 2000-01-01 or to > now+48h400timestamps out of range
Request exceeded 5s timeout504timeout
DB error after validation500internal_error

Performance

  • Rate limit: 60 rpm per IP (shared with other TV endpoints).
  • Request budget: 5s per request via context.WithTimeout. On timeout → 504 + timeout UDF error.
  • gzip: applied to the TV group; typical 225-bar daily response shrinks from 8.4KB → 3KB (~2.75×).
  • Nginx proxy_cache: recommended 5-minute TTL for general /history, 10-second TTL for intraday-with-recent-to. See Caching.

Edge cases

Market hours: incomplete intraday bars

The currently-forming intraday bar is not returned by /history until the poller flushes it. The poller runs every ~2s during market hours, so /history trails live by at most one poll cycle. TV's default UDFCompatibleDatafeed polls /history every ~10s via its built-in subscribeBars, so the chart's last bar visibly updates within seconds without any extra integration. A tick-level WebSocket path (Phase 6) is specced but deferred — see docs/TRADINGVIEW_UDF_PHASE6.md.

Unknown symbol (any resolution)

resolve() returns a 404-class error → GetHistory short-circuits to {"s":"no_data"} with no nextTime. Without a resolved ID we can't run the kind-specific "next-before" query. This is a signal, not an error — the chart treats it like any other empty response. Applies to unknown stocks (XXX) and unknown ^-prefixed symbols (^XXX) alike.

to > now

Allowed (up to now + 48h) because the Charting Library often sends slightly-in-the-future to for the currently-forming bar. The server simply returns data up to the last persisted bar.

Weekly request within a week that isn't complete yet

If today is Wednesday and the client asks for this week's weekly bar, the aggregation runs on the partial week's daily rows (Mon, Tue, Wed). The resulting bar updates as new daily rows land. This is expected UDF behavior for real-time weekly charts.

Implementation

  • Handler: internal/modules/nepse/tradingview/handler.go(*Handler).History
  • Service routing: internal/modules/nepse/tradingview/service.go(*Service).GetHistoryresolve + get{Daily,Weekly,Monthly,Intraday}HistoryForKind
  • Stock helpers: getStockDailyHistoryByID, getStockWeeklyHistoryByID, getStockMonthlyHistoryByID, getStockIntradayHistory
  • Index helpers: getIndexDailyHistory, getIndexWeeklyHistory, getIndexMonthlyHistory, getIndexIntradayHistory
  • Sub-index helpers: getSubIndexDailyHistory, getSubIndexWeeklyHistory, getSubIndexMonthlyHistory, getSubIndexIntradayHistory
  • No-data helpers: dailyNoData / intradayNoData (kind-aware)
  • sqlc queries in internal/platform/database/queries/charts.sql:
    • Stocks: GetStockPriceHistory, GetStockWeeklyHistory, GetStockMonthlyHistory, GetIntradayStockCandles, GetNextDailyBarBefore, GetNextIntradayBarBefore
    • Indices: GetIndexHistory, GetIndexWeeklyHistory, GetIndexMonthlyHistory, GetIntradayIndexCandles, GetNextIndexDailyBarBefore, GetNextIndexIntradayBarBefore
    • Sub-indices: GetSubIndexHistory, GetSubIndexWeeklyHistory, GetSubIndexMonthlyHistory, GetIntradaySubIndexCandles, GetNextSubIndexDailyBarBefore, GetNextSubIndexIntradayBarBefore