DocsSignal Analysis, Models & ValidationThe Signal tab and the time-series chart
Signal Analysis, Models & ValidationHow-to

The Signal tab and the time-series chart

Write a Signal, evaluate it over a symbol and date range, and read its value series on the time-series chart. Save it to reuse it, extend it forward as new data lands, and import it into other signals. This is the tab where a Signal goes from text to a line you can look at.

Updated Jul 20266 min read

Opening a Signal tab and picking a symbol and dates

A Signal tab has two halves: the code editor panel on the left, where you write the program, and the chart panel on the right, where the value series renders after you run it.

  1. Open a Signal tab.
  2. Pick a symbol from the project-level symbol selector. For bar.* and option.* accessor data, see Data coverage for which symbols are supported today.
  3. Set the start and end date for the evaluation window.
  4. Write or edit the program in the editor.

The symbol and dates define the evaluation window. The end date is inclusive of the full final day: the query keeps every observation with timestamp < midnight(end_date + 1 day), so a program run to 2024-12-31 includes that whole session, not only its midnight boundary.

You are done with setup when the editor holds a program that ends on a Signal and the symbol and date range are set.

The shipped example program

A fresh tab loads a canonical starter so a first read shows the shape of a Signal:

// Intraday IV term structure spread
// Compares short-dated vs long-dated implied volatility
iv_7 = option.iv(dte=7)   // short-dated ATM IV
iv_30 = option.iv(dte=30) // medium-dated ATM IV
// Positive = normal term structure; negative = inverted (near-term stress)
iv_30 - iv_7

The last expression is the program's result, and for a standalone signal it must be a Signal. Here the result is iv_30 - iv_7, and both operands trace back to option.iv, so it resolves to a {timestamp, value} Signal. For the language rules behind the return contract, see the Signal language.

Evaluating versus saving a Signal

Two actions run the program, and they differ in one thing: whether the result is kept.

Evaluate runs the program ad-hoc. The value series streams back to the chart panel and nothing is persisted. Use it while you iterate on a program you have not committed to.

Save names the signal, derives a slug, and stores the computed values so the signal can be imported and extended later. Give the signal a name; the slug is derived from it. The slug is lowercased and sanitized to [a-z0-9-]: any character outside that set becomes -, runs of dashes collapse to one, and leading or trailing dashes are trimmed. IV Rank 30d! becomes iv-rank-30d.

Saving under a name that already exists overwrites that signal in place. There is one row per name: the stored values, dates, and program are replaced. There is no version history and no draft-versus-published status, so a re-save is a mutation, not a new revision.

You are done when the chart panel shows the value series and, for a save, the signal appears in your library under its name.

Reading the time-series chart

The Signal is {timestamp, value}. Timestamps are tz-aware in America/New_York, and values are Float64 and nullable. A comparison or logical signal plots as 1.0 and 0.0, never as booleans, because those operators return Float64 1.0/0.0 Signals.

The spine and what a gap means

Values sit on the accessor spine: 09:31 through the session close. On a normal NYSE day that is 16:00 and 390 observations; on an early-close day it is 13:00 and 210 observations. Holidays and early closes are handled from the NYSE calendar, so non-trading minutes and whole non-trading days are absent from the spine rather than filled.

A null on the chart is missing data, not zero. It renders as a break in the line, not a filled point. The option surface returns null where it is sparse or the value could not be computed, so a program built on option.* can have gaps even on a trading minute. This spine runs the full 09:31 to close and is not trimmed to 09:35 or to close minus five minutes; those are backtest-engine session bounds, not data-accessor ones.

Observation-count windows on the chart

Warning

Rolling windows count observations, not calendar time. A rolling_mean(bar.close(), 30) line covers 30 observations at the effective sample rate. On a raw 1-minute series that is 30 minutes, not 30 days. The number in the window is a count of points on the current spine, and the chart will happily draw the wrong thing if you read it as days.

To make a window count trading days, sample the input to daily first, then apply the window:

// 20-day realized vol: sample to daily so the window counts trading days
daily_close = bar.close(sampling=sampling(period="day", time="close"))
returns = log_change(daily_close, 1)
rolling_std(returns, 20) * sqrt(252)

Here sampling(period="day", time="close") collapses the 1-minute series to one observation per session at the close, so rolling_std(returns, 20) is 20 trading days.

EDA lives on this tab

Once a signal is saved and a benchmark context is set, four EDA charts (Time Series, Distribution, Scatter, and Decile) appear on this tab. There is no separate button to press. See Reading the EDA charts for how to read each one, and Benchmark context and the inheritance badge for the outcome axis they need.

Extending a saved Signal to a later date

Extend moves a saved signal's end date forward and appends the new points to the chart. It resumes from the stored state and computes only the incremental range, so extending a year-long signal by a week does not recompute the year.

Extend falls back to a full rebuild when it cannot resume: a missing state archive (no_state) or a graph-version drift (graph_version_drift) both force a rebuild that recomputes the whole range and redraws the chart from the start rather than appending. An earlier end date is rejected. An extend to a date the signal already covers is a no-op and adds no points.

Importing one Signal into another

signal("slug") pulls a saved signal onto the importing program's spine as an input. The importing program does not need to know how the imported one was built; it references the slug and gets a Signal back.

// Reuse a saved IV-rank signal as one input
rank = signal("iv-rank-30d")
rank > 0.50

The resolver decides how to satisfy the import from the stored signal's symbol and date extent. If the stored range covers the request, it uses the values as they are. If the symbols match and the request runs past the stored end, it extends. If the symbol differs or the stored range cannot be sliced to fit, it recomputes. Extends and recomputes re-persist the stored signal, so the import stays current. There is no version pin; resolution is by data-extent coverage, and the import effectively resolves to the latest saved values.

Import graphs are bounded. Depth is capped at 10 and the number of distinct dependencies at 50; exceeding either raises a depth or dependency error. A signal that imports itself, directly or through a chain, is rejected as a cycle.

What a fresh Signal evaluation assumes

Every line on this chart carries the defaults that produced it. Read them before you trust a number.

  • Rolling windows are observation-count on the current spine, not calendar windows.
  • The option surface is 2D-interpolated across DTE and delta or moneyness, and returns null where the surface is sparse or undefined. It is never extrapolated.
  • The end date is inclusive of the full final day.

For how mixed-frequency series align and why a daily value is not readable intraday, see the execution model.

This tab is code, and that's the point

The program in the tab is the Signal language. There is no visual signal builder, because a signal is an expression, and the expression is the thing you want to be able to read, diff, and reuse. See operators and functions for the catalog and writing and saving a signal for the full authoring flow.

If you would rather not write one, select an existing signal instead. A signal-selection modal carries your own signal library, the official Backtest.ai library, and an option to browse community signals and clone one into your library. Referencing a saved signal with signal("slug") and cloning by re-saving under a new name both work.

Was this page helpful?