Custom Indicator Guide¶
This page answers one practical question: when built-in indicators are not enough, how do you write, register, and maintain your own indicators in AKQuant?
Typical use cases:
- private factors or strategy-specific signals;
- rapid prototypes built on pandas;
- stateful indicators updated bar by bar in the event stream;
- indicators that must survive warm-start resume.
Start With The Right Scope¶
In AKQuant, these are related but different tasks:
| Goal | Recommended path | Typical API |
|---|---|---|
| Add a private signal to a strategy | custom Indicator / custom incremental object |
register on Strategy |
Compute a full series from a DataFrame |
indicator_mode="precompute" |
register_precomputed_indicator(...) |
| Maintain state bar by bar | indicator_mode="incremental" |
register_incremental_indicator(...) |
Add a new name to akquant.talib |
modify the compatibility layer source | not runtime plugin registration |
If your goal is simply "use my own indicator inside a strategy", you usually do not need to extend akquant.talib.
Path 1: Precomputed Indicators¶
Use precompute mode when the indicator is naturally vectorized over the full DataFrame.
Minimal example¶
from akquant import Indicator, Strategy
class PrecomputeMomentumStrategy(Strategy):
def __init__(self):
super().__init__()
self.indicator_mode = "precompute"
self.mom10 = Indicator(
"mom10",
lambda df: df["close"] - df["close"].shift(10),
)
self.register_precomputed_indicator("mom10", self.mom10)
def on_bar(self, bar):
value = self.mom10.get_value(bar.symbol, bar.timestamp)
if value == value and value > 0:
self.buy(bar.symbol, 100)
Good fit when¶
- the indicator is naturally vectorized;
- you want to reuse pandas
rolling,shift, orewm; - development speed matters more than streaming-style updates;
- each symbol has a full history slice available up front.
Notes¶
Indicator(name, fn, **kwargs)expects a function returning apd.Series;get_value(symbol, timestamp)reads from the cached series;- results are cached per symbol.
Path 2: Incremental Indicators¶
Use incremental mode when the indicator should evolve inside the event stream.
Minimal example¶
from collections import deque
import pandas as pd
from akquant import Indicator, Strategy
class MyMomentum(Indicator):
def __init__(self, period: int = 10):
super().__init__("my_momentum", lambda df: df["close"] - df["close"].shift(period))
self.period = period
self.buffer: deque[float] = deque(maxlen=period)
self._current_value = float("nan")
def update(self, value: float) -> float:
if pd.isna(value):
return self._current_value
self.buffer.append(float(value))
if len(self.buffer) < self.period:
self._current_value = float("nan")
else:
self._current_value = self.buffer[-1] - self.buffer[0]
return self._current_value
@property
def value(self) -> float:
return self._current_value
class IncrementalMomentumStrategy(Strategy):
def __init__(self):
super().__init__()
self.indicator_mode = "incremental"
def on_start(self):
self.register_incremental_indicator(
"mom10",
indicator_factory=lambda: MyMomentum(period=10),
source="close",
symbols=["AAPL", "MSFT"],
warmup_bars=10,
)
def on_bar(self, bar):
value = self.mom10.value
if value == value and value > 0:
self.buy(bar.symbol, 100)
Why indicator_factory is recommended¶
In a multi-symbol strategy, incremental indicators usually carry internal state. Reusing one instance across multiple symbols can mix state and produce incorrect results.
Recommended:
self.register_incremental_indicator(
"mom10",
indicator_factory=lambda: MyMomentum(period=10),
source="close",
symbols=["AAPL", "MSFT"],
)
Single-instance form, better for quick single-symbol experiments:
self.mom10 = MyMomentum(period=10)
self.register_incremental_indicator("mom10", self.mom10, source="close")
What source means¶
source tells the framework which field from the market event should be fed into the indicator. Common choices:
source="close"source="open"source="high"source="low"source="volume"
If your indicator needs multiple inputs, align your update(...) signature with the incremental input mode expected by the framework.
Using warmup_bars¶
warmup_bars bootstraps the incremental indicator with bars before start_time.
Use it when:
- you want a valid value on the first active bar;
- your indicator depends on a rolling window;
- you do not want to manually skip the first N bars inside
on_bar.
Runnable example:
Warm Start And Serialization¶
If the strategy uses run_warm_start, your custom indicator must preserve its internal state correctly.
Practical rules:
- simple Python objects are often pickle-compatible already;
- if the indicator stores file handles, sockets, locks, or other non-serializable objects, handle them explicitly;
- implement
__getstate__and__setstate__when needed.
Example:
def __getstate__(self):
state = self.__dict__.copy()
return state
def __setstate__(self, state):
self.__dict__.update(state)
See also: Warm Start Guide.
Boundary With akquant.talib¶
Many users mix up "custom strategy indicators" and "extending akquant.talib". A practical mental model:
akquant.talib: built-in TA-Lib-style compatibility layer;- custom strategy indicators: strategy-local building blocks registered on
Strategy; - new Rust high-performance indicators: source-level extension plus recompilation, not runtime hot-plugging.
If you only need a private signal inside one strategy, prefer a custom strategy indicator instead of extending akquant.talib.
Common Pitfalls¶
- Pitfall 1: every custom indicator must inherit from
Indicator - Not always.
Indicator(name, fn)is enough for many precompute cases. - Pitfall 2: one incremental instance can be shared safely across symbols
- Usually no. Prefer
indicator_factoryfor production multi-symbol strategies. - Pitfall 3:
warmup_bars=20double-consumes the first active bar - It does not. Warmup only uses history before the active start boundary.
- Pitfall 4: custom indicators automatically work with warm start
- Not guaranteed. Verify serialization.
- Pitfall 5: strategy-local indicators and
akquant.talibextensions are the same thing - They solve different problems at different layers.
Recommendation Matrix¶
| Goal | Recommended approach |
|---|---|
| Validate an idea quickly | Indicator(name, fn) + precompute |
| Single-symbol bar-by-bar state | incremental + single instance |
| Multi-symbol production strategy | incremental + indicator_factory |
| Need valid values on the first active bar | incremental + warmup_bars |
| Need resumable state | ensure the indicator is serializable |
| Need maximum performance | consider a Rust implementation later |