request.security()

request.security() lets you access data from other symbols or timeframes within your script. PyneCore runs each security context as a separate OS process with its own Series history, enabling true multi-symbol and multi-timeframe analysis.

Quick Start

1. Prepare OHLCV Data

Each security context needs its own OHLCV data file. Convert your data sources to PyneCore’s binary format using the CLI:

# Chart data (5-minute EURUSD)
pyne data convert-from EURUSD_5m.csv

# Security data (daily EURUSD for HTF analysis)
pyne data convert-from EURUSD_1D.csv

# Or aggregate from existing data
pyne data aggregate EURUSD_5m -tf 1D

This creates .ohlcv + .toml file pairs in workdir/data/.

2. Write Your Script

Use request.security() like you would in Pine Script:

"""@pyne"""

from pynecore.lib import *
from pynecore.types import *


@script.indicator("Multi-Timeframe SMA")
def main():
    # Fetch daily SMA(20) while running on 5-minute chart
    daily_sma: Series[float] = lib.request.security(
        lib.syminfo.tickerid, "1D", lib.ta.sma(lib.close, 20)
    )

    lib.plot.plot(daily_sma, "Daily SMA", color=lib.color.blue)
    lib.plot.plot(lib.close, "Close")

3. Run with Security Data

Use the --security flag to provide OHLCV data for each security context. The flag can be repeated for multiple contexts:

# Single security context (daily data for same symbol)
pyne run multi_tf_sma.py EURUSD_5m --security "1D=EURUSD_1D"

# Multiple security contexts (different symbols)
pyne run advance_decline.py SPX_1D \
  --security "USI:ADVN.NY=USI_ADVN_NY" \
  --security "USI:DECL.NY=USI_DECL_NY"

The format is "KEY=DATA_NAME" where:

  • KEY is "TIMEFRAME" or "SYMBOL:TIMEFRAME" (matching the request.security() call)
  • DATA_NAME is the OHLCV data name in workdir/data/ (without extension)

Key Matching Rules

The security_data dict keys are matched against each request.security() call’s symbol and timeframe:

Key formatExampleMatches
"TIMEFRAME""1D"Any security call with timeframe "1D"
"SYMBOL:TIMEFRAME""AAPL:1H"Exact match on both symbol and timeframe
"SYMBOL""USI:ADVN.NY"Any security call with that symbol

Timeframe-only keys are convenient when all security calls use the same symbol (the chart symbol).

For programmatic usage (ScriptRunner API), see Providing Security Data.

Tip — skip the per-run --security flags. To translate TradingView symbols to your provider-native data once, for every run, declare them in a workdir-level Symbol Map (config/symbol_map.toml) instead. An explicit --security mapping still overrides the symbol map when you need a one-off.

Examples

Multi-Timeframe Indicator

"""@pyne"""

from pynecore.lib import *
from pynecore.types import *


@script.indicator("MTF RSI")
def main():
    rsi_5m: Series[float] = lib.ta.rsi(lib.close, 14)

    # Get RSI from higher timeframes
    rsi_1h: Series[float] = lib.request.security(
        lib.syminfo.tickerid, "60", lib.ta.rsi(lib.close, 14)
    )
    rsi_daily: Series[float] = lib.request.security(
        lib.syminfo.tickerid, "1D", lib.ta.rsi(lib.close, 14)
    )

    lib.plot.plot(rsi_5m, "RSI 5m")
    lib.plot.plot(rsi_1h, "RSI 1H")
    lib.plot.plot(rsi_daily, "RSI Daily")
security_data = {
    "60": "workdir/data/EURUSD_60",  # 1-hour bars
    "1D": "workdir/data/EURUSD_1D",  # daily bars
}

Multi-Symbol Analysis (Advance/Decline Ratio)

"""@pyne"""

from pynecore.lib import *
from pynecore.types import *


@script.indicator("Advance/Decline Ratio")
def main():
    advancing: Series[float] = lib.request.security("USI:ADVN.NY", "", lib.close)
    declining: Series[float] = lib.request.security("USI:DECL.NY", "", lib.close)

    ratio: Series[float] = lib.nz(advancing) / lib.nz(declining, 1.0)
    lib.plot.plot(ratio, "A/D Ratio")
security_data = {
    "USI:ADVN.NY": "workdir/data/USI_ADVN_NY",
    "USI:DECL.NY": "workdir/data/USI_DECL_NY",
}

Note: When the timeframe argument is "" (empty string), the chart’s own timeframe is used.

Supported Features

