Live Mode

Live mode extends PyneCore beyond backtesting: after replaying historical data the script seamlessly transitions to real-time streaming from a LiveProviderPlugin. Indicators update on every tick; strategies run in paper-trading mode with tick-level order fill accuracy.

Quick Start

# Stream BTC/USDT on 1-minute bars from Bybit via CCXT, prefetching 500 historical bars
pyne run my_strategy.py ccxt:BYBIT:BTC/USDT:USDT@1 --live -f -500

The --live flag requires a provider string as the data source β€” it does not work with local OHLCV files.

Historical Bar Count

The -f / --from parameter controls how many historical bars the script processes before going live. Default: 500 bars β€” enough for most scripts out of the box.

Indicators need a warm-up period: ta.sma(close, 200) requires 200 bars before producing its first value, and the initial values are still distorted by limited lookback. A good rule of thumb is 2Γ— the largest length parameter in your script:

# Script uses ta.ema(close, 50) and ta.atr(14) β†’ largest length is 50 β†’ -f -100 is enough
pyne run my_strategy.py ccxt:BYBIT:BTC/USDT:USDT@1 --live -f -100

# Script uses ta.sma(close, 200) β†’ use -f -400
pyne run my_strategy.py ccxt:BYBIT:BTC/USDT:USDT@1 --live -f -400

# No -f specified β†’ default 500 bars, sufficient for most scripts
pyne run my_strategy.py ccxt:BYBIT:BTC/USDT:USDT@1 --live

Too few bars β†’ indicators produce NaN or unreliable values, leading to missed or false signals. Too many β†’ longer startup, but no harm beyond that. When in doubt, err on the side of more.

How It Works

A live session has two phases:

PhaseData sourcebarstate.ishistorybarstate.isrealtimeStrategy
HistoricalProvider downloadTrueFalseSuppressed
LiveWebSocket streamingFalseTrueActive

Historical Phase

The provider downloads OHLCV data (controlled by -f / --from). The script runs on each bar exactly like a normal backtest β€” indicators build up their series, ta.sma() warms up, etc. Strategy functions are suppressed: calls to strategy.entry(), strategy.exit(), and friends are silently ignored. This prevents phantom trades on historical bars that the script sees for the first time.

Transition

The ScriptRunner detects the transition automatically when the iterator yields its first BarUpdate object (instead of a plain OHLCV). At this point:

  • barstate.islastconfirmedhistory becomes True on the final historical bar
  • Output writers flush to disk (plot CSV, trade CSV)
  • Strategy suppression is lifted β€” orders are now active

Live Phase

The provider streams BarUpdate objects via WebSocket. Each update carries an OHLCV snapshot and an is_closed flag:

BarUpdate(ohlcv=OHLCV(...), is_closed=False)   # intra-bar tick
BarUpdate(ohlcv=OHLCV(...), is_closed=True)     # bar closed

The script executes on every update β€” both intra-bar ticks and bar closes.

Intra-Bar Updates

On TradingView, a real-time script re-executes on every tick within a bar. PyneCore replicates this behavior in live mode.

barstate Values

Eventisconfirmedisnewislastisrealtime
First tick of barFalseTrueTrueTrue
Later intra-barFalseFalseTrueTrue
Bar closeTrueFalseTrueTrue

var vs varip in Live Mode

The distinction between Persistent (Pine var) and IBPersistent (Pine varip) becomes meaningful during intra-bar re-executions β€” the same mechanism used by calc_on_order_fills:

  • Persistent (var): rolled back to the bar-open snapshot before each intra-bar tick. Every tick starts from the same baseline.
  • IBPersistent (varip): not rolled back β€” accumulates across all ticks within the bar.
var_counter: Persistent[int] = 0
varip_counter: IBPersistent[int] = 0

var_counter += 1      # always == bar_index + 1 (rolled back each tick)
varip_counter += 1    # bar_index + 1 + total intra-bar ticks across all bars

This uses the same VarSnapshot mechanism as the bar magnifier’s COOF loop.

Order Processing

Strategies use magnifier-style order processing in live mode: intra-bar ticks are accumulated as sub_bars. When the bar closes, process_orders_magnified(sub_bars, final_bar) runs β€” checking limit, stop, and trailing stop orders against each tick’s OHLCV in chronological order. This gives tick-level fill accuracy even in paper trading.

If calc_on_order_fills=True, the COOF re-execution loop runs on bar close as well β€” exactly as it does in backtesting with the bar magnifier.

Strategy Suppression

During the historical phase, all 7 strategy functions (entry, exit, close, close_all, cancel, cancel_all, order) are no-ops. This is controlled by the internal lib._strategy_suppressed flag β€” the same pattern as lib._lib_semaphore.

Output

Plot CSV

Written only on closed bars. Intra-bar ticks do not produce plot output. This matches TradingView behavior where plot values are committed only at bar close.

Strategy Stats CSV

In live mode, the strategy statistics file is rewritten after every closed bar β€” not appended. This means opening the file at any time shows the complete, up-to-date statistics aggregated over the entire run (historical + live).

Trade CSV

Trade entries and exits are recorded on the bar where the fill occurs, as in backtesting.

Provider String Format

provider:EXCHANGE:SYMBOL:SETTLE@TIMEFRAME
PartExampleDescription
providerccxtPlugin name (entry point)
EXCHANGEBYBITExchange identifier
SYMBOLBTC/USDTTrading pair
SETTLEUSDTSettlement currency (optional)
TIMEFRAME1TradingView timeframe format

The -f / --from option accepts a negative integer for relative bar count:

# Prefetch last 500 bars before going live
pyne run script.py ccxt:BYBIT:BTC/USDT:USDT@1 --live -f -500

CLI Options

FlagDescription
--live, -lEnable live streaming after historical phase
--shutdown-timeoutMax seconds for graceful shutdown (default: 120)

Press Ctrl+C to stop live streaming. The provider goes through a graceful shutdown sequence: can_shutdown() is polled every second, then disconnect() is called.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    provider.download()    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  run.py     β”‚ ───────────────────────── β”‚  OHLCV file  β”‚
β”‚  (CLI)      β”‚                           β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚             β”‚                                  β”‚ OHLCVReader
β”‚             β”‚    itertools.chain()             β”‚
β”‚             β”‚ ◄─────────────────────────────────
β”‚             β”‚                                  β”‚
β”‚             β”‚    live_ohlcv_generator() β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”
β”‚             β”‚ ◄──── Queue ◄──── async ──│  WebSocket   β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜                           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚ Iterator[OHLCV | BarUpdate]
       β”‚
β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”
β”‚ ScriptRunner β”‚
β”‚              β”‚  isinstance() detects BarUpdate β†’ live transition
β”‚  historical  β”‚  OHLCV bars β†’ normal backtest loop
β”‚  live loop   β”‚  BarUpdate β†’ intra-bar + bar close processing
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The live_ohlcv_generator bridges the async WebSocket world to synchronous iteration via a background thread and queue.Queue. The ScriptRunner is completely data-source agnostic β€” it only cares whether it receives OHLCV or BarUpdate objects.

Limitations

  • Paper trading only β€” no real order execution. Live order routing is provided by dedicated per-exchange broker plugins (pynecore-bybit, pynecore-binance, etc.).
  • Single timeframe β€” request.security() with live providers (multi-timeframe live) is not yet supported.
  • Provider required β€” --live only works with provider strings, not local data files.
  • No replay β€” there is no mechanism to replay missed ticks if the connection drops mid-bar. The provider reconnects and resumes from the next available update.