If you have ever attempted to deploy an algorithmic trading bot or a stock scanner to a lightweight Docker container, AWS Lambda, or a standard Windows machine, you have likely run into the dreaded TA-Lib compilation failure. Error messages like cl.exe failed with exit status 2 or missing libta_lib.so dependencies are notorious roadblocks for Python developers.

While TA-Lib is the industry workhorse for technical analysis, its reliance on underlying C-extensions makes packaging and CI/CD pipelines fragile. Fortunately, you can reliably scan historical OHLC (Open, High, Low, Close) data for Japanese candlestick patterns using pure Python. In this guide, we will explore two robust approaches: a zero-dependency vectorized pandas/NumPy solution and the popular pure-Python library pandas-ta.

Why Avoid C-Extensions for Candlestick Scanning?

  • Serverless Readiness: Minimal runtimes like AWS Lambda or Google Cloud Functions have tight size constraints and lack pre-installed C development headers.
  • Container Portability: Alpine Linux-based Docker images require extra build tools to compile C libraries, inflating image size and build times.
  • Windows Compatibility: Installing TA-Lib on Windows often requires hunting down third-party precompiled .whl files or installing gigabytes of Visual Studio Build Tools.

Approach 1: Vectorized Candlestick Detection with Pandas & NumPy

Candlestick patterns are simply geometric relationships between candle bodies and shadows over a defined rolling window. Because pandas supports vectorized operations, computing these patterns natively with pandas and NumPy is often faster and much easier to debug than compiling external binaries.

Here is an implementation detecting three common patterns: Doji, Hammer, and Bullish Engulfing.

import numpy as np
import pandas as pd
import yfinance as yf

def add_candlestick_patterns(df: pd.DataFrame) -> pd.DataFrame:
    # Ensure required columns exist
    open_p = df['Open']
    high_p = df['High']
    low_p = df['Low']
    close_p = df['Close']
    
    # Dimensions of the candle
    body = (close_p - open_p).abs()
    candle_range = high_p - low_p
    upper_shadow = high_p - np.maximum(open_p, close_p)
    lower_shadow = np.minimum(open_p, close_p) - low_p
    
    # Avoid division by zero
    safe_range = candle_range.replace(0, np.nan)
    
    # 1. Doji: Body is less than 10% of total candle range
    df['is_doji'] = (body / safe_range) < 0.10
    
    # 2. Hammer: Small body at upper end, long lower shadow (>= 2x body), minimal upper shadow
    df['is_hammer'] = (
        (lower_shadow >= 2 * body) &
        (upper_shadow <= 0.2 * body) &
        (body > 0) &
        (body / safe_range > 0.1)
    )
    
    # 3. Bullish Engulfing: Prior red candle followed by a green candle that completely engulfs it
    prev_open = open_p.shift(1)
    prev_close = close_p.shift(1)
    
    prior_is_bearish = prev_close < prev_open
    current_is_bullish = close_p > open_p
    
    df['is_bullish_engulfing'] = (
        prior_is_bearish &
        current_is_bullish &
        (open_p <= prev_close) &
        (close_p >= prev_open)
    )
    
    return df

# Fetch sample data
df = yf.download("AAPL", period="3mo", interval="1d")
df = add_candlestick_patterns(df)

# Filter for signals
signals = df[df['is_bullish_engulfing'] | df['is_hammer'] | df['is_doji']]
print(signals[['Open', 'High', 'Low', 'Close', 'is_doji', 'is_hammer', 'is_bullish_engulfing']].tail())

Approach 2: Using pandas-ta (Drop-In Pure-Python Alternative)

If you need dozens of patterns (like Morning Star, Three White Soldiers, Evening Star, Hanging Man) and don't want to code the math manually, the pandas-ta library is the leading choice. It is written in pure Python and requires no external C compiler.

Install it easily via pip:

pip install pandas-ta yfinance

You can execute candlestick detection directly through the DataFrame accessor:

import pandas as pd
import pandas_ta as ta
import yfinance as yf

# Download historical OHLC data
df = yf.download("SPY", period="6mo", interval="1d")

# Flatten multi-index columns if returned by newer yfinance versions
if isinstance(df.columns, pd.MultiIndex):
    df.columns = df.columns.get_level_values(0)

# Option A: Run specific candlestick patterns
# Detects Doji and Morning Star
df.ta.cdl_pattern(name=["doji", "morningstar"], append=True)

# Option B: Run all supported candlestick patterns at once
# df.ta.cdl_pattern(name="all", append=True)

# pandas-ta assigns numerical scores (e.g., 100 for bullish, -100 for bearish, 0 for none)
matches = df[df['CDL_DOJI_10_0.1'] != 0]
print(matches[['Open', 'High', 'Low', 'Close', 'CDL_DOJI_10_0.1']].tail())

Performance Comparison: Vectorized Pandas vs. TA-Lib

You might wonder: is pure Python fast enough for real-time market data?

  • Throughput: Vectorized pandas and NumPy operations process 10,000 daily candles in a few milliseconds. For end-of-day (EOD) market scanners checking 3,000 equities, vectorized Python will complete the full scan in under 10 seconds.
  • Memory: NumPy avoids heavy memory overhead by executing contiguous array operations in low-level memory behind the scenes.
  • Maintainability: Custom formulas let you tweak definitions (such as relaxing the shadow ratio on a Hammer candle) directly in readable Python code, which is cumbersome in TA-Lib's hardcoded C engine.

Summary

You don't need to struggle with C-compiler toolchains to scan financial markets in Python. If you need simple pattern triggers, a vectorized NumPy/Pandas function gives you maximum speed with zero dependencies. If you need a comprehensive suite of conventional Japanese patterns, pandas-ta provides an effortless, drop-in replacement that runs seamlessly across Windows, Alpine Docker, and AWS Lambda.