Advanced Indicators

This page covers advanced technical indicators that require OHLC (Open, High, Low, Close) data.

Using the DataFrame Accessor

All OHLC indicators can be called directly on a DataFrame. Rhoa auto-detects the Close, High, and Low columns:

import pandas as pd
import rhoa

df = pd.read_csv('prices.csv')

# DataFrame accessor — columns auto-detected
atr = df.rhoa.indicators.atr(window_size=14)
stoch = df.rhoa.indicators.stochastic(k_window=14, d_window=3)
cci = df.rhoa.indicators.cci(window_size=20)
wr = df.rhoa.indicators.williams_r(window_size=14)
adx_data = df.rhoa.indicators.adx(window_size=14)
sar = df.rhoa.indicators.parabolic_sar()

# Single-series indicators also work, defaulting to Close
sma = df.rhoa.indicators.sma(window_size=20)
rsi = df.rhoa.indicators.rsi(window_size=14)
macd = df.rhoa.indicators.macd()

The examples below use the DataFrame accessor pattern throughout.

MACD (Moving Average Convergence Divergence)

MACD is a trend-following momentum indicator showing the relationship between two moving averages.

Basic MACD

import pandas as pd
import rhoa

df = pd.read_csv('prices.csv')

# Calculate MACD with default parameters (12, 26, 9)
macd_data = df.rhoa.indicators.macd()

# Extract components
df['MACD'] = macd_data['macd']
df['MACD_Signal'] = macd_data['signal']
df['MACD_Histogram'] = macd_data['histogram']

print(df[['Close', 'MACD', 'MACD_Signal', 'MACD_Histogram']].tail())

Custom Parameters

# Use custom parameters: fast=8, slow=21, signal=5
macd_data = df.rhoa.indicators.macd(
    fast_period=8,
    slow_period=21,
    signal_period=5
)

MACD Crossover Signals

macd_data = df.rhoa.indicators.macd()
macd = macd_data['macd']
signal = macd_data['signal']

# Bullish crossover: MACD crosses above signal line
bullish_cross = (macd > signal) & (macd.shift(1) <= signal.shift(1))

# Bearish crossover: MACD crosses below signal line
bearish_cross = (macd < signal) & (macd.shift(1) >= signal.shift(1))

# Zero line crossovers
macd_above_zero = (macd > 0) & (macd.shift(1) <= 0)
macd_below_zero = (macd < 0) & (macd.shift(1) >= 0)

print(f"Bullish crossovers: {bullish_cross.sum()}")
print(f"Bearish crossovers: {bearish_cross.sum()}")
print(f"MACD crossed above zero: {macd_above_zero.sum()}")
print(f"MACD crossed below zero: {macd_below_zero.sum()}")

MACD Histogram Analysis

macd_data = df.rhoa.indicators.macd()
histogram = macd_data['histogram']

# Histogram turning positive (momentum shift)
momentum_shift_bullish = (histogram > 0) & (histogram.shift(1) <= 0)

# Histogram divergence (simplified)
# When histogram makes higher lows while price makes lower lows
df['MACD_Hist'] = histogram

Bollinger Bands

Bollinger Bands measure volatility and identify potential overbought/oversold conditions.

Basic Bollinger Bands

# Calculate Bollinger Bands (20-period, 2 std devs)
bb = df.rhoa.indicators.bollinger_bands(window_size=20, num_std=2.0)

df['BB_Upper'] = bb['upper_band']
df['BB_Middle'] = bb['middle_band']
df['BB_Lower'] = bb['lower_band']

print(df[['Close', 'BB_Upper', 'BB_Middle', 'BB_Lower']].tail())

Bollinger Band Squeeze

Identify low volatility periods that often precede large moves:

bb = df.rhoa.indicators.bollinger_bands(window_size=20, num_std=2.0)

# Calculate band width
band_width = (bb['upper_band'] - bb['lower_band']) / bb['middle_band']

# Squeeze: band width in lowest 20% historically
squeeze_threshold = band_width.quantile(0.20)
squeeze = band_width < squeeze_threshold

print(f"Squeeze periods: {squeeze.sum()}")
print(f"Average band width: {band_width.mean():.4f}")

Bollinger Band Breakouts

bb = df.rhoa.indicators.bollinger_bands(window_size=20, num_std=2.0)

# Price touching or exceeding upper band
upper_touch = df['Close'] >= bb['upper_band']

# Price touching or exceeding lower band
lower_touch = df['Close'] <= bb['lower_band']

# Strong breakout: close outside bands
upper_breakout = df['Close'] > bb['upper_band']
lower_breakout = df['Close'] < bb['lower_band']

print(f"Upper band touches: {upper_touch.sum()}")
print(f"Lower band touches: {lower_touch.sum()}")

%B Indicator

Calculate where price is relative to the bands:

