The quest for reliable trading signals, especially in the notoriously volatile Bitcoin market, often feels like searching for a financial crystal ball. We want indicators that don’t just describe the past, but offer genuine insight into future price movements. While true prediction is elusive, can we refine our technical strategies to filter out noise and identify higher-probability setups? Can layering specific filters significantly enhance the predictive quality of our signals?
We previously explored a strategy combining trend following (using an EMA) with momentum confirmation (using the standard A/D or Chaikin Oscillator). Now, we’re adding two powerful filters designed to address common pitfalls: the ADX for trend strength and a custom Volatility-Adjusted A/D Oscillator for relative momentum magnitude. Let’s see if this multi-layered approach brings us closer to clarity.
The Foundation: Trend and Momentum Trigger
Our base strategy remains simple and logical:
Refinement Layer 1: Filtering the Chop with ADX
Trading during directionless, choppy markets is a primary source of false signals.2 The Average Directional Index (ADX) helps by measuring trend strength.3
Refinement Layer 2: Normalizing Momentum with Volatility Adjustment
Standard oscillators can be misleading. A large swing might look significant but simply reflect high market volatility, while a small swing in a quiet market might be more meaningful. We address this by creating a Volatility-Adjusted A/D Oscillator.
VolAdj_ADOSC = Standard_Chaikin_Oscillator / Smoothed_ATR
VolAdj_ADOSC
to be above a certain positive threshold
(e.g., +0.5). For shorts, we might require it to be below a negative
threshold (e.g., -0.5). This demands that the momentum is not just
directionally aligned but also relatively strong compared to recent
price fluctuations.The Combined Logic: Seeking Higher Probability
A signal is only generated when all enabled conditions align:
Implementation Snippets (Python)
Here’s how calculating the indicators and combining the logic looks:
Python
# --- Assume 'data' DataFrame with OHLCV exists ---
import pandas_ta as ta
import numpy as np
# --- Parameters ---
ema_length = 50
adosc_fast = 3
adosc_slow = 10
adx_length = 14
adx_threshold = 25
atr_length = 14
atr_smoothing_period = 10
vol_adj_threshold_long = 0.5 # Tune this!
vol_adj_threshold_short = -0.5 # Tune this!
enable_adx_filter = True
enable_vol_adj_filter = True
# --- Calculate Indicators ---
data[f'EMA_{ema_length}'] = ta.ema(data['Close'], length=ema_length)
data['ADOSC'] = ta.adosc(data['High'], data['Low'], data['Close'], data['Volume'], fast=adosc_fast, slow=adosc_slow)
if enable_adx_filter:
adx_df = ta.adx(data['High'], data['Low'], data['Close'], length=adx_length)
adx_col_name = f'ADX_{adx_length}'
if adx_col_name in adx_df.columns: data['ADX'] = adx_df[adx_col_name]
else: data['ADX'] = pd.NA
data['ATR'] = ta.atr(data['High'], data['Low'], data['Close'], length=atr_length)
data['ATR_Smooth'] = ta.ema(data['ATR'], length=atr_smoothing_period)
denominator = data['ATR_Smooth'].replace(0, np.nan)
data['VolAdj_ADOSC'] = data['ADOSC'] / denominator
data.dropna(inplace=True) # Drop initial NaNs
# --- Define Filter Conditions ---
is_trending = (data['ADX'].notna() & (data['ADX'] > adx_threshold)) if enable_adx_filter else True
vol_adj_long_filter = (data['VolAdj_ADOSC'].notna() & (data['VolAdj_ADOSC'] > vol_adj_threshold_long)) if enable_vol_adj_filter else True
vol_adj_short_filter = (data['VolAdj_ADOSC'].notna() & (data['VolAdj_ADOSC'] < vol_adj_threshold_short)) if enable_vol_adj_filter else True
# --- Base Signal Conditions ---
long_trend = data['Close'] > data[f'EMA_{ema_length}']
short_trend = data['Close'] < data[f'EMA_{ema_length}']
long_momentum_cross = (data['ADOSC'] > 0) & (data['ADOSC'].shift(1) <= 0)
short_momentum_cross = (data['ADOSC'] < 0) & (data['ADOSC'].shift(1) >= 0)
# --- Combine for Final Signals ---
data['Long_Signal'] = (long_trend & long_momentum_cross & is_trending & vol_adj_long_filter)
data['Short_Signal'] = (short_trend & short_momentum_cross & is_trending & vol_adj_short_filter)
Visualizing the Refinement
Plotting this helps immensely. You’d see the price and EMA on the top chart. Below it, the Volatility-Adjusted A/D Oscillator with its thresholds, then the standard A/D Oscillator showing the zero crosses, and finally the ATR/ADX panel showing the filter conditions. Crucially, the buy/sell arrows on the price chart only appear when the base conditions are met and the relevant indicators in the lower panels satisfy their filter criteria (ADX above threshold, VolAdj_ADOSC beyond its threshold).
Predictive Power: Enhancing Probabilities, Not Seeing the Future
Does adding these filters give the signals true “predictive power”? Let’s be precise. Technical indicators don’t predict the future like a crystal ball. They identify historical patterns and conditions associated with higher probabilities of certain outcomes.
The “predictive power” we seek isn’t certainty; it’s a statistically improved edge. The real test lies in backtesting. Does this multi-filtered strategy demonstrably outperform the simpler versions on historical data, considering metrics like win rate, profit factor, and maximum drawdown, after accounting for estimated trading costs?
Conclusion
Layering filters like ADX and a custom Volatility-Adjusted A/D Oscillator onto a core trend and momentum strategy represents a logical step towards refining signals and potentially avoiding unfavorable market conditions. It aims to enhance the quality and probability associated with each signal, moving closer to clarity rather than relying on a flawed crystal ball.
However, complexity isn’t always better. Whether these filters genuinely improve the strategy’s “predictive power” in a practical, profitable sense can only be determined through meticulous backtesting, parameter optimization, and ultimately, cautious application with robust risk management. This approach offers a framework for seeking higher-probability signals, but the hard work of validation remains essential.