Skip to main content

fugazi (Python)

Python bindings for fugazi, a library of incremental, composable technical-analysis primitives.

  • Incremental — every indicator and signal carries its own state and is advanced one sample at a time with update(), in ~O(1) and with no full-history recomputation. The same object serves live streaming and batch backtesting.
  • Composable — indicators own their input source, so you build complex indicators and signals by nesting constructors. There is no pipe or glue step: an "EMA of an SMA of the close" is literally ta.ema(ta.sma(ta.close(), 10), 20), and a trade condition is a single object you can feed bars.

Install

pip install fugazi

Then import fugazi. Prebuilt wheels are published for Linux, macOS (Intel + Apple Silicon) and Windows.

To build from a checkout instead (for development):

pip install maturin
maturin develop --release   # editable install into the active virtualenv

Quick start

You build indicators by nesting constructors. Every indicator is rooted at a leaf source — usually a candle field (close(), high(), volume(), ...):

import fugazi as ta

ema = ta.ema(ta.close(), 20)                  # EMA-20 of the close
node = ta.ema(ta.sma(ta.close(), 10), 20)     # EMA-20 of an SMA-10 — just keep nesting

The root decides what the indicator consumes. A candle-rooted indicator takes Candles (any of OHLCV); to work on a bare stream of numbers instead, root it at identity() — the leaf that passes raw values straight through:

prices = ta.rsi(ta.identity(), 14)            # RSI of a plain float series

Then drive it one of two ways: streaming (a bar at a time) or batch (a whole series at once). They share the same indicators; pick by how your data arrives.

What you feed update()/feed() follows from the root: a candle-rooted indicator consumes candles, an identity()-rooted one consumes plain numbers.

Streaming API — one sample at a time

Feed one sample to update(); it returns a float, or None until warmed up. This is the live/incremental path. Every node also has value() (or is_true() for a boolean Signal), is_ready(), and reset().

node = ta.ema(ta.sma(ta.close(), 10), 20)        # candle-rooted

for o, h, l, c, v in bars:
    value = node.update(ta.Candle(o, h, l, c, v))   # feed a Candle -> float | None
    print(value)

prices = ta.rsi(ta.identity(), 14)               # identity-rooted
for px in [100.0, 101.5, 100.8]:
    prices.update(px)                            # feed a float

Batch API — a whole series at once

feed(data) computes every bar in one call. For a candle-rooted indicator, data is a dataframe with OHLCV columns — pandas and polars both work (also a dict of columns) — and only the columns an indicator needs have to be present:

import pandas as pd      # or: import polars as pl

# df is your OHLCV frame (open/high/low/close/volume columns)
df["ema20"] = ta.ema(ta.close(), 20).feed(df)   # assigns straight back
ta.atr(14).feed(df)                             # uses high/low/close
ta.vwap().feed(df)                              # uses high/low/close/volume

Column names are matched case-insensitively (Close/CLOSE/close), and close is required. An identity()-rooted indicator instead takes a plain 1-D series — a list, NumPy array, or pandas/polars Series:

ta.ema(ta.identity(), 20).feed([100.0, 101.5, 100.8, 102.3, 101.9])
ta.ema(ta.identity(), 20).feed(df["close"])

(The root is the contract: a candle indicator won't silently treat a bare array as the close, and a value indicator won't accept a frame — pick the root that matches your data.)

The output mirrors the input library, one value per bar, with warm-up bars as NaN (so the result lines up with your rows and assigns straight back):

Input Indicator Multi-line (macd, bollinger, …) Signal
pandas Series (index preserved) DataFrame (one column per line) bool Series
polars Series DataFrame bool Series
list / dict / NumPy ndarray dict of ndarrays bool ndarray
ta.ema(ta.close(), 20).feed(df)            # pandas Series, df.index
ta.macd(ta.close()).feed(df)               # pandas DataFrame: macd/signal/histogram
ta.macd(ta.identity()).feed(prices_list)   # {"macd": ndarray, "signal": ndarray, ...}