bb = df.rhoa.indicators.bollinger_bands(window_size=20, num_std=2.0)

# %B = (Close - Lower Band) / (Upper Band - Lower Band)
# %B > 1: above upper band
# %B < 0: below lower band
# %B = 0.5: at middle band

percent_b = (df['Close'] - bb['lower_band']) / (bb['upper_band'] - bb['lower_band'])
df['Percent_B'] = percent_b

# Identify extremes
extremely_overbought = percent_b > 1.0
extremely_oversold = percent_b < 0.0

ADX (Average Directional Index)

ADX measures trend strength without indicating direction.

Basic ADX

# Calculate ADX with 14-period window
adx_data = df.rhoa.indicators.adx(window_size=14)

df['ADX'] = adx_data['ADX']
df['+DI'] = adx_data['+DI']
df['-DI'] = adx_data['-DI']

print(df[['Close', 'ADX', '+DI', '-DI']].tail())

Interpret ADX Values

adx_data = df.rhoa.indicators.adx(window_size=14)
adx = adx_data['ADX']

# ADX interpretation
# < 20: Weak trend or ranging market
# 20-25: Emerging trend
# 25-50: Strong trend
# > 50: Very strong trend

weak_trend = adx < 20
strong_trend = adx > 25
very_strong = adx > 50

print(f"Weak trend periods: {weak_trend.sum()}")
print(f"Strong trend periods: {strong_trend.sum()}")
print(f"Very strong trend periods: {very_strong.sum()}")

Directional Movement Strategy

adx_data = df.rhoa.indicators.adx(window_size=14)

adx = adx_data['ADX']
plus_di = adx_data['+DI']
minus_di = adx_data['-DI']

# Strong uptrend: +DI > -DI and ADX > 25
strong_uptrend = (plus_di > minus_di) & (adx > 25)

# Strong downtrend: -DI > +DI and ADX > 25
strong_downtrend = (minus_di > plus_di) & (adx > 25)

# Ranging market: ADX < 20
ranging = adx < 20

print(f"Strong uptrend periods: {strong_uptrend.sum()}")
print(f"Strong downtrend periods: {strong_downtrend.sum()}")
print(f"Ranging periods: {ranging.sum()}")

Average True Range (ATR)

ATR measures market volatility.

Basic ATR

# Calculate 14-period ATR
atr = df.rhoa.indicators.atr(window_size=14)
df['ATR_14'] = atr

print(f"Average ATR: {atr.mean():.2f}")
print(f"Current ATR: {atr.iloc[-1]:.2f}")

Volatility Regimes

atr = df.rhoa.indicators.atr(window_size=14)

# Define volatility regimes using percentiles
low_vol = atr < atr.quantile(0.33)
normal_vol = (atr >= atr.quantile(0.33)) & (atr < atr.quantile(0.67))
high_vol = atr >= atr.quantile(0.67)

print(f"Low volatility: {low_vol.sum()} days")
print(f"Normal volatility: {normal_vol.sum()} days")
print(f"High volatility: {high_vol.sum()} days")

ATR-Based Position Sizing

atr = df.rhoa.indicators.atr(window_size=14)

# Risk per trade: 2% of account
account_size = 100000
risk_per_trade = account_size * 0.02  # $2000

# Position size based on ATR stop loss
# Stop loss = 2x ATR
stop_loss_atr = 2 * atr
position_size = risk_per_trade / stop_loss_atr

df['Position_Size'] = position_size
print(df[['Close', 'ATR_14', 'Position_Size']].tail())

Stochastic Oscillator

The Stochastic Oscillator compares closing price to the price range over time.

Basic Stochastic

# Calculate Stochastic with 14-period %K and 3-period %D
stoch = df.rhoa.indicators.stochastic(k_window=14, d_window=3)

df['Stoch_K'] = stoch['%K']
df['Stoch_D'] = stoch['%D']

print(df[['Close', 'Stoch_K', 'Stoch_D']].tail())

Stochastic Signals

stoch = df.rhoa.indicators.stochastic(k_window=14, d_window=3)
k = stoch['%K']
d = stoch['%D']

# Overbought/Oversold levels
overbought = k > 80
oversold = k < 20

# Crossover signals
bullish_cross = (k > d) & (k.shift(1) <= d.shift(1))
bearish_cross = (k < d) & (k.shift(1) >= d.shift(1))

# Buy signal: bullish cross in oversold region
buy_signal = bullish_cross & (k < 20)

# Sell signal: bearish cross in overbought region
sell_signal = bearish_cross & (k > 80)

print(f"Buy signals: {buy_signal.sum()}")
print(f"Sell signals: {sell_signal.sum()}")

CCI (Commodity Channel Index)

CCI identifies cyclical trends and potential reversals.

Basic CCI

# Calculate 20-period CCI
cci = df.rhoa.indicators.cci(window_size=20)
df['CCI_20'] = cci

