Skip to main content

Python bindings for the fugazi incremental technical-analysis library

Project description

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 maturin
maturin develop --release   # editable install into the active virtualenv

Then import fugazi.

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}

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

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: quantity}
wallet.equity()              # funds + positions marked at the fed prices
wallet.orders()              # the blotter: list of Order(symbol, side, quantity)

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")

Project details


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.1.1.tar.gz (145.8 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.1.1-cp39-abi3-win_amd64.whl (258.5 kB view details)

Uploaded CPython 3.9+Windows x86-64

fugazi-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (422.1 kB view details)

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

fugazi-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (422.5 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

fugazi-0.1.1-cp39-abi3-macosx_11_0_arm64.whl (376.4 kB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

fugazi-0.1.1-cp39-abi3-macosx_10_12_x86_64.whl (376.1 kB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

File details

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

File metadata

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

File hashes

Hashes for fugazi-0.1.1.tar.gz
Algorithm Hash digest
SHA256 fa695bd38e68fbcf68a695a6f2657c7ea02720d3da6799cf733fe1a2c957f882
MD5 ee081a394212b54daa289a48f5a83c2a
BLAKE2b-256 83b1c682efcffad0d782b5c8b952bea17f49997df8e259dcaeb559a5786cb200

See more details on using hashes here.

Provenance

The following attestation bundles were made for fugazi-0.1.1.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.1.1-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: fugazi-0.1.1-cp39-abi3-win_amd64.whl
  • Upload date:
  • Size: 258.5 kB
  • 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.1.1-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 a6de17a2c5ce39908e7a18c4b378897af992d8dc4bb555011c3f868e7cb3edec
MD5 415372ba45908d810eae1c2b4964620e
BLAKE2b-256 0982dc2773cdc3d2efe33f2379f245f491876d123ec257a876cc5578ea8fe442

See more details on using hashes here.

Provenance

The following attestation bundles were made for fugazi-0.1.1-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.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for fugazi-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 e70839678169a6ec40dafec8eff55c3a8890e30253c0331f1b8fdd507aa9b92e
MD5 87a70e3033c9508bc7f8ec5fdf651318
BLAKE2b-256 b2dedbc156b9a15a811e18a760845ae878ed14b0577e152f8a0d51d246afd806

See more details on using hashes here.

Provenance

The following attestation bundles were made for fugazi-0.1.1-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.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for fugazi-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 eca45516e196f13a0b7ba05d2ffe9bdab7eda301e5a1bacb16279a6e32915e11
MD5 32289f0a1120f32ec06b00aa8d9064d1
BLAKE2b-256 048ce13665dceaad11317d8b741142b3ff7cf96561a8272457586bf5a0254ff6

See more details on using hashes here.

Provenance

The following attestation bundles were made for fugazi-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_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.1.1-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

  • Download URL: fugazi-0.1.1-cp39-abi3-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 376.4 kB
  • Tags: CPython 3.9+, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fugazi-0.1.1-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 d9b0b6b7d19c79add3bdc9599e7fe3e765a19880d4829106cfe1e564c69e5d7d
MD5 7d509574e68203ca8483ae6cd0c57c57
BLAKE2b-256 5e11196b6715b637c89e5fdd3e44327ffdced5ed1bf7d7ffde8c575bf12d230f

See more details on using hashes here.

Provenance

The following attestation bundles were made for fugazi-0.1.1-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.1.1-cp39-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for fugazi-0.1.1-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 2f228e86a0a247caa504567f16d403af9f2383697caf05c828e54d3a8cd059e6
MD5 8d3404aaa6b3eba7dcab611a92e4ad1c
BLAKE2b-256 661fd1a6d4ec762333b1faa9476d11196298233aeb9f77541fc13a32fbb4864e

See more details on using hashes here.

Provenance

The following attestation bundles were made for fugazi-0.1.1-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.

Supported by

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