FeatureStatusNotes
Different timeframesupportedHTF (1D, 1W, 1M, etc.) from lower TF chart
Different symbolsupportedAny symbol with available OHLCV data
Lower timeframe (LTF)supportedrequest.security_lower_tf() returns arrays
Multiple security callssupportedEach gets its own process
Conditional callssupportedInside if/for/while blocks
Nested security callssupportedsecurity(... security(...) ...)
barmerge.gaps_offsupportedForward-fills last value (default)
barmerge.gaps_onsupportedReturns na between periods
barmerge.lookahead_offsupportedMost recently closed bar — historical + live (same-symbol HTF in live)
barmerge.lookahead_last_closedsupportedPyneSys-native synonym for “last closed”; identical transport to lookahead_off
barmerge.lookahead_onsupportedSame-symbol HTF: TV-compatible (live steps into developing HTF bar). Cross-symbol HTF: close[0] is na inside an open HTF period, close[1] at boundary delivers just-closed bar
ignore_invalid_symbolsupportedReturns na for missing symbols
currency parametersupportedAuto-converts result using CurrencyRateProvider
ticker.heikinashi()supportedTransformed to Heikin Ashi bars in the security child, backtest and live (see below)
ticker.renko() / pointfigure() / kagi() / linebreak()unsupportedPath-dependent chart types need tick data PyneCore does not have

How It Works

Under the hood, each request.security() call spawns a separate OS process:

  1. AST transformation rewrites the call into signal/write/read/wait protocol functions
  2. ScriptRunner detects the transformed code, creates shared memory, and spawns processes
  3. Each security process loads its own OHLCV data and runs the script independently
  4. Processes communicate results via shared memory
  5. The chart process waits for security results only when a new period is confirmed
  6. Pipeline parallelism: security processes run on separate CPU cores concurrently

HTF Period Confirmation

For higher-timeframe data, values are confirmed with lookahead_off semantics following TradingView’s historical merge rule: an HTF bar is confirmed on the chart bar whose close instant reaches the HTF bar’s close — the period’s last chart bar already carries the period’s final value, not the next period’s first bar.

Chart bars (5m):    10:00  10:05  10:10 ... 23:50  23:55  00:00
                                                    ^
                                              Closes at 00:00 — exactly when the
                                              daily bar closes, so the day's value
                                              is confirmed on THIS chart bar

For same-timeframe contexts (different symbol), values are confirmed on every bar.

Chart types (Heikin Ashi)

request.security(ticker.heikinashi(symbol), timeframe, expr) returns expr evaluated on Heikin Ashi candles instead of ordinary ones. Heikin Ashi is a deterministic recurrence over the requested timeframe’s candles:

haClose = (open + high + low + close) / 4
haOpen  = (haOpen[1] + haClose[1]) / 2       # first bar: (open + close) / 2
haHigh  = max(high, haOpen, haClose)
haLow   = min(low,  haOpen, haClose)

The security child transforms each bar to Heikin Ashi before the script reads it, so close returns haClose, open returns haOpen, and any indicator (ta.sma(close, 20), ta.atr(14), …) is computed on the Heikin Ashi series. This works in both backtest and live: on a developing realtime bar haOpen stays fixed (from the prior closed HA bar) while haClose/haHigh/haLow move, and the value commits when the bar closes — matching TradingView. The two carried HA values ride the child’s var-snapshot, so a developing bar recomputes from the fixed prior-close baseline. On a higher timeframe the bars are aggregated to the period first, then transformed — matching TradingView, which builds Heikin Ashi from the requested timeframe’s candles. Inside the child, chart.is_heikinashi reads true.

The source feed is the chart’s own .ohlcv when the symbol is the chart symbol (no --security mapping needed), the mapped file for a cross-symbol backtest, or the live provider stream. ticker.heikinashi() evaluates on every bar in Pine — a cond ? security(...) : x still runs the transform even when cond is false, exactly as TradingView does.

Only Heikin Ashi is supported: renko, pointfigure, kagi and linebreak are path-dependent chart types that need intrabar tick data PyneCore does not carry, and they raise a clear error.

gaps_on vs gaps_off

ModeNew period confirmedBetween periods
gaps_offReturn new valueReturn last value (forward-fill)
gaps_onReturn new valueReturn na

Lookahead modes

request.security() accepts a lookahead argument. Three modes are recognized:

ModeBehavior (historical / backtest)Behavior (live mode, same-symbol HTF)
barmerge.lookahead_off (default)Most recently CLOSED security bar (TV-faithful)Most recently CLOSED security bar; each HTF period close is shipped via the chart-side HTFAggregator (the static .ohlcv file cannot grow at runtime). No developing exposure.
barmerge.lookahead_last_closedMost recently CLOSED security bar (functionally equivalent to lookahead_off)Most recently CLOSED security bar — uses the same closed-bar transport as lookahead_off; repaint-free.
barmerge.lookahead_onMost recently CLOSED security bar — historical falls back to lookahead_off to avoid future-leakDeveloping (containing) HTF bar with barstate.isconfirmed=False; OHLCV is aggregated from the chart timeframe by HTFAggregator. On HTF period close the just-closed bar is delivered first (so the security bar_index advances), then the new developing bar. TV-compatible close[1] idiom returns the previously closed bar.

Why lookahead_last_closed — in historical backtests it matches lookahead_off, and in live mode it stays repaint-free (it never shows the developing security bar). Prefer it when you want stable last-closed values without depending on the TV close[1] idiom.

