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.