Custom Functions¶
Custom functions let you define reusable single-expression computations.
Syntax¶
Basic Usage¶
Text Only
fn volatility(x, window=20):
rolling_std(ret(x, 1), window)
signal example:
vol = volatility(prices) // Uses default window=20
vol60 = volatility(prices, 60) // Override window
emit vol
Function Parameters¶
Required Parameters¶
Text Only
fn returns(x, lookback):
ret(x, lookback)
signal example:
r = returns(prices, 20) // Must provide both
emit r
Default Parameters¶
Text Only
fn volatility(x, window=20):
rolling_std(ret(x, 1), window)
signal example:
vol1 = volatility(prices) // window=20
vol2 = volatility(prices, 60) // window=60
emit vol1
Multiple Defaults¶
Text Only
fn momentum_score(x, lookback=20, skip=5):
ret(x, lookback) - ret(x, skip)
signal example:
m1 = momentum_score(prices) // lookback=20, skip=5
m2 = momentum_score(prices, 60) // lookback=60, skip=5
m3 = momentum_score(prices, 60, 10) // lookback=60, skip=10
emit m1
Function Body¶
The body is a single expression (no variables):
Text Only
// CORRECT: Single expression
fn volatility(x, window=20):
rolling_std(ret(x, 1), window)
// ERROR: Multiple statements not allowed in fn
fn bad(x, window=20):
r = ret(x, 1) // Not allowed
rolling_std(r, window)
For multiple statements, use macros instead.
Calling Functions¶
Positional Arguments¶
Text Only
fn example(a, b, c=10):
a + b * c
signal test:
x = example(1, 2) // a=1, b=2, c=10
y = example(1, 2, 3) // a=1, b=2, c=3
emit x
Named Arguments¶
Not yet supported. Use positional arguments.
Common Function Patterns¶
Volatility¶
Text Only
fn volatility(x, window=20):
rolling_std(ret(x, 1), window)
fn annualized_vol(x, window=252):
rolling_std(ret(x, 1), window) * sqrt(252)
Sharpe Ratio¶
Text Only
fn sharpe(returns, window=252):
rolling_mean(returns, window) / rolling_std(returns, window)
fn annualized_sharpe(returns, window=252):
sharpe(returns, window) * sqrt(252)
Momentum¶
Text Only
fn momentum(x, lookback=20):
ret(x, lookback)
fn momentum_skip(x, lookback=252, skip=21):
ret(x, lookback) - ret(x, skip)
Moving Averages¶
Text Only
fn sma(x, window=20):
rolling_mean(x, window)
fn ema_crossover(x, fast=10, slow=50):
ema(x, fast) - ema(x, slow)
Normalization¶
Technical¶
Text Only
fn rsi_signal(x, period=14):
rsi(x, period) - 50
fn macd_signal(x, fast=12, slow=26, signal=9):
macd(x, fast, slow, signal)
fn bollinger_position(x, window=20, num_std=2):
ma = rolling_mean(x, window)
std = rolling_std(x, window)
(x - ma) / (num_std * std)
Composing Functions¶
Functions can call other functions:
Text Only
fn volatility(x, window=20):
rolling_std(ret(x, 1), window)
fn vol_adjusted_returns(x, ret_window=20, vol_window=60):
ret(x, ret_window) / volatility(x, vol_window)
fn normalized_vol_adj(x, ret_window=20, vol_window=60):
zscore(vol_adjusted_returns(x, ret_window, vol_window))
signal example:
emit normalized_vol_adj(prices)
Using Functions in Signals¶
Text Only
fn vol(x, window=60):
rolling_std(ret(x, 1), window)
fn momentum(x, lookback=20):
ret(x, lookback)
signal vol_adjusted_momentum:
m = momentum(prices, 60)
v = vol(prices, 252)
emit zscore(m / v)
Using Functions with Parameters¶
Text Only
params:
lookback = 20
vol_window = 60
fn momentum(x, lb):
ret(x, lb)
fn vol(x, window):
rolling_std(ret(x, 1), window)
signal example:
m = momentum(prices, lookback) // Uses param
v = vol(prices, vol_window) // Uses param
emit zscore(m / v)
Functions vs Macros¶
| Feature | Functions (fn) |
Macros (macro) |
|---|---|---|
| Body | Single expression | Multiple statements |
| Variables | Not allowed | let statements |
| Output | Return value | emit statement |
| Type params | No | Yes (expr, number) |
| Best for | Simple computations | Complex patterns |
Use functions for:
- Single-expression calculations
- Commonly used operators
- Simple transformations
Use macros for:
- Multi-step computations
- Reusable signal patterns
- Complex logic with intermediates
Example: Building a Signal Library¶
Text Only
// ============================================
// Signal Library: Common Functions
// ============================================
// --- Returns ---
fn ret_simple(x, n):
ret(x, n)
fn ret_log(x, n):
log(x / lag(x, n))
// --- Volatility ---
fn vol(x, window=20):
rolling_std(ret(x, 1), window)
fn vol_annualized(x, window=252):
vol(x, window) * sqrt(252)
// --- Momentum ---
fn mom(x, lookback=20):
ret(x, lookback)
fn mom_skip(x, lookback=252, skip=21):
ret(x, lookback) - ret(x, skip)
// --- Technical ---
fn rsi_centered(x, period=14):
rsi(x, period) - 50
fn bollinger_zscore(x, window=20):
(x - rolling_mean(x, window)) / rolling_std(x, window)
// --- Normalization ---
fn normalize(x):
zscore(x)
fn clean(x, p=0.01):
winsor(zscore(x), p)
// ============================================
// Strategy
// ============================================
data:
prices: load csv from "data/prices.csv"
signal my_signal:
m = mom_skip(prices, 252, 21)
v = vol(prices, 60)
raw = m / v
emit clean(raw)
portfolio main:
weights = rank(my_signal).long_short(top=0.2, bottom=0.2)
backtest from 2024-01-01 to 2024-12-31
Best Practices¶
1. Use Descriptive Names¶
Text Only
// Good
fn annualized_volatility(x, window=252):
rolling_std(ret(x, 1), window) * sqrt(252)
// Avoid
fn av(x, w=252):
rolling_std(ret(x, 1), w) * sqrt(252)
2. Provide Sensible Defaults¶
Text Only
// Common window sizes as defaults
fn volatility(x, window=20): // 20-day is standard
rolling_std(ret(x, 1), window)
fn momentum(x, lookback=60): // ~3 months
ret(x, lookback)
3. Keep Functions Focused¶
Text Only
// Good: Single purpose
fn volatility(x, window):
rolling_std(ret(x, 1), window)
// Avoid: Too much in one function
fn everything(x, ret_window, vol_window, winsor_pct):
winsor(zscore(ret(x, ret_window) / rolling_std(ret(x, 1), vol_window)), winsor_pct)
4. Document Complex Functions¶
Text Only
// Sharpe ratio: mean return / volatility, annualized
fn sharpe_annualized(returns, window=252):
rolling_mean(returns, window) / rolling_std(returns, window) * sqrt(252)
Next Steps¶
- Macros - Multi-statement reusable patterns
- Operators - Available operators
- Signal Section - Using functions in signals