Using Indicators¶
The mintalib.indicators module provides composable indicator objects that bind a calculation with its parameters (pandas only — for polars, use mintalib.expressions).
Indicators are named in upper case (e.g. SMA, EMA, MACD). An indicator instance is callable and can be passed directly to prices.assign() or invoked as SMA(50)(prices). The | operator chains indicators: EMA(20) | ROC(1) means ROC applied after EMA.
import numpy as np
import pandas as pd
from mintalib.samples import sample_prices
from mintalib.indicators import EMA, SMA, ROC, RSI, EVAL, LOG, BBANDS
Basic Usage¶
An indicator instance is a callable. Applied to a DataFrame, series-based indicators use the close column by default — the item parameter selects another column. A pandas Series or numpy array can be passed directly as well (results always come back as pandas objects):
prices = sample_prices()
SMA(50)(prices)
date
1980-12-12 NaN
1980-12-15 NaN
1980-12-16 NaN
1980-12-17 NaN
1980-12-18 NaN
...
2026-04-20 260.501999
2026-04-21 260.268199
2026-04-22 260.239200
2026-04-23 260.234200
2026-04-24 260.145400
Length: 11433, dtype: float64
SMA(50, item="open")(prices)
date
1980-12-12 NaN
1980-12-15 NaN
1980-12-16 NaN
1980-12-17 NaN
1980-12-18 NaN
...
2026-04-20 260.634220
2026-04-21 260.527001
2026-04-22 260.325201
2026-04-23 260.328401
2026-04-24 260.289601
Length: 11433, dtype: float64
RSI(14)(prices["close"])
date
1980-12-12 NaN
1980-12-15 NaN
1980-12-16 NaN
1980-12-17 NaN
1980-12-18 NaN
...
2026-04-20 66.533046
2026-04-21 56.723876
2026-04-22 62.742587
2026-04-23 62.948689
2026-04-24 59.706160
Length: 11433, dtype: float64
Chaining¶
The | operator chains indicators left to right: LOG() | EMA(20) | ROC(1) applies LOG first, then EMA, then ROC. .then() is the fluent equivalent, and .alias() names the result:
(LOG() | EMA(20) | ROC(1)).alias("trend")(prices)
date
1980-12-12 NaN
1980-12-15 NaN
1980-12-16 NaN
1980-12-17 NaN
1980-12-18 NaN
...
2026-04-20 0.000893
2026-04-21 0.000370
2026-04-22 0.000779
2026-04-23 0.000721
2026-04-24 0.000503
Name: trend, Length: 11433, dtype: float64
The Assign Idiom¶
Because indicators are callables, they can be passed directly to prices.assign, which invokes each with the DataFrame. EVAL evaluates a pandas expression string against the columns — and since assign processes keyword arguments sequentially, it can reference columns created earlier in the same call:
result = prices.assign(
sma50 = SMA(50),
sma200 = SMA(200),
rsi = RSI(14),
slope = LOG() | EMA(20) | ROC(1),
uptrend = EVAL("sma50 > sma200")
).iloc[:, -5:]
result
| sma50 | sma200 | rsi | slope | uptrend | |
|---|---|---|---|---|---|
| date | |||||
| 1980-12-12 | NaN | NaN | NaN | NaN | 0.0 |
| 1980-12-15 | NaN | NaN | NaN | NaN | 0.0 |
| 1980-12-16 | NaN | NaN | NaN | NaN | 0.0 |
| 1980-12-17 | NaN | NaN | NaN | NaN | 0.0 |
| 1980-12-18 | NaN | NaN | NaN | NaN | 0.0 |
| ... | ... | ... | ... | ... | ... |
| 2026-04-20 | 260.501999 | 252.134106 | 66.533046 | 0.000893 | 1.0 |
| 2026-04-21 | 260.268199 | 252.400445 | 56.723876 | 0.000370 | 1.0 |
| 2026-04-22 | 260.239200 | 252.719730 | 62.742587 | 0.000779 | 1.0 |
| 2026-04-23 | 260.234200 | 253.040016 | 62.948689 | 0.000721 | 1.0 |
| 2026-04-24 | 260.145400 | 253.342819 | 59.706160 | 0.000503 | 1.0 |
11433 rows × 5 columns
Multi-Output Indicators¶
Multi-output indicators return a DataFrame, so they cannot be assigned to a single column — join the result instead:
prices.join(BBANDS(20)(prices))
| open | high | low | close | volume | upperband | middleband | lowerband | |
|---|---|---|---|---|---|---|---|---|
| date | ||||||||
| 1980-12-12 | 0.098298 | 0.098725 | 0.098298 | 0.098298 | 469033600 | NaN | NaN | NaN |
| 1980-12-15 | 0.093597 | 0.093597 | 0.093169 | 0.093169 | 175884800 | NaN | NaN | NaN |
| 1980-12-16 | 0.086758 | 0.086758 | 0.086331 | 0.086331 | 105728000 | NaN | NaN | NaN |
| 1980-12-17 | 0.088468 | 0.088895 | 0.088468 | 0.088468 | 86441600 | NaN | NaN | NaN |
| 1980-12-18 | 0.091032 | 0.091460 | 0.091032 | 0.091032 | 73449600 | NaN | NaN | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2026-04-20 | 270.329987 | 274.279999 | 270.290009 | 273.049988 | 36590200 | 270.976486 | 257.638998 | 244.301511 |
| 2026-04-21 | 271.500000 | 272.799988 | 265.399994 | 266.170013 | 50209800 | 271.890660 | 258.372999 | 244.855337 |
| 2026-04-22 | 267.820007 | 273.739990 | 266.869995 | 273.170013 | 43249200 | 274.037695 | 259.449500 | 244.861304 |
| 2026-04-23 | 275.049988 | 275.769989 | 271.649994 | 273.429993 | 33399600 | 275.925261 | 260.489999 | 245.054738 |
| 2026-04-24 | 272.760010 | 273.059998 | 269.649994 | 271.059998 | 38124500 | 277.074557 | 261.398499 | 245.722442 |
11433 rows × 8 columns
Pandas Expressions¶
With pandas >= 3.0, as_expr() converts an indicator into a pandas Expression. For multi-output indicators, as_expr(item) picks a single output — which makes them usable inside assign after all:
prices.assign(
upper = BBANDS(20).as_expr("upperband"),
lower = BBANDS(20).as_expr("lowerband"),
).iloc[:, -2:]
| upper | lower | |
|---|---|---|
| date | ||
| 1980-12-12 | NaN | NaN |
| 1980-12-15 | NaN | NaN |
| 1980-12-16 | NaN | NaN |
| 1980-12-17 | NaN | NaN |
| 1980-12-18 | NaN | NaN |
| ... | ... | ... |
| 2026-04-20 | 270.976486 | 244.301511 |
| 2026-04-21 | 271.890660 | 244.855337 |
| 2026-04-22 | 274.037695 | 244.861304 |
| 2026-04-23 | 275.925261 | 245.054738 |
| 2026-04-24 | 277.074557 | 245.722442 |
11433 rows × 2 columns