ML Signal Combined Strategy
Build a hybrid ML-enhanced strategy that combines machine learning model predictions with traditional technical indicator signals, using the ML confidence score to dynamically weight and filter indicator-based trade entries.
ML + Indicator Hybrid Strategy
1. Strategy Overview
This section details the design and operational logic of the ML + Indicator Hybrid strategy, which integrates a machine learning classifier with a traditional indicator signal. The primary objective is to achieve higher signal precision through a dual-confirmation gate mechanism.
Components:
- Machine Learning Model (Random Forest): A Random Forest classifier is trained on historical features to predict the directional movement of the subsequent candle's return. The model's output is quantized to +1 for predicted upward movement and -1 for predicted downward movement.
- Indicator Signal (Moving Average Crossover): A conventional technical indicator generates a directional signal. This signal is +1 when a fast-moving average (MA) is positioned above a slow-moving average, and -1 when it is positioned below.
- Combined Signal: A trade signal is generated exclusively when both the ML model's prediction and the indicator's signal concurrently agree on the direction.
Rationale for Combining ML and Indicators:
- Enhanced Feature Interaction: Machine learning models are capable of discerning complex, non-linear relationships within feature sets that traditional indicators often cannot capture.
- Regime Robustness: Indicators offer interpretable and stable signals across various market regimes, which ML models, particularly with limited datasets, may not consistently replicate.
- Improved Precision: The combined signal, while occurring less frequently, exhibits higher precision. Discrepancies between the two independent signal sources filter out false positives or negatives, thereby increasing the reliability of generated trade signals.
Data Integrity Note:
A strict forward-looking (no shuffle) train/test split is implemented to prevent data leakage. This methodology ensures that the model is never exposed to future data during its training phase, maintaining the integrity of backtesting and predictive performance evaluations.
2. Dependency Installation
Required libraries for data generation, machine learning, and visualization are installed.
import warnings; warnings.filterwarnings("ignore")
!pip install pandas numpy scikit-learn 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: scikit-learn in /usr/local/lib/python3.12/dist-packages (1.6.1) 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: scipy>=1.6.0 in /usr/local/lib/python3.12/dist-packages (from scikit-learn) (1.16.3) Requirement already satisfied: joblib>=1.2.0 in /usr/local/lib/python3.12/dist-packages (from scikit-learn) (1.5.3) Requirement already satisfied: threadpoolctl>=3.1.0 in /usr/local/lib/python3.12/dist-packages (from scikit-learn) (3.6.0) 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)
3. Library Imports
Essential Python libraries are imported to facilitate data manipulation, machine learning, and interactive plotting.
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler4. Data Generation
A synthetic dataset is generated to simulate financial time-series data. This function produces candlestick data (open, high, low, close) and volume over a specified number of periods, suitable for strategy testing.
def generate_data(periods):
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)5. ML + Indicator Hybrid Strategy Implementation
This function implements the hybrid strategy, incorporating feature engineering, machine learning model training, and signal generation based on both ML predictions and a moving average crossover indicator.
def ml_signal_combined_strategy(
df: pd.DataFrame,
train_size: int = 350,
) -> pd.DataFrame:
df = df.copy().sort_values("datetime", ignore_index=True)
# Feature Engineering
df["roc"] = df["close"].pct_change(5)
df["sma_diff"] = df["close"].rolling(5).mean() - df["close"].rolling(20).mean()
df["vol_ratio"] = df["volume"] / df["volume"].rolling(10).mean()
df["ret"] = df["close"].pct_change()
df["label"] = (df["ret"].shift(-1) > 0).astype(int)
df = df.dropna().reset_index(drop=True)
# Data Preparation for ML
features = ["roc","sma_diff","vol_ratio"]
X = StandardScaler().fit_transform(df[features])
y = df["label"].values
# ML Model Training and Prediction
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X[:train_size], y[:train_size])
ml_pred = np.full(len(df), 0)
ml_pred[train_size:] = model.predict(X[train_size:])
# Signal Generation
df["ml_signal"] = np.where(ml_pred == 1, 1, -1)
df["indicator_signal"] = np.where(df["sma_diff"] > 0, 1, -1)
df["signal"] = np.where(
(df["ml_signal"] == 1) & (df["indicator_signal"] == 1), 1,
np.where(
(df["ml_signal"] == -1) & (df["indicator_signal"] == -1), -1, 0))
return df
df_signals = ml_signal_combined_strategy(df, train_size=350)6. Signal Analysis and Visualization
Training and Prediction Scope: The machine learning model is trained exclusively on rows 0 through 349 of the dataset. Predictions are subsequently generated solely for rows 350 onwards.
Combined Signal Logic: The combined signal is assigned a value of zero when the ML model's prediction and the indicator's signal exhibit disagreement. This conservative approach significantly reduces the frequency of raw signals by approximately 50%, retaining only those instances where both independent sources of information are in strong agreement. This filtering mechanism is designed to enhance signal reliability.
The distribution of the generated signals is reported, followed by a visualization of these signals overlaid on a candlestick chart of the synthetic price data.
print("--- Signal Distribution ---"); print(df_signals["signal"].value_counts())
buy_signals = df_signals[df_signals["signal"] == 1]
sell_signals = df_signals[df_signals["signal"] == -1]
fig = go.FigureWidget(data=[go.Candlestick(
x=df_signals["datetime"], open=df_signals["open"], high=df_signals["high"],
low=df_signals["low"], close=df_signals["close"], name="Price")])
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)"))
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)"))
fig.update_layout(title_text="ML + Indicator Hybrid Strategy — Combined Signals",
xaxis_rangeslider_visible=False, height=600, yaxis=dict(autorange=True))
fig.show()--- Signal Distribution --- signal 0 258 -1 179 1 44 Name: count, dtype: int64
Conclusion
This notebook demonstrates a hybrid trading strategy combining a Random Forest machine learning model with a moving average crossover indicator. The approach focuses on achieving higher signal precision by requiring agreement from both the ML model and the traditional indicator before generating a trade signal. This dual-confirmation mechanism aims to filter out false signals and improve the reliability of trading decisions.