Machine Learning Techniques for Trading › Complete Worked Example

Michael BrenndoerferJanuary 2, 20264 min read
Quantitative FinanceAll chapters
Part 13 of 15Machine Learning and Alternative Data
Chapter 69Machine Learning Techniques for Trading › Complete Worked Example
Reading section 8 of 10 · Complete Worked Example · 4 minSection 8 of 10 · 4 min

Book contents

Section 8 of 10 · 4 min read

Machine Learning Techniques for Trading › Complete Worked ExampleOne section · Sections in this chapter8 / 10 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Let's bring together the concepts from this chapter in a complete example that demonstrates the full workflow from data preparation through model evaluation.

In[40]:
Code
# Complete ML pipeline for return prediction
import warnings

import numpy as np
import pandas as pd

warnings.filterwarnings("ignore")

# Generate more realistic synthetic data
np.random.seed(42)
n_days = 1000

# Simulate price with some predictable patterns
price = [100]
for i in range(n_days - 1):
    momentum_effect = 0.001 * (
        price[-1] / np.mean(price[-min(20, len(price)) :]) - 1
    )
    mean_rev_effect = -0.0005 * (
        price[-1] / np.mean(price[-min(50, len(price)) :]) - 1
    )
    noise = 0.02 * np.random.randn()
    ret = momentum_effect + mean_rev_effect + 0.0003 + noise
    price.append(price[-1] * (1 + ret))

price = np.array(price)
volume = np.random.lognormal(15, 0.5, n_days)

# Create DataFrame and features
data = pd.DataFrame({"close": price, "volume": volume})
data["return"] = data["close"].pct_change()

# Feature engineering
data["momentum_5"] = data["close"].pct_change(5)
data["momentum_10"] = data["close"].pct_change(10)
data["momentum_20"] = data["close"].pct_change(20)
data["vol_10"] = data["return"].rolling(10).std()
data["vol_20"] = data["return"].rolling(20).std()
data["vol_ratio"] = data["vol_10"] / data["vol_20"]
data["ma_ratio"] = data["close"] / data["close"].rolling(20).mean()
data["zscore"] = (data["close"] - data["close"].rolling(20).mean()) / data[
    "close"
].rolling(20).std()
data["volume_ratio"] = data["volume"] / data["volume"].rolling(10).mean()

# Target: next-day direction
data["target"] = (data["return"].shift(-1) > 0).astype(int)
data = data.dropna()

feature_cols = [
    "momentum_5",
    "momentum_10",
    "momentum_20",
    "vol_10",
    "vol_20",
    "vol_ratio",
    "ma_ratio",
    "zscore",
    "volume_ratio",
]
In[41]:
Code
from sklearn.base import clone

# Prepare data
X = data[feature_cols].values
y = data["target"].values

# Scale features
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# Time series cross-validation with multiple models
tscv = TimeSeriesSplit(n_splits=5)

models = {
    "Logistic Regression": LogisticRegression(C=0.1, max_iter=1000),
    "Random Forest": RandomForestClassifier(
        n_estimators=100, max_depth=4, random_state=42
    ),
    "Gradient Boosting": GradientBoostingClassifier(
        n_estimators=100, learning_rate=0.1, max_depth=3, random_state=42
    ),
}

results = {name: {"accuracy": [], "auc": []} for name in models}

for train_idx, test_idx in tscv.split(X_scaled):
    X_train, X_test = X_scaled[train_idx], X_scaled[test_idx]
    y_train, y_test = y[train_idx], y[test_idx]

    for name, model in models.items():
        # Clone model for fresh training
        m = clone(model)

        m.fit(X_train, y_train)
        y_pred = m.predict(X_test)
        y_prob = m.predict_proba(X_test)[:, 1]

        results[name]["accuracy"].append(accuracy_score(y_test, y_pred))
        results[name]["auc"].append(roc_auc_score(y_test, y_prob))
Out[42]:
Console
Cross-Validation Results Summary
============================================================
Model                     Accuracy           ROC-AUC           
------------------------------------------------------------
Logistic Regression       0.499 ± 0.025     0.503 ± 0.014
Random Forest             0.507 ± 0.021     0.508 ± 0.049
Gradient Boosting         0.517 ± 0.048     0.506 ± 0.048
Out[43]:
Visualization
Box plot of validation accuracy for logistic regression, random forest, and gradient boosting. A dashed horizontal reference marks 0.5. Logistic regression is centered below the line, the other two have medians above it, and all three extend below it in some folds.
Accuracy across walk-forward validation folds. Random forest and gradient boosting have medians above 0.5, while logistic regression's median is below 0.5; every model also has at least one fold below the reference line.
Out[44]:
Visualization
Box plot of validation ROC-AUC for logistic regression, random forest, and gradient boosting. A dashed line marks 0.5. Logistic regression clusters near it, random forest has the highest median with a low outlier, and gradient boosting has the widest spread.
ROC-AUC across walk-forward validation folds. Logistic regression is centered near the 0.5 reference, random forest has the highest median but includes a low fold, and gradient boosting spans the broadest range; the distributions do not demonstrate a consistent edge in every period.
In[45]:
Code
# Placeholder cell to ensure proper notebook structure
pass

The cross-validation results are mixed across folds. Random forest and gradient boosting have median scores modestly above the reference values, but logistic regression and several individual folds fall at or below them. The variation across periods is evidence of regime sensitivity, not a consistent edge; trading usefulness would still need to be established after costs on untouched data.