Signals·Technical Analysis Basics·Beginner

TA with Talib

Compute industry-standard technical indicators using TA-Lib, the battle-tested C library with Python bindings that provides reliable, numerically stable, and highly efficient indicator calculations used in professional trading systems worldwide.

technical-analysistrading-signals

Technical Indicators Framework — TA-Lib

This notebook defines a standardized protocol for computing technical indicators using the TA-Lib library on OHLCV data. It covers the same indicator categories as Notebook 18 using TA-Lib's compiled C backend.


1. Dependency Installation

[ ]
# TA-Lib requires the C library installed at the OS level first:
# Ubuntu/Debian:  sudo apt-get install ta-lib
# macOS:          brew install ta-lib
# Then install the Python wrapper:
!pip install TA-Lib
Collecting TA-Lib
  Downloading ta_lib-0.6.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (23 kB)
Collecting build (from TA-Lib)
  Downloading build-1.5.0-py3-none-any.whl.metadata (5.7 kB)
Requirement already satisfied: numpy in /usr/local/lib/python3.12/dist-packages (from TA-Lib) (2.0.2)
Requirement already satisfied: packaging>=24.0 in /usr/local/lib/python3.12/dist-packages (from build->TA-Lib) (26.1)
Collecting pyproject_hooks (from build->TA-Lib)
  Downloading pyproject_hooks-1.2.0-py3-none-any.whl.metadata (1.3 kB)
