Classification Signal Model
Predict trade direction (long/short/flat) using a classification model trained on lagged returns, volume, and volatility features.
Signals — Classification Models
1. Dependency Installation
!pip install pandas numpy plotly scikit-learnRequirement 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: scikit-learn in /usr/local/lib/python3.12/dist-packages (1.6.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: 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: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)
2. Library Imports
import warnings; warnings.filterwarnings("ignore")
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import cross_val_score3. Strategy Overview
Classification models predict the discrete direction of future price movement (+1 up, 0 down) from engineered technical features.
Models evaluated:
- Logistic Regression: Linear baseline; interpretable coefficients.
- Random Forest: Ensemble of decision trees; handles non-linear feature interactions.
- Gradient Boosting: Sequential boosting; typically higher accuracy at the cost of longer training.
Evaluation metrics:
- Accuracy, Precision, Recall, F1 — standard classification metrics.
- ROC-AUC — rank-ordering quality; preferable for imbalanced datasets.
- Confusion matrix — breakdown of true/false positives and negatives.
Limitation: Classification accuracy alone does not guarantee profitability; a model may be directionally correct but generate signals at poor risk/reward points. Backtest P&L evaluation is required.
4. Data Generation
def generate_data(periods: int) -> pd.DataFrame:
"""
Generate synthetic OHLCV price data using a geometric random walk.
Parameters
----------
periods : int
Number of 1-minute bars to generate.
Returns
-------
pd.DataFrame
DataFrame with columns: open, high, low, close, volume, datetime.
"""
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)
display(df.head())| open | high | low | close | volume | datetime | |
|---|---|---|---|---|---|---|
| 0 | 42009 | 42382 | 41992 | 42227 | 378.076885 | 2024-01-01 00:00:00+00:00 |
| 1 | 42273 | 42503 | 42266 | 42503 | 468.429426 | 2024-01-01 00:01:00+00:00 |
| 2 | 42497 | 42593 | 42385 | 42584 | 498.404160 | 2024-01-01 00:02:00+00:00 |
| 3 | 42593 | 42856 | 42513 | 42538 | 449.158083 | 2024-01-01 00:03:00+00:00 |
| 4 | 42548 | 42691 | 42308 | 42319 | 378.990673 | 2024-01-01 00:04:00+00:00 |
5. Feature Engineering (Shared Pipeline)
def build_features(df: pd.DataFrame, horizon: int = 5) -> tuple:
"""
Compute technical features and binary labels for classification.
Parameters
----------
df : pd.DataFrame
OHLCV DataFrame.
horizon : int
Forward bars for label computation.
Returns
-------
tuple
(X_train, X_test, y_train, y_test, feature_names)
"""
df = df.copy().sort_values("datetime", ignore_index=True)
df["return_1"] = df["close"].pct_change(1)
df["return_5"] = df["close"].pct_change(5)
delta = df["close"].diff()
gain = delta.clip(lower=0).rolling(14).mean()
loss = (-delta.clip(upper=0)).rolling(14).mean()
df["rsi_14"] = 100 - 100 / (1 + gain / loss.replace(0, np.nan))
df["sma_ratio"] = df["close"].rolling(10).mean() / df["close"].rolling(20).mean()
tr = pd.concat([
df["high"] - df["low"],
(df["high"] - df["close"].shift(1)).abs(),
(df["low"] - df["close"].shift(1)).abs(),
], axis=1).max(axis=1)
df["atr_pct"] = tr.rolling(14).mean() / df["close"]
df["vol_ratio"] = df["volume"] / df["volume"].rolling(20).mean()
df["label"] = (df["close"].shift(-horizon) > df["close"]).astype(int)
features = ["return_1", "return_5", "rsi_14", "sma_ratio", "atr_pct", "vol_ratio"]
df.dropna(subset=features + ["label"], inplace=True)
X, y = df[features].values, df["label"].values
split = int(len(X) * 0.8)
X_tr, X_te = X[:split], X[split:]
y_tr, y_te = y[:split], y[split:]
sc = StandardScaler()
X_tr = sc.fit_transform(X_tr)
X_te = sc.transform(X_te)
return X_tr, X_te, y_tr, y_te, features
X_train, X_test, y_train, y_test, feat_names = build_features(df, horizon=5)
print(f"Train: {len(X_train)} | Test: {len(X_test)} | Features: {feat_names}")Train: 384 | Test: 97 | Features: ['return_1', 'return_5', 'rsi_14', 'sma_ratio', 'atr_pct', 'vol_ratio']
6. Model Training and Evaluation
def classification_model(X_train, X_test, y_train, y_test) -> dict:
"""
Train and evaluate multiple classification models.
Core logic
----------
1. Instantiate three classifiers (Logistic Regression, Random Forest,
Gradient Boosting) with fixed random seeds for reproducibility.
2. Fit each model on the training set.
3. Predict class labels and probabilities on the test set.
4. Compute accuracy, ROC-AUC, and a full classification report.
Parameters
----------
X_train, X_test : np.ndarray Scaled feature matrices.
y_train, y_test : np.ndarray Binary labels.
Returns
-------
dict
Model name → {'model', 'predictions', 'probabilities', 'auc', 'report'}
"""
models = {
"Logistic Regression": LogisticRegression(max_iter=1000, random_state=42),
"Random Forest": RandomForestClassifier(n_estimators=100, random_state=42),
"Gradient Boosting": GradientBoostingClassifier(n_estimators=100, random_state=42),
}
results = {}
for name, model in models.items():
model.fit(X_train, y_train)
preds = model.predict(X_test)
proba = model.predict_proba(X_test)[:, 1]
auc = roc_auc_score(y_test, proba)
results[name] = {
"model": model,
"predictions": preds,
"probabilities": proba,
"auc": auc,
"report": classification_report(y_test, preds, output_dict=True),
}
print(f"\n{'='*50}\n{name}\nROC-AUC: {auc:.4f}")
print(classification_report(y_test, preds))
return results
results = classification_model(X_train, X_test, y_train, y_test)
==================================================
Logistic Regression
ROC-AUC: 0.4370
precision recall f1-score support
0 0.62 0.27 0.38 66
1 0.29 0.65 0.40 31
accuracy 0.39 97
macro avg 0.46 0.46 0.39 97
weighted avg 0.52 0.39 0.39 97
==================================================
Random Forest
ROC-AUC: 0.4707
precision recall f1-score support
0 0.67 0.33 0.44 66
1 0.31 0.65 0.42 31
accuracy 0.43 97
macro avg 0.49 0.49 0.43 97
weighted avg 0.55 0.43 0.44 97
==================================================
Gradient Boosting
ROC-AUC: 0.5103
precision recall f1-score support
0 0.67 0.21 0.32 66
1 0.32 0.77 0.45 31
accuracy 0.39 97
macro avg 0.49 0.49 0.39 97
weighted avg 0.55 0.39 0.36 97
8. Visualization — Confusion Matrices
To further evaluate the performance of each classification model, we will visualize their confusion matrices. A confusion matrix provides a detailed breakdown of correct and incorrect predictions for each class, showing true positives, true negatives, false positives, and false negatives.
import matplotlib.pyplot as plt
import seaborn as sns
for name, res in results.items():
cm = confusion_matrix(y_test, res["predictions"])
plt.figure(figsize=(6, 5))
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues",
xticklabels=['Predict 0', 'Predict 1'],
yticklabels=['Actual 0', 'Actual 1'])
plt.title(f'Confusion Matrix - {name}')
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.show()