sifting/io
Developer Tutorials
6 min readSiftingIO Team

TA-Lib installation error? Get RSI and MACD readings from an API instead

TA-Lib won't install? Skip the C compile and numpy version matrix: pull RSI, MACD, and moving average readings from two REST endpoints with plain requests.

TA-Lib installation error? Get RSI and MACD readings from an API instead

A TA-Lib installation error is usually the first thing standing between a Python script and its first RSI value. The message is famous enough to be a rite of passage:

talib/_ta_lib.c:747:10: fatal error: ta-lib/ta_defs.h: No such file or directory
compilation terminated.

The TA-Lib package on PyPI is a Cython wrapper around a C library that pip cannot build for you. If the C library isn't already installed system-wide, the wheel build dies with the error above. This post walks through why the install breaks, what maintaining your own indicator code actually costs, and how to get the same readings, RSI, MACD, and moving averages included, from a REST API with nothing but requests.

Why pip install TA-Lib fails#

There are two distinct failure modes, and developers tend to hit both.

The first is the missing C library. Before pip install TA-Lib can succeed, the underlying C library has to be compiled and installed separately: download the source tarball, run ./configure & make & sudo make install, or use a system package manager where a build exists. On Windows the standard advice is to hunt down an unofficial prebuilt wheel. None of this is captured in requirements.txt, so the install works on the machine where someone fought through it once and fails everywhere else, including CI and fresh Docker builds.

The second failure mode arrives later, after everything worked. The Python wrapper is a compiled extension tied to a specific numpy ABI, so upgrading numpy underneath a wheel that was built against the previous ABI makes imports start failing with binary incompatibility errors such as numpy.dtype size changed. The fix is to re-pin numpy or rebuild TA-Lib against the version you now have, and either choice can conflict with the pins that pandas or another dependency wants.

pandas-ta exists as a pure-Python workaround and covers most common indicators. But its candlestick pattern functions still import TA-Lib under the hood, so the moment a project needs those, the compile problem is back.

The do-it-yourself cost#

Say the install succeeds. What you own at that point is bigger than one package.

You need clean OHLCV bars for every symbol you care about, which means a data pipeline before the first indicator runs. You need enough history for warmup: an EMA(200) on daily bars needs 200 trading days of data before its first honest value, and every indicator has its own window. You need the computation to be identical across your laptop, CI, and production, which in practice means baking a C toolchain and the TA-Lib library into a Docker image. And if the project spans asset classes, stocks plus forex plus crypto, the bar-fetching code multiplies before the indicator code even starts.

For one indicator on one symbol, this is a fine afternoon project. For a panel of indicators across a watchlist, it is a small system you now maintain.

Getting indicator signals from an API instead#

In August 2026 SiftingIO shipped two REST endpoints that return technical-analysis readings computed server-side, covering US stocks, forex, crypto, and commodities. They are documented at /docs/signals. The live endpoint returns the current reading for a symbol:

curl -H "X-API-Key: $SIFTING_KEY" \
  "https://api.sifting.io/v1/last/signals/crypto/BTCUSD?interval=1h"

The venue segment is one of stocks, forex, crypto, or commodities, and interval ranges from 1m to 1mo (default 1h). The response carries a summary plus every per-indicator vote behind it:

{
  "data": {
    "summary": {
      "signal": "buy",
      "score": 0.36,
      "counts": {"buy": 11, "neutral": 6, "sell": 3}
    },
    "oscillators": {
      "signal": "neutral",
      "score": 0.08,
      "indicators": [
        {"name": "RSI(14)", "value": 58.2, "vote": "neutral"},
        {"name": "MACD(12,26,9)", "value": 42.10, "signal_line": 30.44, "vote": "buy"}
      ]
    },
    "moving_averages": {
      "signal": "strong_buy",
      "score": 0.83,
      "indicators": [
        {"name": "SMA(10)", "value": 61240.5, "vote": "buy"},
        {"name": "EMA(50)", "value": 59870.2, "vote": "buy"}
      ]
    },
    "price": {"close": 61980.4, "bar_status": "forming"}
  }
}

The panel splits into oscillators (RSI, MACD, Stochastic, CCI, Williams %R, Momentum) and moving averages (SMA and EMA at 10 through 200 periods). Each indicator reports its computed value and a vote of buy, neutral, or sell; the summary signal is an enum from strong_sell to strong_buy with a score between -1 and +1. To be clear about what that means: a vote of buy on RSI(14) describes where the oscillator sits relative to its standard thresholds on that bar. It is a computed property of recent prices, the same number your own TA-Lib call would produce, and never a recommendation or forecast for the symbol.

Consuming it in Python takes no compiler and no numpy pin:

import os
import requests

r = requests.get(
    "https://api.sifting.io/v1/last/signals/crypto/BTCUSD",
    headers={"X-API-Key": os.environ["SIFTING_KEY"]},
    params={"interval": "1h"},
)
r.raise_for_status()
data = r.json()["data"]

print(data["summary"]["signal"], data["summary"]["score"])
for ind in data["oscillators"]["indicators"]:
    print(ind["name"], ind["vote"])

The second endpoint returns the same signal as a historical series, one point per bar, which is what you want for charts and research:

import pandas as pd

r = requests.get(
    "https://api.sifting.io/v1/hist/crypto/BTCUSD/signals",
    headers={"X-API-Key": os.environ["SIFTING_KEY"]},
    params={"interval": "1d", "limit": 90},
)
df = pd.DataFrame(r.json()["data"])
df["t"] = pd.to_datetime(df["t"], unit="ms", utc=True)
crosses = df[df["events"].map(len) > 0]

Each point carries the bar-open time t in epoch milliseconds (matching the bars endpoints), the bar's close, the summary and score, and an events array flagging discrete occurrences: golden_cross, death_cross, macd_cross_up, macd_cross_down. That makes cross events a filter expression instead of a hand-rolled comparison of two moving average columns. A useful side effect: because the API also serves the raw OHLCV bars, you can keep computing indicators locally where you want custom parameters and use the signals series to cross-check your implementation.

Common pitfalls#

A 422 with error code insufficient_history is not an outage. Long-window indicators need bars before they can vote, and a newly listed symbol, or a long interval like 1mo, may simply not have 200 bars yet for EMA(200). Retry with a shorter interval or accept that the panel is thinner for that symbol.

Don't hardcode the vote denominator. Indicators still in warmup are omitted from the response rather than reported as neutral, so counts.buy + counts.neutral + counts.sell varies by symbol and interval. Compute percentages from the counts you actually received.

The live endpoint computes on the currently forming bar, and the response says so via price.bar_status. Its reading can change until the bar closes, while the history endpoint reports closed bars. If you diff the two at the same interval and see a mismatch on the newest bar, that is the difference between a forming and a final bar, not a bug.

The free tier includes 10,000 REST calls a month with no credit card, which is enough to replace a local TA-Lib setup for a small watchlist. Start building free

Keep reading

Related posts