Time-Series Guide
Polarway provides native time-series support optimized for financial and IoT data: OHLCV resampling, rolling windows, as-of joins, and common indicators.
TimeSeriesFrame
The TimeSeriesFrame wraps a Polars DataFrame with frequency awareness:
import polarway as pw
# Load tick data and create TimeSeriesFrame
ticks = pw.read_parquet("btc_ticks.parquet")
ts = ticks.as_timeseries("timestamp")
# Frequency-aware operations
print(ts.frequency) # "tick"
print(ts.timespan) # "2026-01-01 → 2026-02-01"
OHLCV Resampling
Convert tick data to OHLCV bars at any frequency:
# Tick → 1-minute OHLCV bars
ohlcv_1m = ts.resample_ohlcv("1m", price_col="price", volume_col="volume")
# Tick → 5-minute bars
ohlcv_5m = ts.resample_ohlcv("5m", price_col="price", volume_col="volume")
# Multi-timeframe: 1m → 1h → 1d
ohlcv_1h = ohlcv_1m.resample_ohlcv("1h")
ohlcv_1d = ohlcv_1h.resample_ohlcv("1d")
Supported frequencies: tick, 1s, 5s, 10s, 30s, 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w, 1mo
Rolling Windows
Apply rolling computations with configurable window sizes:
import polars as pl
# SMA (Simple Moving Average) over 20 periods
sma_20 = ohlcv_5m.with_columns(
pl.col("close").rolling_mean(window_size=20).alias("sma_20")
)
# Bollinger Bands (20-period SMA ± 2 std dev)
bollinger = ohlcv_5m.with_columns([
pl.col("close").rolling_mean(window_size=20).alias("bb_mid"),
(pl.col("close").rolling_mean(window_size=20)
+ 2 * pl.col("close").rolling_std(window_size=20)).alias("bb_upper"),
(pl.col("close").rolling_mean(window_size=20)
- 2 * pl.col("close").rolling_std(window_size=20)).alias("bb_lower"),
])
# EMA (Exponential Moving Average)
ema_12 = ohlcv_5m.with_columns(
pl.col("close").ewm_mean(span=12).alias("ema_12")
)
Financial Indicators
VWAP (Volume Weighted Average Price)
vwap = (
pl.scan_parquet("ticks/*.parquet")
.group_by("symbol")
.agg(
(pl.col("price") * pl.col("volume")).sum() / pl.col("volume").sum()
)
.collect(engine="streaming") # Constant memory
)
Returns and Volatility
# Percentage returns
returns = ohlcv_5m.with_columns(
pl.col("close").pct_change().alias("returns")
)
# Realized volatility (rolling 20-period std of returns)
realized_vol = returns.with_columns(
pl.col("returns").rolling_std(window_size=20).alias("realized_vol")
)
RSI (Relative Strength Index)
def compute_rsi(df: pl.DataFrame, period: int = 14) -> pl.DataFrame:
delta = df.with_columns(pl.col("close").diff().alias("delta"))
gains = delta.with_columns(
pl.when(pl.col("delta") > 0).then(pl.col("delta")).otherwise(0).alias("gain")
)
losses = delta.with_columns(
pl.when(pl.col("delta") < 0).then(-pl.col("delta")).otherwise(0).alias("loss")
)
avg_gain = gains.with_columns(pl.col("gain").rolling_mean(window_size=period).alias("avg_gain"))
avg_loss = losses.with_columns(pl.col("loss").rolling_mean(window_size=period).alias("avg_loss"))
return avg_gain.join(avg_loss, on="timestamp").with_columns(
(100 - 100 / (1 + pl.col("avg_gain") / pl.col("avg_loss"))).alias("rsi")
)
As-Of Joins
Align data from different frequencies using point-in-time joins:
# Join 1-minute OHLCV with daily reference data
aligned = ohlcv_1m.join_asof(
daily_stats,
on="timestamp",
strategy="backward" # Use most recent daily value
)
# Join trades with order book snapshots
trades_with_book = trades.join_asof(
orderbook_snapshots,
on="timestamp",
tolerance="100ms" # Max 100ms staleness
)
Streaming Time-Series
Process larger-than-RAM time-series with constant memory:
# Stream 100GB of tick data — constant memory
(
pl.scan_parquet("ticks/**/*.parquet")
.sort("timestamp")
.group_by_dynamic("timestamp", every="5m")
.agg([
pl.col("price").first().alias("open"),
pl.col("price").max().alias("high"),
pl.col("price").min().alias("low"),
pl.col("price").last().alias("close"),
pl.col("volume").sum().alias("volume"),
])
.sink_parquet("ohlcv_5m/output.parquet")
)
See the Streaming Guide for more streaming patterns.
Next Steps
- Storage Architecture — Hybrid storage for time-series data
- Streaming Guide — Real-time processing patterns
- Examples — Complete working examples
- Performance Comparison — Benchmarks