Operators and functions
This page groups the Signal language's callable names by category so you know what exists and how to reach for it. The exhaustive per-function signatures and examples live in the DSL operator & function catalog.
How to read this page
The Signal language exposes a fixed, complete set of callables: roughly 70 functions, two constants, and the Table accessors documented with the data accessors. This page groups them by what they do; the catalog is the exhaustive lookup.
Four naming rules prevent most compile failures. Learn them before anything else.
- Full names only. Use
rolling_mean, nevermean;rolling_std, neverstd;rolling_rank, neverrank. The short forms are not registered and fail to compile. - Rolling and lag windows count observations, not calendar time. The window argument is
periods, and it counts rows on the input's current spine.rolling_mean(x, 20)on a 1-minute series averages 20 minutes. Sample to daily first for "20 days" (see the execution model). - Comments are
//. There is no block-comment form. - Accessors are called with parentheses. Write
bar.close(), neverbar.close.
Operators and precedence
The operators split into four groups.
- Arithmetic:
+-*/** - Comparison:
>>=<<===!= - Logical:
&&(aliasand),||(aliasor) - Unary:
-,!(aliasnot)
Every comparison and every logical result is a Float64 Signal of 1.0 (true) or 0.0 (false), never a boolean. This keeps the value column uniform and lets you compose comparisons with arithmetic. Dividing a finite numerator by zero yields NaN, consistent with IEEE 754.
Precedence runs from highest to lowest. Use parentheses to override it.
**-, !, not*, /+, ->, >=, <, <===, !=&&, and||, orSo (iv - rv) / rv is a percent spread, while iv - rv / rv subtracts rv / rv from iv. And (iv > 0.3) && (delta > 0.5) needs the parentheses, because relational binds tighter than && only within each side.
Element-wise transforms
Each transform maps a Signal to a Signal row by row, with signature (Signal) -> Signal unless noted. null and NaN propagate unchanged; any ±inf result becomes NaN. See the catalog for per-function arguments.
abs: absolute value.sign:1.0if positive,-1.0if negative,0.0at zero.sqrt: principal square root;NaNfor negative inputs.root(x, power):x ** (1 / power);poweris a positive integer.log(x, base="e"|2|10): logarithm in the requested base;NaNforx <= 0.lognp(x, n=1, base="e"|2|10):log(x + n), shifting the input off the domain boundary at zero (useful for series like volume that include zeroes).exp:e ** x; overflow to+infbecomesNaN.floor,ceil: round toward negative and positive infinity.round(x, decimals=0): banker's rounding (half-to-even).clip(x, lower=None, upper=None): clamp to[lower, upper]; either bound may be omitted; raises iflower > upper.- Trig and hyperbolic:
sincostanarcsinarccosarctandeg2radrad2degsinhcoshtanharcsinharccosharctanh. Trig inputs are in radians.
Missing-data and conditional
These functions read and act on the two kinds of missing value the language tracks, null versus NaN.
fill_missing(x, value, fill="null"|"nan"|"both"): replace missing observations ofxwithvalue. Defaultfill="nan". A row wherevalueis itself missing is skipped.coalesce(*args, exhausted="null"|"nan"): first non-null, non-NaN value across the arguments, left to right per row. Variadic, at least one argument. Defaultexhausted="null"chooses the fallback when every argument is missing.where(cond, arg1, arg2): row-wise selectarg1wherecondis truthy, elsearg2. Any non-zero numeric value is truthy; anullcondyieldsnull, aNaNcondyieldsNaN.is_missing(x, kind="null"|"nan"|"both"):1.0wherexis missing,0.0otherwise. The output is never null or NaN itself.replace_where(x, cond, value):xwith truthy-condrows replaced byvalue. AnullorNaNcondkeeps the originalx.
Lag, diff, and change
Each of these requires a timeseries and an integer periods >= 1. The first periods rows are null because there is not enough history, and all four are no-lookahead by construction.
lag(x, periods):xshifted forwardperiodsobservations:result[i] = x[i - periods].diff(x, periods):x - lag(x, periods).pct_change(x, periods):x / lag(x, periods) - 1; a lagged value of exactly zero yieldsNaN.log_change(x, periods, base="e"|2|10):log(x / lag(x, periods), base); a non-positive ratio yieldsNaN.
// One-period log return of the daily close
daily_close = bar.close(sampling=sampling(period="day", time="close"))
log_change(daily_close, 1)Rolling aggregations
Every rolling function takes a timeseries and an observation-count periods. They share four keyword arguments:
periods(required): window size in observations.min_periods=None: minimum valid observations to emit a value.Nonerequires the fullperiodswindow, so the output isnulluntil the row index reachesperiods - 1.ignore="both": which missing kinds to exclude from the window:"null","nan","both"(default), or"neither". Under"null", a NaN in the window propagates to a NaN output; under"nan", a null propagates to a null output;"neither"propagates both.ddof(onrolling_std,rolling_var,rolling_zscoreonly):0for population (default),1for sample.
The functions:
rolling_sumrolling_meanrolling_medianrolling_min, rolling_maxrolling_std, rolling_varvar equals std ** 2.rolling_skewnull below 3 valid observations.rolling_kurtnull below 4 valid observations.rolling_countFloat64.rolling_zscore(x - rolling_mean) / rolling_std; NaN when the rolling std is 0.rolling_demeanx - rolling_mean(x, periods).rolling_rank[0, 1]; a window of size 1 emits 0.5.rolling_winsorize[lower_q, upper_q] quantile range.rolling_quantile(x, periods, q)q-th quantile: 0 is the min, 0.5 the median, 1 the max.rolling_ewmahalf_life, alpha, or center_of_mass; with none, the span is periods (alpha = 2 / (periods + 1)).This is where the observation-count window bites. A "trailing year" is 252 daily observations, not 252 of whatever the accessor returns by default.
// WRONG: option.iv(30) is a 1-minute series, so 252 counts 252 minutes, not a year
rolling_rank(option.iv(30), 252)// RIGHT: sample to daily first, then 252 observations is about one year
iv_daily = option.iv(30, sampling=sampling(period="day", time="close"))
rolling_rank(iv_daily, 252)The window counts observations on the input's spine: 252 on a daily series is roughly a year; on a 1-minute series it is 252 minutes.
Resampling functions and builders
Two builders describe a resampling, and two apply functions run it against an already-computed Signal.
sampling(*, period=None, multiplier=1, offset=None, time=None, times=None, days=None, weeks=None, months=None): build a point-in-time sampling config.bucketing(*, period, agg="last", multiplier=1, offset=None, partial=true): build a window-aggregation config.sample(x, sampling): apply a sampling to Signalx. Sampling selects rows and forward-fills the last known value onto the target spine (as-of).bucket(x, bucketing): apply a bucketing to Signalx. Bucketing aggregates the observed rows in each window into one row, labeled at the window's right endpoint.
sampling and bucketing are mutually exclusive on a single accessor call. The execution model covers the point-in-time versus window-aggregation semantics, and how each interacts with the union spine.
// Daily-close last value of a derived intraday ratio
ratio = bar.close() / bar.open()
bucket(ratio, bucketing(period="day", agg="last"))Constants
Two bare-name constants resolve to a scalar at compile time and can be used anywhere a scalar is expected.
math.pi:3.141592653589793math.e:2.718281828459045
Defaults and edge cases
These behaviours shape any result the functions above produce, so read a number against them.
- Dividing a finite numerator by zero yields
NaN(IEEE 754). - Any
±infresult is converted toNaN. This covers overflow inexp,sinh,cosh, and trig singularities intan. - With
min_periods=None, a rolling output isnulluntil the row index reachesperiods - 1. - Windows are observation-count on the input's current spine, not calendar time.
Two calling rules apply across every signature. Positional arguments must precede named arguments. A bare * in a signature marks the start of keyword-only arguments, so everything after it must be passed by name.