Downloading ta_lib-0.6.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (4.1 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.1/4.1 MB 29.5 MB/s eta 0:00:00
[?25hDownloading build-1.5.0-py3-none-any.whl (26 kB)
Downloading pyproject_hooks-1.2.0-py3-none-any.whl (10 kB)
Installing collected packages: pyproject_hooks, build, TA-Lib
Successfully installed TA-Lib-0.6.8 build-1.5.0 pyproject_hooks-1.2.0

2. Library Imports

[ ]
import warnings
warnings.filterwarnings("ignore")

import pandas as pd
import numpy as np
import talib

import plotly.graph_objects as go
from plotly.subplots import make_subplots

3. What Is TA-Lib?

TA-Lib (Technical Analysis Library) is a widely used open-source library that implements over 200 technical indicators in compiled C code. The Python wrapper exposes these functions as simple array-in, array-out calls. Because the core is compiled C rather than Python, TA-Lib is significantly faster than pandas-based implementations — relevant for large datasets or live signal computation. The trade-off is that it requires the C library to be installed at the operating system level before the Python wrapper can be used.


4. Dummy Dataset

[ ]
def generate_data(periods: int) -> pd.DataFrame:
    """Generates a larger synthetic OHLCV dataset with more realistic price fluctuations."""
    start_date = pd.to_datetime("2024-01-01 00:00:00+00:00")
    datetime_index = pd.date_range(start_date, periods=periods, freq="1min", tz="UTC")

    price_data = []
    last_close = 42000 # Starting price
    volatility_scale = 0.005 # Controls the general magnitude of price changes
    wick_deviation_scale = 0.002 # Controls how much wicks extend beyond body

    for i in range(periods):
        # Open price drifts slightly from the previous close
        open_price = last_close + np.random.normal(0, last_close * volatility_scale * 0.1)

        # Simulate a price change to determine the closing price
        price_change = np.random.normal(0, last_close * volatility_scale)
        close_price = open_price + price_change

        # Determine the high and low of the candle body
        body_high = max(open_price, close_price)
        body_low = min(open_price, close_price)

        # Simulate wicks extending beyond the body
        # High wick should be above the body_high
        high_wick_extension = np.abs(np.random.normal(0, last_close * wick_deviation_scale))
        high_price = body_high + high_wick_extension

        # Low wick should be below the body_low
        low_wick_extension = np.abs(np.random.normal(0, last_close * wick_deviation_scale))
        low_price = body_low - low_wick_extension

        # Ensure OHLC integrity: High must be the absolute highest, Low the absolute lowest
        high_price = max(high_price, open_price, close_price)
        low_price = min(low_price, open_price, close_price)

        # Ensure High is never less than Low
        if high_price < low_price:
            high_price, low_price = low_price, high_price # Swap if somehow invalid

        # Ensure all values are positive integers
        open_price = max(1, int(open_price))
        high_price = max(1, int(high_price))
        low_price = max(1, int(low_price))
        close_price = max(1, int(close_price))

        price_data.append({
            "open": open_price,
            "high": high_price,
            "low": low_price,
            "close": close_price
        })
        last_close = close_price # Update last_close for the next iteration

    df_large = pd.DataFrame(price_data, index=datetime_index)
    df_large.index.name = "datetime"

    # Simulate volume with some fluctuation
    df_large["volume"] = np.random.uniform(100.0, 500.0, periods)

    return df_large

periods = 500 # Generate 500 data points
df_large = generate_data(periods)
df_large["datetime"] = df_large.index.to_series()
df_large = df_large.reset_index(drop=True)

open_  = df_large["open"].to_numpy(dtype=float)
high   = df_large["high"].to_numpy(dtype=float)
low    = df_large["low"].to_numpy(dtype=float)
close  = df_large["close"].to_numpy(dtype=float)
volume = df_large["volume"].to_numpy(dtype=float)

Code Logic

  • TA-Lib functions accept numpy arrays, not pandas Series. Each OHLCV column is extracted as a float64 numpy array before being passed to TA-Lib functions.

5. Indicator Computation Function

[ ]
def compute_indicators_talib(df: pd.DataFrame) -> pd.DataFrame:
    df = df.copy()

    o = df["open"].to_numpy(dtype=float)
    h = df["high"].to_numpy(dtype=float)
    l = df["low"].to_numpy(dtype=float)
    c = df["close"].to_numpy(dtype=float)
    v = df["volume"].to_numpy(dtype=float)

    df["sma_10"]          = talib.SMA(c, timeperiod=10)
    df["ema_10"]          = talib.EMA(c, timeperiod=10)
    df["rsi_14"]          = talib.RSI(c, timeperiod=14)

    macd, signal, hist    = talib.MACD(c, fastperiod=12, slowperiod=26, signalperiod=9)
    df["macd"]            = macd
    df["macd_signal"]     = signal
    df["macd_hist"]       = hist

    upper, mid, lower     = talib.BBANDS(c, timeperiod=10)
    df["bb_upper"]        = upper
    df["bb_middle"]       = mid
    df["bb_lower"]        = lower

    df["atr_10"]          = talib.ATR(h, l, c, timeperiod=10)
    df["obv"]             = talib.OBV(c, v)

    slowk, slowd          = talib.STOCH(h, l, c)
    df["stoch_k"]         = slowk
    df["stoch_d"]         = slowd

    return df

df_indicators_large = compute_indicators_talib(df_large)

print("--- Indicators Output ---")
display(df_indicators_large.tail())
df_indicators_large.info()
--- Indicators Output ---
open high low close volume datetime sma_10 ema_10 rsi_14 macd macd_signal macd_hist bb_upper bb_middle bb_lower atr_10 obv stoch_k stoch_d
495 39615 40075 39560 39919 236.959469 2024-01-01 08:15:00+00:00 39182.2 39307.461538 69.650163 190.837604 113.086714 77.750890 39851.380275 39182.2 38513.019725 298.355099 5781.818667 89.939434 85.032844
496 39921 39983 39815 39868 289.310518 2024-01-01 08:16:00+00:00 39253.3 39409.377622 68.062363 225.236573 135.516686 89.719887 40037.810064 39253.3 38468.789936 285.319589 5492.508149 88.049788 89.628123
497 39889 39903 39750 39804 339.996727 2024-01-01 08:17:00+00:00 39355.6 39481.127145 66.028146 244.515121 157.316373 87.198748 40133.851990 39355.6 38577.348010 272.087630 5152.511422 79.977152 85.988791
498 39816 39910 39814 39894 164.902418 2024-01-01 08:18:00+00:00 39456.0 39556.194937 67.499209 264.012386 178.655575 85.356810 40227.097919 39456.0 38684.902081 255.478867 5317.413840 76.437253 81.488064
499 39876 40342 39683 40184 159.507031 2024-01-01 08:19:00+00:00 39579.0 39670.341312 71.744906 299.413236 202.807107 96.606128 40382.285752 39579.0 38775.714248 295.830980 5476.920872 75.765654 77.393353
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 500 entries, 0 to 499
Data columns (total 19 columns):
 #   Column       Non-Null Count  Dtype              
---  ------       --------------  -----              
 0   open         500 non-null    int64              
 1   high         500 non-null    int64              
 2   low          500 non-null    int64              
 3   close        500 non-null    int64              
 4   volume       500 non-null    float64            
 5   datetime     500 non-null    datetime64[ns, UTC]
 6   sma_10       491 non-null    float64            
 7   ema_10       491 non-null    float64            
 8   rsi_14       486 non-null    float64            
 9   macd         467 non-null    float64            
 10  macd_signal  467 non-null    float64            
 11  macd_hist    467 non-null    float64            
 12  bb_upper     491 non-null    float64            
 13  bb_middle    491 non-null    float64            
 14  bb_lower     491 non-null    float64            
 15  atr_10       490 non-null    float64            
 16  obv          500 non-null    float64            
 17  stoch_k      492 non-null    float64            
 18  stoch_d      492 non-null    float64            
dtypes: datetime64[ns, UTC](1), float64(14), int64(4)
memory usage: 74.3 KB

Code Logic

  • All TA-Lib functions return numpy arrays of the same length as the input, with leading NaN values where insufficient data exists to compute the indicator.
  • Multi-output functions (MACD, BBANDS, STOCH) return multiple arrays via tuple unpacking, each assigned to its own column.
  • Indicator definitions match Notebook 18 — the library differs but the mathematical output is identical.

6. Data Visualization with Plotly

6.1 Candlestick Chart with Moving Averages (SMA, EMA)

[ ]
fig = go.FigureWidget(data=[
    go.Candlestick(
        x=df_indicators_large["datetime"],
        open=df_indicators_large['open'],
        high=df_indicators_large['high'],
        low=df_indicators_large['low'],
        close=df_indicators_large['close'],
        name='Price'
    )
])

# Add SMA_10
fig.add_trace(go.Scatter(
    x=df_indicators_large["datetime"],
    y=df_indicators_large['sma_10'],
    mode='lines',
    name='SMA 10',
    line=dict(color='blue', width=1)
))

# Add EMA_10
fig.add_trace(go.Scatter(
    x=df_indicators_large["datetime"],
    y=df_indicators_large['ema_10'],
    mode='lines',
    name='EMA 10',
    line=dict(color='orange', width=1)
))

fig.update_layout(
    title_text='Candlestick Chart with SMA and EMA',
    xaxis_rangeslider_visible=False,
    xaxis_title='Date',
    yaxis_title='Price',
    height=600,
    yaxis=dict(autorange=True) # Ensure y-axis scales to visible candles
)

fig.show()

6.2 Candlestick Chart with Bollinger Bands

[ ]
fig = go.FigureWidget(data=[
    go.Candlestick(
        x=df_indicators_large["datetime"],
        open=df_indicators_large['open'],
        high=df_indicators_large['high'],
        low=df_indicators_large['low'],
        close=df_indicators_large['close'],
        name='Price'
    )
])

# Add Bollinger Bands
fig.add_trace(go.Scatter(
    x=df_indicators_large["datetime"],
    y=df_indicators_large['bb_lower'],
    mode='lines',
    name='BB Lower',
    line=dict(color='red', width=1)
))
fig.add_trace(go.Scatter(
    x=df_indicators_large["datetime"],
    y=df_indicators_large['bb_middle'],
    mode='lines',
    name='BB Middle',
    line=dict(color='green', width=1, dash='dot')
))
fig.add_trace(go.Scatter(
    x=df_indicators_large["datetime"],
    y=df_indicators_large['bb_upper'],
    mode='lines',
    name='BB Upper',
    line=dict(color='red', width=1)
))

fig.update_layout(
    title_text='Candlestick Chart with Bollinger Bands',
    xaxis_rangeslider_visible=False,
    xaxis_title='Date',
    yaxis_title='Price',
    height=600,
    yaxis=dict(autorange=True) # Ensure y-axis scales to visible candles
)

fig.show()

6.3 Relative Strength Index (RSI)

[ ]
fig = make_subplots(
    rows=2, cols=1,
    shared_xaxes=True,
    vertical_spacing=0.03,
    row_heights=[0.7, 0.3]
)

# Candlestick chart
fig.add_trace(go.Candlestick(
    x=df_indicators_large["datetime"],
    open=df_indicators_large['open'],
    high=df_indicators_large['high'],
    low=df_indicators_large['low'],
    close=df_indicators_large['close'],
    name='Price'
), row=1, col=1)

# RSI chart
fig.add_trace(go.Scatter(
    x=df_indicators_large["datetime"],
    y=df_indicators_large['rsi_14'],
    mode='lines',
    name='RSI 14',
    line=dict(color='purple', width=1)
), row=2, col=1)

# Add RSI overbought/oversold levels
fig.add_hline(y=70, line_dash="dot", line_color="red", row=2, col=1)
fig.add_hline(y=30, line_dash="dot", line_color="green", row=2, col=1)

fig.update_layout(
    title_text='Candlestick Chart with RSI',
    xaxis_rangeslider_visible=False,
    xaxis_title='Date',
    yaxis_title='Price',
    height=800,
    yaxis=dict(autorange=True) # Ensure y-axis scales to visible candles for candlestick
)

fig.update_yaxes(title_text="RSI", row=2, col=1)

fig.show()

6.4 Moving Average Convergence Divergence (MACD)

[ ]
fig = make_subplots(
    rows=2, cols=1,
    shared_xaxes=True,
    vertical_spacing=0.03,
    row_heights=[0.7, 0.3]
)

# Candlestick chart
fig.add_trace(go.Candlestick(
    x=df_indicators_large["datetime"],
    open=df_indicators_large['open'],
    high=df_indicators_large['high'],
    low=df_indicators_large['low'],
    close=df_indicators_large['close'],
    name='Price'
), row=1, col=1)

# MACD traces
fig.add_trace(go.Scatter(
    x=df_indicators_large["datetime"],
    y=df_indicators_large['macd'],
    mode='lines',
    name='MACD',
    line=dict(color='blue', width=1)
), row=2, col=1)

fig.add_trace(go.Scatter(
    x=df_indicators_large["datetime"],
    y=df_indicators_large['macd_signal'],
    mode='lines',
    name='Signal',
    line=dict(color='red', width=1)
), row=2, col=1)

fig.add_trace(go.Bar(
    x=df_indicators_large["datetime"],
    y=df_indicators_large['macd_hist'],
    name='Histogram',
    marker_color='green' # Green for positive, could add logic for red for negative
), row=2, col=1)

fig.update_layout(
    title_text='Candlestick Chart with MACD',
    xaxis_rangeslider_visible=False,
    xaxis_title='Date',
    yaxis_title='Price',
    height=800,
    yaxis=dict(autorange=True) # Ensure y-axis scales to visible candles for candlestick
)

fig.update_yaxes(title_text="MACD", row=2, col=1)

fig.show()

6.5 Average True Range (ATR)

[ ]
fig = make_subplots(
    rows=2, cols=1,
    shared_xaxes=True,
    vertical_spacing=0.03,
    row_heights=[0.7, 0.3]
)

# Candlestick chart
fig.add_trace(go.Candlestick(
    x=df_indicators_large["datetime"],
    open=df_indicators_large['open'],
    high=df_indicators_large['high'],
    low=df_indicators_large['low'],
    close=df_indicators_large['close'],
    name='Price'
), row=1, col=1)

# ATR chart
fig.add_trace(go.Scatter(
    x=df_indicators_large["datetime"],
    y=df_indicators_large['atr_10'],
    mode='lines',
    name='ATR 10',
    line=dict(color='darkorange', width=1)
), row=2, col=1)

fig.update_layout(
    title_text='Candlestick Chart with ATR',
    xaxis_rangeslider_visible=False,
    xaxis_title='Date',
    yaxis_title='Price',
    height=800,
    yaxis=dict(autorange=True) # Ensure y-axis scales to visible candles for candlestick
)

fig.update_yaxes(title_text="ATR", row=2, col=1)

fig.show()

6.6 On-Balance Volume (OBV)

[ ]
fig = make_subplots(
    rows=2, cols=1,
    shared_xaxes=True,
    vertical_spacing=0.03,
    row_heights=[0.7, 0.3]
)

# Candlestick chart
fig.add_trace(go.Candlestick(
    x=df_indicators_large["datetime"],
    open=df_indicators_large['open'],
    high=df_indicators_large['high'],
    low=df_indicators_large['low'],
    close=df_indicators_large['close'],
    name='Price'
), row=1, col=1)

# OBV chart
fig.add_trace(go.Scatter(
    x=df_indicators_large["datetime"],
    y=df_indicators_large['obv'],
    mode='lines',
    name='OBV',
    line=dict(color='brown', width=1)
), row=2, col=1)

fig.update_layout(
    title_text='Candlestick Chart with OBV',
    xaxis_rangeslider_visible=False,
    xaxis_title='Date',
    yaxis_title='Price',
    height=800,
    yaxis=dict(autorange=True) # Ensure y-axis scales to visible candles for candlestick
)

fig.update_yaxes(title_text="OBV", row=2, col=1)

fig.show()

6.7 Stochastic Oscillator

[ ]
fig = make_subplots(
    rows=2, cols=1,
    shared_xaxes=True,
    vertical_spacing=0.03,
    row_heights=[0.7, 0.3]
)

# Candlestick chart
fig.add_trace(go.Candlestick(
    x=df_indicators_large["datetime"],
    open=df_indicators_large['open'],
    high=df_indicators_large['high'],
    low=df_indicators_large['low'],
    close=df_indicators_large['close'],
    name='Price'
), row=1, col=1)

# Stochastic Oscillator chart
fig.add_trace(go.Scatter(
    x=df_indicators_large["datetime"],
    y=df_indicators_large['stoch_k'],
    mode='lines',
    name='%K',
    line=dict(color='blue', width=1)
), row=2, col=1)

fig.add_trace(go.Scatter(
    x=df_indicators_large["datetime"],
    y=df_indicators_large['stoch_d'],
    mode='lines',
    name='%D',
    line=dict(color='red', width=1)
), row=2, col=1)

# Add overbought/oversold levels for Stochastic
fig.add_hline(y=80, line_dash="dot", line_color="red", row=2, col=1)
fig.add_hline(y=20, line_dash="dot", line_color="green", row=2, col=1)

fig.update_layout(
    title_text='Candlestick Chart with Stochastic Oscillator',
    xaxis_rangeslider_visible=False,
    xaxis_title='Date',
    yaxis_title='Price',
    height=800,
    yaxis=dict(autorange=True) # Ensure y-axis scales to visible candles for candlestick
)

fig.update_yaxes(title_text="Stochastic", row=2, col=1)

fig.show()

Conclusion

This notebook demonstrates how to effectively compute and visualize various technical indicators using the TA-Lib library, a powerful tool for financial analysis. By leveraging TA-Lib's compiled C backend, we achieved efficient calculations on OHLCV data. The visualizations provided a clear understanding of each indicator's behavior relative to price action, highlighting their utility in identifying potential market trends, momentum, volatility, and overbought/oversold conditions.

Key takeaways include:

  • Efficiency: TA-Lib's performance advantage, especially with large datasets.
  • Comprehensive Indicators: The ability to calculate a wide range of popular technical indicators.
  • Visualization: The importance of plotting indicators alongside price data for meaningful analysis.

This framework can be extended for further analysis, strategy development, and backtesting in financial markets.