Why historical lookahead_on is degraded to lookahead_off — TV’s classical historical lookahead_on behavior is to expose the containing HTF bar’s close on every chart bar, producing a future-leak that silently inflates backtest results. PyneCore deliberately does not reproduce this leak: in historical mode the closed bar is the only bar the subprocess ever runs. The TV idiom request.security(..., lookahead_on)[1] still returns the correct last-closed value (since close[1] is always the previously closed bar). In live mode the subprocess additionally steps into the developing bar so close[0] exposes the in-progress close as TV does.

Cross-symbol HTF — the chart-side HTFAggregator aggregates the chart symbol’s OHLCV; it cannot produce OHLCV for a different security symbol. Cross-symbol HTF contexts therefore keep no aggregator and read closed bars directly from the security’s own .ohlcv (historical) or live feed.

  • lookahead_off / lookahead_last_closed — closed-bar semantics, identical to same-symbol behaviour. Repaint-free.
  • lookahead_on — the containing developing bar is unknown (cannot be aggregated from the wrong instrument). Chart-side __sec_read__ returns na for every chart bar inside an open HTF period; the subprocess still advances on closed cross-symbol HTF bars, so close[1] on the first chart bar of a fresh HTF period delivers the just-closed cross-symbol HTF close. The TV lookahead_on + close[1] idiom continues to work at the period boundary. Behaviour is identical in historical and live mode — the backtest never silently emits a value live could not produce.
# Default — most recently closed bar
htf_close = lib.request.security(lib.syminfo.tickerid, "60", lib.close)

# PyneSys-native — repaint-free in both historical and live mode
htf_close = lib.request.security(
    lib.syminfo.tickerid, "60", lib.close,
    lookahead=lib.barmerge.lookahead_last_closed,
)

# TV-compatible — live mode exposes the developing bar; the close[1] idiom
# is the canonical TV way to read the most recently closed HTF bar.
htf_close_prev = lib.request.security(
    lib.syminfo.tickerid, "60", lib.close[1],
    lookahead=lib.barmerge.lookahead_on,
)

Limitations

  • Cross-symbol HTF — the live HTF transport aggregates chart OHLCV, so it works only when the security symbol matches the chart symbol. Cross-symbol HTF lookahead_off / lookahead_last_closed deliver closed bars from the security’s own .ohlcv / live feed. Cross-symbol HTF lookahead_on returns na for the current chart bar inside any open HTF period (the developing bar cannot be aggregated cross-instrument); close[1] at the period boundary still delivers the just-closed cross-symbol HTF close, preserving the TV idiom. For continuous developing-bar coverage of a cross-symbol HTF, run that context as a separate same-symbol chart.
  • Standalone modepython script.py data.csv does not support --security yet. Use pyne run or the ScriptRunner API.
  • Chart types + LTF — a chart type combined with request.security_lower_tf() raises at security-context setup (it would need per-intrabar transformation). Ordinary (non-chart-type) LTF requests are unaffected.

Debugging Security Contexts

log.info(), log.warning(), and log.error() calls inside security processes are suppressed by default (matching TradingView behavior). To enable logging for debugging, set the PYNE_SECURITY_LOG environment variable:

PYNE_SECURITY_LOG=security.log pyne run my_script.py my_data --security "1D=my_data_1D"

Each line is prefixed with the security context identifier:

[AAPL 1D] [2025-07-05 14:30:00-0400] bar:    42 INFO    Daily SMA: 150.25
[EURUSD 1H] [2025-07-05 14:00:00+0000] bar:   100 INFO    RSI: 72.5

See Debugging for more details.

Session anchoring of intraday higher timeframes

For intraday higher timeframes (minutes and hours), PyneCore anchors the HTF bar grid to the session open, matching TradingView. On a market that opens at 09:30, a 1H security therefore produces bars at 09:30, 10:30, 11:30… rather than 09:00, 10:00, 11:00. The grid steps in real time from the open, so it stays correct across daylight-saving transitions, and the alignment is derived from the symbol’s own session — a cross-symbol security in a different exchange session anchors to its open, not the chart’s. Markets that open on a whole-tf boundary (24/7 crypto, on-hour forex and futures) are unaffected: the session-anchored grid is identical to a plain clock-floor there.

The session open is read from the symbol’s session_starts metadata. If a security’s session information is missing, intraday bars fall back to the UTC clock-floor for that symbol.

Known Differences from TradingView

On markets with shortened trading sessions (e.g., half-day sessions before holidays), minor differences may occur when the chart symbol and the security symbol follow different session calendars — one closes early while the other trades a full day. This can cause period boundary alignment to differ slightly from TradingView. In practice, this is rare and only affects a handful of bars on specific calendar dates.

Markets with an intraday recess (e.g. a lunch break) keep a single grid anchored to the day’s primary session open; the bar whose window spans the recess simply holds the data of the first trade after it, as on TradingView for the common case.

For technical implementation details (AST transformation, shared memory layout, process lifecycle), see the request.security() Internals page.