(If NumPy isn't installed, list/dict input falls back to plain Python lists.)

feed is itself incremental — it just loops update over the batch through the node's own state and never auto-resets. So calling it on successive chunks continues the same stream: the warm-up is paid once, and the concatenated outputs equal a single feed over the whole series. This is what lets you process data as it arrives without recomputing history:

node = ta.sma(ta.identity(), 3)
x1 = node.feed(series1)         # warms up, emits for series1
x2 = node.feed(series2)         # continues from where series1 left off
# np.concatenate([x1, x2]) == ta.sma(ta.identity(), 3).feed(series1 + series2)

node.reset()                   # call reset() to start a fresh, independent pass

A source can be reused after you pass it into a constructor:

src = ta.close()
fast = ta.ema(src, 10)
slow = ta.ema(src, 20)   # `src` is still usable here

Indicators

Constructor Output
open() high() low() close() volume() typical() median() the candle field
identity() the raw value stream (root for a bare numeric series)
value(x) a constant
sma ema rma wma hma rsi stddev stochastic cci (source, period) a value
stoch_rsi(source, rsi_period=14, stoch_period=14) a value
atr mfi williams_r (period) a value
obv() vwap() ad() true_range() a value
sar(step=0.02, max=0.2) a value
macd(source, fast=12, slow=26, signal=9) dict {macd, signal, histogram}
bollinger(source, period=20, k=2.0) dict {upper, middle, lower}
keltner(source, ema_period=20, atr_period=10, multiplier=2.0) dict {upper, middle, lower}
donchian(high, low, period) dict {upper, middle, lower}
adx(period) dict {plus_di, minus_di, adx}
dmi(period) dict {plus_di, minus_di}
aroon(period) dict {up, down, oscillator}
resample(every, inner) inner's output every every bars (aggregated HTF candle fed to inner), None between
latch(source) source's last Some output, held across None ticks (works on indicators and signals)
stable(signal) True once signal has been fed at least its stable_period() samples

Multi-line indicators return a dict of their named lines (or None while warming up).

Cross-timeframe composition

resample + latch compose a higher-timeframe pipeline over a base candle stream: resample(N, inner) aggregates every N base candles into one HTF candle and runs inner (any candle-rooted Real source — close(), ema(close(), 20), …) over it, emitting inner's output on the completing tick and None in between. The resample's clock stays base-timeframe: it's fed one base candle per update() and reports at that same cadence — the emitted output marks whether the inner produced a value on a completed bucket. Wrap the whole resample in latch() so per-base-tick reads see the finished value between boundaries.

# EMA-20 of the closes of every 4-bar candle, latched for per-base-tick reads.
htf_ema = ta.latch(ta.resample(4, ta.ema(ta.close(), 20)))

The only correct ordering is resample(N, ema(...)) — with the recursive smoother as the resample's inner — then latch on the outside; latching before the recursive smoother would feed it a held (repeated) value on every base tick, distorting the recurrence.

stable(signal) composes with the same signal in an and_ to gate a strategy's entry on its own sources being past their unstable tail:

entry = ta.close().crosses_above(ta.ema(ta.close(), 20))
gated = entry.and_(ta.stable(entry))   # fires only once the EMA has settled

Operators

Combine value indicators into other indicators:

ta.close().add(other)        # also: sub, mul, div  — or the + - * / operators
ta.close().lag(1)            # also: diff, ratio, roc
ta.close().rolling_max(20)   # also: rolling_min

...or into signals (booleans):

fast.gt(slow)                        # also: lt, ge, le, eq, ne  (optional epsilon=...)
ta.rsi(ta.close(), 14).above(70.0)   # also: below(level)
fast.crosses_above(slow)             # also: crosses_below

Signals compose with each other and update to a bool:

sig = a.and_(b)     # also: or_, xor_, not_(), changed()  — or  a & b | ~c
sig.update(candle)  # -> bool

Example

"Fast EMA crosses above slow EMA while RSI is not already overbought" — one signal, usable either way:

import fugazi as ta

def golden():
    return (
        ta.ema(ta.close(), 12)
          .crosses_above(ta.ema(ta.close(), 26))
          .and_(ta.rsi(ta.close(), 14).below(70.0))
    )

# streaming: react bar by bar
signal = golden()
for bar in stream:
    if signal.update(bar):
        print("entry signal")

# batch: a boolean Series/array over the whole frame
entries = golden().feed(df)

Trading: the wallet

The strategy layer is exposed as a wallet you trade into. There is no strategy class to subclass — a "strategy" in Python is just your own code that, each bar, reads signals and calls wallet methods. PaperWallet is the built-in, in-memory book (funds + positions + a trade blotter); live execution belongs in your own code, not here.

import fugazi as ta

wallet = ta.PaperWallet(10_000.0)          # seed with cash

wallet.update("AAPL", 185.0)               # feed the price each tick (before trading)

# set: absolute target (opposite side reverses) · set_position: absolute units · close: flat
wallet.set("AAPL", "buy", 10)                       # target 10 units (a number = units)
wallet.set("AAPL", "buy", ta.Size.value_frac(0.25)) # target 25% of equity
wallet.set("AAPL", "buy", ta.Size.position_frac(0.5))  # trim to 50% of the position
wallet.set_position("AAPL", 4)                      # drive straight to 4 units
wallet.close("AAPL")                                # flatten

wallet.funds                 # cash balance
wallet.position("AAPL")      # signed position (negative = short)
wallet.price("AAPL")         # last fed price (or None)
wallet.positions()           # {symbol: units}
wallet.equity()              # funds + positions marked at the fed prices
wallet.orders()              # the blotter: list of Order(symbol, side, units)

The wallet is fed each symbol's price with update(symbol, price) and is otherwise market-agnostic. Sizes are an absolute number of units, or ta.Size.funds_frac(f) (cash) / ta.Size.value_frac(f) (equity; 1.0 is all-in) / ta.Size.position_frac(f); sides are "buy"/"sell". A movement that can't be carried out — no/zero price fed, or a buy beyond available funds — raises ValueError. A full strategy loop — price the wallet, advance every signal each bar, then act:

enter = ta.sma(ta.close(), 3).crosses_above(ta.sma(ta.close(), 10))
exit_ = ta.sma(ta.close(), 3).crosses_below(ta.sma(ta.close(), 10))
wallet = ta.PaperWallet(10_000.0)

for o, h, l, c, v in bars:
    candle = ta.Candle(o, h, l, c, v)
    wallet.update("AAPL", c)                          # price the wallet
    went_long, went_flat = enter.update(candle), exit_.update(candle)
    if went_long:
        wallet.set("AAPL", "buy", ta.Size.value_frac(1.0))   # all-in long
    elif went_flat:
        wallet.close("AAPL")

Fetching data

Two remote candle providers ship built in — Binance (crypto spot klines) and Yahoo (stocks, ETFs, indices, FX). Each is a client class with one method, candles(...), returning a polars/pandas DataFrame (or a dict of lists with output="numpy"):

import fugazi as ta

binance = ta.Binance()                     # public endpoint, defaults
df = binance.candles(symbol="BTCUSDT", freq="1d",
                     since="2020-01-01", until="today")

yahoo = ta.Yahoo()
df = yahoo.candles(symbol="AAPL", freq="1d", since="2020-01-01")

freq is a bar-cadence token ("1m"/"5m"/"1h"/"4h"/"1d"/"1w"/"1M"); since/until accept ISO ("YYYY-MM-DD"), EU ("D-M-YYYY"), or relative ("today", "yesterday", "Nd ago", "Nw ago") dates, until is exclusive and defaults to now. The returned frame has time (ISO 8601 UTC), open, high, low, close, volume columns.

fugazi.fetch(provider=..., symbol=..., ...) is the provider-generic form of the same call — handy when the provider name is itself a variable:

df = ta.fetch(provider="yfinance", symbol="AAPL", freq="1d", since="2020-01-01")

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fugazi-0.14.0.tar.gz (413.9 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

fugazi-0.14.0-cp39-abi3-win_amd64.whl (2.4 MB view details)

Uploaded CPython 3.9+Windows x86-64

fugazi-0.14.0-cp39-abi3-manylinux_2_28_aarch64.whl (3.0 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.28+ ARM64

fugazi-0.14.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.1 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

fugazi-0.14.0-cp39-abi3-macosx_11_0_arm64.whl (2.8 MB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

fugazi-0.14.0-cp39-abi3-macosx_10_12_x86_64.whl (3.0 MB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

File details

Details for the file fugazi-0.14.0.tar.gz.

File metadata

  • Download URL: fugazi-0.14.0.tar.gz
  • Upload date:
  • Size: 413.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fugazi-0.14.0.tar.gz
Algorithm Hash digest
SHA256 97d0d55cf807c0b696d41ada9982d1b43c7d2a9e7dba4957038a52c3e7e93936
MD5 75e9ff2202310f4ad1d0f3c62ac339e2
BLAKE2b-256 f39cb478f8a7971354d618a3714211b549ff8c5935463323c767b33a67b4016a

See more details on using hashes here.

Provenance

The following attestation bundles were made for fugazi-0.14.0.tar.gz:

Publisher: release.yml on acpuchades/fugazi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fugazi-0.14.0-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: fugazi-0.14.0-cp39-abi3-win_amd64.whl
  • Upload date:
  • Size: 2.4 MB
  • Tags: CPython 3.9+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fugazi-0.14.0-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 c57e545ff32282f4f5aa8e19b585f202d6a529cb752d3f5e6f60036511344efe
MD5 491efdf52fed4a7a7e9f5c046080a723
BLAKE2b-256 f8ae61b63d7f4a2aae2d10f58108ce30af21520b53d2f12dbf616ffe0306bcf3

See more details on using hashes here.

Provenance

The following attestation bundles were made for fugazi-0.14.0-cp39-abi3-win_amd64.whl:

Publisher: release.yml on acpuchades/fugazi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fugazi-0.14.0-cp39-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for fugazi-0.14.0-cp39-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 f12afb457114c4b7fa21b017f679a730ad6a15872d9b7478309dfeb2944e52db
MD5 58f16e09a7323fd4436537f5220cf057
BLAKE2b-256 51a94e00a0f4ea745f695764e473d949ccfd56dd6315e59e9318ffaa2c56bc6e

See more details on using hashes here.

Provenance

The following attestation bundles were made for fugazi-0.14.0-cp39-abi3-manylinux_2_28_aarch64.whl:

Publisher: release.yml on acpuchades/fugazi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fugazi-0.14.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for fugazi-0.14.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 79bd815599a75cf963347e4759f703e88ae6e1538d5325a240ddfe59adf0880c
MD5 403bdb620789fb18f4066cb85fd1b8d8
BLAKE2b-256 37af7205534a98d20fe9abd333ea910d3c6b882eb69c072e7e4c2f8b2e2a5793

See more details on using hashes here.

Provenance

The following attestation bundles were made for fugazi-0.14.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on acpuchades/fugazi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fugazi-0.14.0-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for fugazi-0.14.0-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 6af2003dfd91ed583d3b37dd3ea903ca12328c22c853339e9e8e3a2978ccd03a
MD5 c993814e0242e309295535693071de85
BLAKE2b-256 fada8f3c4e8f6c51598c034ead579f3b92b6598d0992894773c123f3f03cd8dc

See more details on using hashes here.

Provenance

The following attestation bundles were made for fugazi-0.14.0-cp39-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on acpuchades/fugazi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fugazi-0.14.0-cp39-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for fugazi-0.14.0-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 34a2c90d5db06e407cdec1ad3dbc811003cc0427667610b543a2b0bfa6e54b56
MD5 c94c634d76accb9866dfef6fe0c679b3
BLAKE2b-256 fb6b0205663400acafb16bf207519ea8b4ede5df6f8e1d566e3128120915b1a0

See more details on using hashes here.

Provenance

The following attestation bundles were made for fugazi-0.14.0-cp39-abi3-macosx_10_12_x86_64.whl:

Publisher: release.yml on acpuchades/fugazi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.83.0

6 files

0.82.0

6 files

0.81.1

6 files

0.81.0

6 files

0.80.0

6 files

0.79.0

6 files

0.78.0

6 files

0.77.0

6 files

0.76.0

6 files

0.75.0

6 files

0.74.0

6 files

0.73.0

6 files

0.72.0

6 files

0.71.0

6 files

0.70.0

6 files

0.69.0

6 files

0.68.0

6 files

0.67.0

6 files

0.66.1

6 files

0.66.0

6 files

0.64.0

6 files

0.63.2

6 files

0.63.1

6 files

0.63.0

6 files

0.62.0

6 files

0.61.0

6 files

0.60.0

6 files

0.59.0

6 files

0.58.0

6 files

0.57.2

6 files

0.57.1

6 files

0.57.0

6 files

0.56.0

6 files

0.55.0

6 files

0.54.1

6 files

0.54.0

6 files

0.53.0

6 files

0.52.0

6 files

0.51.0

6 files

0.50.0

6 files

0.49.1

6 files

0.49.0

6 files

0.48.0

6 files

0.47.0

6 files

0.46.0

6 files

0.45.0

6 files

0.44.0

6 files

0.43.0

6 files

0.42.0

6 files

0.41.1

6 files

0.41.0

6 files

0.39.0

6 files

0.38.0

6 files

0.37.0

6 files

0.36.0

6 files

0.35.1

6 files

0.35.0

6 files

0.34.0

6 files

0.32.0

6 files

0.31.1

6 files

0.31.0

6 files

0.30.0

6 files

0.29.0

6 files

0.28.0

6 files

0.27.0

6 files

0.26.2

6 files

0.26.1

6 files

0.26.0

6 files

0.25.0

6 files

0.24.0

6 files

0.23.0

6 files

0.22.1

6 files

0.22.0

6 files

0.21.0

6 files

0.20.0

6 files

0.19.0

6 files

0.18.1

6 files

0.18.0

6 files

0.17.0

6 files

0.16.0

6 files

0.15.0

6 files

This release

0.14.0 This release

6 files

0.11.0

5 files

0.10.4

6 files

0.10.3

6 files

0.8.0

6 files

0.7.0

6 files

0.5.0

6 files

0.4.0

6 files

0.3.1

6 files

0.3.0

6 files

0.2.0

6 files

0.1.1

6 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page