print(f"CCI range: {cci.min():.2f} to {cci.max():.2f}")

CCI Trading Signals

cci = df.rhoa.indicators.cci(window_size=20)

# Traditional levels
# > +100: Overbought
# < -100: Oversold

overbought = cci > 100
oversold = cci < -100

# Extreme levels
extremely_overbought = cci > 200
extremely_oversold = cci < -200

# Zero line crossovers
bullish_cross = (cci > 0) & (cci.shift(1) <= 0)
bearish_cross = (cci < 0) & (cci.shift(1) >= 0)

print(f"Overbought periods: {overbought.sum()}")
print(f"Oversold periods: {oversold.sum()}")

Parabolic SAR

Parabolic SAR identifies potential reversal points.

Basic SAR

# Calculate Parabolic SAR
sar = df.rhoa.indicators.parabolic_sar()
df['SAR'] = sar

# SAR below price = uptrend
# SAR above price = downtrend
uptrend = df['Close'] > sar
downtrend = df['Close'] < sar

print(f"Uptrend periods: {uptrend.sum()}")
print(f"Downtrend periods: {downtrend.sum()}")

SAR Reversals

sar = df.rhoa.indicators.parabolic_sar()

# Detect trend reversals
uptrend = df['Close'] > sar
downtrend = df['Close'] < sar

# Bullish reversal: downtrend to uptrend
bullish_reversal = uptrend & ~uptrend.shift(1)

# Bearish reversal: uptrend to downtrend
bearish_reversal = downtrend & ~downtrend.shift(1)

print(f"Bullish reversals: {bullish_reversal.sum()}")
print(f"Bearish reversals: {bearish_reversal.sum()}")

Multi-Indicator Strategies

Combine multiple advanced indicators for robust signals.

Trend + Momentum Confirmation

# Calculate indicators
adx_data = df.rhoa.indicators.adx(window_size=14)
macd_data = df.rhoa.indicators.macd()
bb = df.rhoa.indicators.bollinger_bands(20, 2.0)

# Strong trend confirmation
strong_trend = adx_data['ADX'] > 25
bullish_momentum = macd_data['macd'] > macd_data['signal']
price_above_ma = df['Close'] > bb['middle_band']

# All conditions must be true
confirmed_uptrend = strong_trend & bullish_momentum & price_above_ma

print(f"Confirmed uptrend periods: {confirmed_uptrend.sum()}")

Volatility Breakout System

# Combine Bollinger Bands + ATR + ADX
bb = df.rhoa.indicators.bollinger_bands(20, 2.0)
atr = df.rhoa.indicators.atr(window_size=14)
adx_data = df.rhoa.indicators.adx(window_size=14)

# Squeeze: low volatility
band_width = (bb['upper_band'] - bb['lower_band']) / bb['middle_band']
squeeze = band_width < band_width.quantile(0.20)

# Breakout conditions
price_breakout = df['Close'] > bb['upper_band']
increasing_vol = atr > atr.shift(5)
trend_forming = adx_data['ADX'] > 20

# Breakout signal
breakout_signal = squeeze.shift(1) & price_breakout & increasing_vol & trend_forming

print(f"Breakout signals: {breakout_signal.sum()}")

Complete Technical Analysis Dashboard

def technical_dashboard(df):
    """Create comprehensive technical analysis dashboard."""
    # Trend indicators
    macd = df.rhoa.indicators.macd()
    adx = df.rhoa.indicators.adx(window_size=14)

    # Momentum
    rsi = df.rhoa.indicators.rsi(14)
    stoch = df.rhoa.indicators.stochastic(k_window=14, d_window=3)

    # Volatility
    bb = df.rhoa.indicators.bollinger_bands(20, 2.0)
    atr = df.rhoa.indicators.atr(window_size=14)

    # Summary
    print("=" * 50)
    print("TECHNICAL ANALYSIS DASHBOARD")
    print("=" * 50)
    print(f"\nTrend:")
    print(f"  ADX: {adx['ADX'].iloc[-1]:.2f} "
          f"({'Strong' if adx['ADX'].iloc[-1] > 25 else 'Weak'} trend)")
    print(f"  MACD: {macd['macd'].iloc[-1]:.2f}")

    print(f"\nMomentum:")
    print(f"  RSI: {rsi.iloc[-1]:.2f}")
    print(f"  Stochastic %K: {stoch['%K'].iloc[-1]:.2f}")

    print(f"\nVolatility:")
    print(f"  ATR: {atr.iloc[-1]:.2f}")
    print(f"  BB Width: {(bb['upper_band'].iloc[-1] - bb['lower_band'].iloc[-1]):.2f}")

    return df

# Use it
df = pd.read_csv('prices.csv')
df = technical_dashboard(df)

Next Steps

Continue to Target Generation to learn how to create optimized targets for machine learning models.