
Every financial market operates through a two-sided auction. Buyers post the highest price they are willing to pay and sellers post the lowest price they will accept. The gap between those two prices is the bid-ask spread, and it tells you more about market conditions than most indicators ever will. In February 2025 TradingView added two built-in variables to Pine Script that give you direct access to these prices on tick charts1.
This guide explains what the bid and ask variables are, how to use them on the 1T timeframe, and how to build practical spread analysis tools that monitor liquidity and market microstructure in real time.
Before diving into Pine Script code, it helps to understand what bid and ask prices represent in market microstructure. These two prices define the core mechanics of how every trade happens on an exchange.
Bid: The highest price that an active buyer is currently willing to pay for an instrument. If you want to sell immediately at the market price, you will receive the bid price. The bid represents current demand.
Ask: The lowest price that an active seller is currently willing to accept for an instrument. If you want to buy immediately at the market price, you will pay the ask price. The ask represents current supply.
Spread: The difference between the ask and the bid. A narrow spread indicates high liquidity and tight competition among market makers. A wide spread signals lower liquidity, higher trading costs, or increased uncertainty.
The spread is essentially the cost of immediacy. When you place a market order, you cross the spread. Understanding how the spread behaves throughout the trading day gives you an edge in timing entries and exits.
The February 2025 Pine Script update introduced two new built-in variables that provide direct access to real-time market prices at the tick level1.
The bid variable returns a series float value representing the highest price an active buyer is willing to pay for the instrument at the time of the current tick2. It is a built-in variable that requires no function call or library import.
//@version=6
indicator("Bid Price Display", overlay = true)
plot(bid, "Bid", color.green)
The ask variable returns a series float value representing the lowest price an active seller will accept for the instrument at the time of the current tick2. Like bid, it is a built-in variable available without any additional setup.
//@version=6
indicator("Ask Price Display", overlay = true)
plot(ask, "Ask", color.red)
Both variables update only when a new tick arrives. If the bid or ask values change between ticks without a new trade occurring, those changes will not be reflected until the next tick fires3. This is an important distinction for very fast markets where quote updates may happen more frequently than actual trades.
The bid and ask variables are only available on the 1T tick timeframe2. On every other timeframe, including seconds, minutes, daily, and weekly charts, both variables return na. This is not a bug or a limitation you can work around. Tick-level data is fundamentally different from time-based data, and bid-ask information only exists at the individual tick level.
You can verify whether your chart is on the correct timeframe before attempting to use these variables. The timeframe.isticks built-in variable returns true when the current resolution is a tick resolution2.
//@version=6
indicator("Timeframe Check", overlay = true)
if not timeframe.isticks
runtime.error("This indicator requires the 1T tick timeframe. Bid and ask data is not available on other timeframes.")
plot(bid, "Bid", color.green)
plot(ask, "Ask", color.red)
This guard pattern prevents the indicator from displaying misleading empty plots on non-tick charts. Instead, it raises a clear runtime error telling the user what timeframe is required.
Time-based bars aggregate many ticks into a single candle. During that aggregation, the bid and ask prices change many times. A 1-minute bar might contain hundreds of individual ticks, each with different bid and ask values. There is no single bid or ask price that meaningfully represents an entire time-based bar3. The open, high, low, and close of a candle already capture the trade price movement, but those are transaction prices, not quote prices.
Tick data preserves the individual quote-level granularity that makes bid-ask analysis meaningful. Each tick represents a single moment where the market had specific bid and ask prices, and that precision is what makes spread analysis valuable.
The bid-ask spread is simply the difference between the ask price and the bid price. In Pine Script, this calculation is straightforward.
//@version=6
indicator("Basic Spread", overlay = false)
float spread = ask - bid
plot(spread, "Spread", color.orange, 2)
You can also express the spread as a percentage of the mid-price, which normalizes it across instruments with different price levels. A 0.01 spread on a $10 stock is very different from a 0.01 spread on a $500 stock.
//@version=6
indicator("Spread Percentage", overlay = false)
float midPrice = (bid + ask) / 2.0
float spreadPct = midPrice > 0 ? ((ask - bid) / midPrice) * 100.0 : na
plot(spreadPct, "Spread %", color.purple, 2)
This percentage-based spread calculation is especially useful when comparing liquidity conditions across different instruments or when monitoring how spread behavior changes relative to price movements.
A practical spread monitor indicator combines the raw spread calculation with a moving average to show how the current spread compares to its recent history. Spikes above the average often signal increased volatility or reduced liquidity.
//@version=6
indicator("Spread Monitor", overlay = false)
//@variable The lookback period for the average spread calculation.
int lengthInput = input.int(50, "Average Length", minval = 1)
//@variable The multiplier for the high-spread threshold.
float multInput = input.float(2.0, "Threshold Multiplier", minval = 0.5, step = 0.5)
// Ensure we are on a tick timeframe.
if not timeframe.isticks
runtime.error("Switch to the 1T timeframe to use this indicator.")
//@variable The current bid-ask spread in price units.
float spread = ask - bid
//@variable The simple moving average of the spread over the lookback period.
float avgSpread = ta.sma(spread, lengthInput)
//@variable The threshold above which the spread is considered elevated.
float threshold = avgSpread * multInput
//@variable Color changes based on whether spread exceeds the threshold.
color spreadColor = spread > threshold ? color.red : color.green
// Plot the spread and its average.
plot(spread, "Spread", spreadColor, 2)
plot(avgSpread, "Average Spread", color.gray, 1, plot.style_line)
plot(threshold, "Threshold", color.new(color.red, 70), 1, plot.style_line)
// Highlight the background when spread is elevated.
bgcolor(spread > threshold ? color.new(color.red, 90) : na, title = "Wide Spread Alert")
This indicator plots three lines. The green or red line is the current tick-by-tick spread. The gray line is the rolling average spread. The faded red line is the threshold, set at a configurable multiple of the average. When the spread exceeds the threshold, the plot turns red and the background highlights, alerting you to unusual conditions.
A histogram view can make spread changes easier to read at a glance. By plotting the deviation of the current spread from its average, you get a clear visual of when spreads are tighter or wider than normal.
//@version=6
indicator("Spread Histogram", overlay = false)
int lengthInput = input.int(100, "Lookback Length", minval = 1)
if not timeframe.isticks
runtime.error("This indicator requires the 1T tick timeframe.")
float spread = ask - bid
float avgSpread = ta.sma(spread, lengthInput)
//@variable The deviation of the current spread from the average. Positive means wider than normal.
float deviation = spread - avgSpread
//@variable Green when the spread is tighter than average, red when wider.
color histColor = deviation > 0 ? color.red : color.green
// Plot the deviation as a histogram.
plot(deviation, "Spread Deviation", histColor, 2, plot.style_histogram)
// Plot a zero line for reference.
hline(0, "Zero", color.gray)
Green bars below the zero line mean the spread is tighter than average, which generally indicates good liquidity and lower trading costs. Red bars above zero mean the spread is wider than average, suggesting reduced liquidity or increased volatility. This visual pattern helps you quickly identify the best and worst times to execute trades.
Beyond static measurement, tracking how the spread changes over time reveals patterns in market behavior. Spreads tend to widen at market open, narrow during peak trading hours, and widen again near the close. Monitoring these transitions helps you understand the rhythm of the market you are trading.
//@version=6
indicator("Spread Change Tracker", overlay = false)
int fastLen = input.int(20, "Fast MA Length", minval = 1)
int slowLen = input.int(100, "Slow MA Length", minval = 1)
if not timeframe.isticks
runtime.error("This indicator requires the 1T tick timeframe.")
float spread = ask - bid
float fastMA = ta.sma(spread, fastLen)
float slowMA = ta.sma(spread, slowLen)
//@variable True when the fast moving average of spread crosses above the slow, signaling widening.
bool widening = ta.crossover(fastMA, slowMA)
//@variable True when the fast moving average of spread crosses below the slow, signaling tightening.
bool tightening = ta.crossunder(fastMA, slowMA)
plot(fastMA, "Fast Spread MA", color.orange, 2)
plot(slowMA, "Slow Spread MA", color.blue, 1)
fill(plot(fastMA, display = display.none), plot(slowMA, display = display.none),
fastMA > slowMA ? color.new(color.red, 85) : color.new(color.green, 85),
title = "Spread Zone")
// Mark transitions with shapes.
plotshape(widening, "Widening", shape.triangleup, location.bottom, color.red, size = size.small)
plotshape(tightening, "Tightening", shape.triangledown, location.top, color.green, size = size.small)
This dual moving average approach to spread monitoring works similarly to how traders use price crossovers. When the fast spread MA crosses above the slow, spreads are widening. The opposite crossover signals tightening. The filled zone between the two lines gives you an instant visual read on the current spread regime.
The bid and ask variables are powerful additions to Pine Script, but they come with specific constraints you should understand before building strategies around them.
These constraints mean that bid-ask analysis in Pine Script is best suited for real-time monitoring and intraday analysis rather than long-term backtesting3. Historical tick data is often limited to a few days or weeks depending on the data provider.
The bid and ask variables introduced in the February 2025 Pine Script update1 open up an entire category of market microstructure analysis that was previously impossible in TradingView. By providing direct access to real-time quote prices on the 1T tick timeframe2, these variables let you build spread monitors, liquidity indicators, and execution timing tools that go far beyond traditional price-based analysis.
The key concepts to remember are that bid represents demand and ask represents supply, the spread between them measures liquidity and trading costs, and all of this data is only available on the 1T timeframe. Start with the basic spread calculation and then layer on moving averages and threshold alerts to build a monitoring system that fits your trading style.
Once you have built indicators that identify favorable spread conditions, you can connect your TradingView alerts to automated execution through TradersPost4. By sending webhook signals from your tick-based spread analysis to TradersPost, you can automate entries and exits that account for real-time liquidity conditions, helping you avoid executing trades during periods of unusually wide spreads.
1 Pine Script Release Notes — February 2025
2 Pine Script v6 Language Reference
3 Pine Script User Manual
4 TradersPost — Automated Trading Platform