Multi Indicator Score Strategy
Build a multi-indicator composite scoring system that aggregates RSI, MACD, ADX, Bollinger Bands, and volume indicators into a unified bullish and bearish confidence score for systematic trade entry and exit decisions.
Multi-Indicator Scoring Strategy for Financial Signal Generation
1. Strategy Definition
A multi-indicator scoring system aggregates signals from independent technical indicators into a single composite score. Each indicator contributes a directional vote: +1 (bullish), -1 (bearish), or 0 (neutral).
Signal Generation Logic:
The total score determines the overall trading signal:
| Total Score | Signal Description | Trading Signal |
|---|---|---|
| ≥ +2 | Strong bullish consensus | Buy (+1) |
| ≤ -2 | Strong bearish consensus | Sell (-1) |
| -1 to +1 | Mixed or no consensus | No signal (0) |
Constituent Indicators:
The strategy employs the following four independent indicators:
- Relative Strength Index (RSI) Score: Generated as +1 if the asset is oversold (RSI < 40), -1 if overbought (RSI > 60), and 0 otherwise.
- Moving Average (MA) Crossover Score: Generated as +1 if the fast moving average is above the slow moving average, and -1 if the fast moving average is below the slow moving average.
- Volume Score: Generated as +1 if the current trading volume exceeds its rolling average, and 0 otherwise. This indicator is bounded to 0/+1; the absence of high volume is considered neutral, not bearish, serving as an additive confirmation.
- Rate of Change (ROC) Score: Generated as +1 if the 5-period Rate of Change is positive, and -1 if negative.
Rationale for Multi-Indicator Approach:
Individual technical indicators often exhibit high rates of false signals. By requiring consensus across multiple independent indicators, each measuring different aspects of market behavior (e.g., momentum, trend, volume, rate of change), the probability of generating a genuine and robust trading signal is significantly enhanced compared to reliance on any single indicator in isolation. A consensus threshold of two or more indicators (out of a maximum of four) serves as a majority-rule filter, ensuring that signals reflect multi-dimensional market conditions rather than isolated indicator triggers.
2. Setup and Dependencies
2.1. Install Required Libraries
!pip install pandas numpy plotlyRequirement already satisfied: pandas in /usr/local/lib/python3.12/dist-packages (2.2.2) Requirement already satisfied: numpy in /usr/local/lib/python3.12/dist-packages (2.0.2) Requirement already satisfied: plotly in /usr/local/lib/python3.12/dist-packages (5.24.1) Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.12/dist-packages (from pandas) (2.9.0.post0) Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.12/dist-packages (from pandas) (2025.2) Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.12/dist-packages (from pandas) (2026.1) Requirement already satisfied: tenacity>=6.2.0 in /usr/local/lib/python3.12/dist-packages (from plotly) (9.1.4) Requirement already satisfied: packaging in /usr/local/lib/python3.12/dist-packages (from plotly) (26.1) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)
2.2. Import Libraries
import warnings
warnings.filterwarnings("ignore")
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots3. Data Generation
3.1. Generate Synthetic OHLCV Data
def generate_data(periods):
"""
Generates synthetic Open-High-Low-Close-Volume (OHLCV) financial data.
Args:
periods (int): The number of time periods (e.g., minutes) for which to generate data.
Returns:
pd.DataFrame: A DataFrame containing synthetic OHLCV data with a datetime index.
"""
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
for i in range(periods):
open_price = last_close + np.random.normal(0, last_close * 0.0005)
close_price = open_price + np.random.normal(0, last_close * 0.005)
body_high = max(open_price, close_price)
body_low = min(open_price, close_price)
high_price = max(body_high + abs(np.random.normal(0, last_close * 0.002)), open_price, close_price)
low_price = min(body_low - abs(np.random.normal(0, last_close * 0.002)), open_price, close_price)
if high_price < low_price: high_price, low_price = low_price, high_price
price_data.append({"open": max(1,int(open_price)), "high": max(1,int(high_price)),
"low": max(1,int(low_price)), "close": max(1,int(close_price))})
last_close = close_price
df = pd.DataFrame(price_data, index=datetime_index)
df.index.name = "datetime"
df["volume"] = np.random.uniform(100.0, 500.0, periods)
df["datetime"] = df.index.to_series()
return df.reset_index(drop=True)
df = generate_data(500)4. Multi-Indicator Scoring Strategy Implementation
def multi_indicator_score_strategy(df: pd.DataFrame) -> pd.DataFrame:
"""
Applies the multi-indicator scoring strategy to a DataFrame of OHLCV data.
Args:
df (pd.DataFrame): Input DataFrame containing 'open', 'high', 'low', 'close', 'volume', and 'datetime' columns.
Returns:
pd.DataFrame: The input DataFrame augmented with individual indicator scores, total score, and final signal.
"""
df = df.copy().sort_values("datetime", ignore_index=True)
# 1. RSI Score Calculation
delta = df["close"].diff()
gain = delta.clip(lower=0).rolling(14).mean()
loss = (-delta.clip(upper=0)).rolling(14).mean()
rsi = 100 - (100 / (1 + gain / loss.replace(0, np.nan)))
df["score_rsi"] = np.where(rsi < 40, 1, np.where(rsi > 60, -1, 0))
# 2. Moving Average Crossover Score Calculation
df["score_ma"] = np.where(df["close"].rolling(5).mean() > df["close"].rolling(20).mean(), 1, -1)
# 3. Volume Score Calculation
df["score_vol"] = np.where(df["volume"] > df["volume"].rolling(20).mean(), 1, 0)
# 4. Rate of Change (ROC) Score Calculation
roc = df["close"].pct_change(5)
df["score_roc"] = np.where(roc > 0, 1, -1)
# Aggregate Scores
score_cols = ["score_rsi","score_ma","score_vol","score_roc"]
df["total_score"] = df[score_cols].sum(axis=1)
# Generate Final Signal
df["signal"] = np.where(df["total_score"] >= 2, 1,
np.where(df["total_score"] <= -2, -1, 0))
return df5. Apply Strategy and Analyze Signal Distribution
df_signals = multi_indicator_score_strategy(df)
print("Signal Distribution:")
print(df_signals["signal"].value_counts())
print("\nScore Distribution:")
print(df_signals["total_score"].value_counts().sort_index())Signal Distribution: signal 0 360 1 82 -1 58 Name: count, dtype: int64 Score Distribution: total_score -2 58 -1 134 0 126 1 100 2 57 3 25 Name: count, dtype: int64
6. Visualization of Strategy Signals
buy_signals = df_signals[df_signals["signal"] == 1]
sell_signals = df_signals[df_signals["signal"] == -1]
fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
subplot_titles=["Price and Trading Signals", "Composite Score"],
row_heights=[0.65, 0.35])
fig.add_trace(go.Candlestick(x=df_signals["datetime"],
open=df_signals["open"], high=df_signals["high"],
low=df_signals["low"], close=df_signals["close"], name="Price"), row=1, col=1)
fig.add_trace(go.Scatter(x=buy_signals["datetime"], y=buy_signals["low"] * 0.999,
mode="markers", marker=dict(symbol="triangle-up", size=10, color="green"), name="Buy (+1)"), row=1, col=1)
fig.add_trace(go.Scatter(x=sell_signals["datetime"], y=sell_signals["high"] * 1.001,
mode="markers", marker=dict(symbol="triangle-down", size=10, color="red"), name="Sell (−1)"), row=1, col=1)
fig.add_trace(go.Bar(x=df_signals["datetime"], y=df_signals["total_score"],
name="Total Score",
marker_color=["green" if v >= 2 else "red" if v <= -2 else "gray" for v in df_signals["total_score"]]),
row=2, col=1)
fig.add_hline(y= 2, line_dash="dash", line_color="green", row=2, col=1, annotation_text="Buy threshold")
fig.add_hline(y=-2, line_dash="dash", line_color="red", row=2, col=1, annotation_text="Sell threshold")
fig.update_layout(title_text="Multi-Indicator Scoring Strategy Performance",
xaxis_rangeslider_visible=False, height=700, yaxis=dict(autorange=True))
fig.show()7. Conclusion
This notebook demonstrates a multi-indicator scoring strategy for generating financial signals. By combining four independent technical indicators (RSI, Moving Average Crossover, Volume, and Rate of Change), the strategy aims to produce more robust trading signals by requiring a consensus among these indicators.
Key takeaways:
- The
multi_indicator_score_strategyfunction successfully calculates individual indicator scores, aggregates them into atotal_score, and generates a finalsignalbased on predefined thresholds. - The signal distribution and score distribution provide insight into how often buy, sell, and no-signal conditions are met, and how the individual scores contribute to the aggregate total.
- Visualizations clearly illustrate the synthetic price action, the generated buy/sell signals, and the composite score over time, helping to understand the strategy's performance.
This approach helps to mitigate false signals that can arise from relying on a single indicator, offering a more comprehensive view of market conditions before making a trading decision.