Machine Learning Techniques for Trading › Feature Engineering for Finance

Michael BrenndoerferJanuary 2, 20265 min read
Quantitative FinanceAll chapters
Part 13 of 15Machine Learning and Alternative Data
Chapter 69Machine Learning Techniques for Trading › Feature Engineering for Finance
Reading section 4 of 10 · Feature Engineering for Finance · 5 minSection 4 of 10 · 5 min

Book contents

Section 4 of 10 · 5 min read

Machine Learning Techniques for Trading › Feature Engineering for FinanceOne section · Sections in this chapter4 / 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

Raw market data, such as prices, volumes, and order book snapshots, must be transformed into informative features before machine learning algorithms can extract predictive patterns. Feature engineering is often the most impactful step in building a successful trading model, and it draws heavily on domain knowledge from the strategies we explored in earlier chapters.

From Raw Data to Features

The transformation from prices to features typically begins with returns rather than price levels. As we discussed in Part III when examining stylized facts of financial returns, prices are non-stationary while returns are approximately stationary. This property better fits machine learning models that assume the data distribution doesn't change. Using prices directly would violate this assumption, as a stock trading at $100 today might trade at $200 in five years but daily returns tend to have similar statistical properties across time.

In[15]:
Code
import numpy as np
import pandas as pd

# Generate synthetic price series
np.random.seed(42)
n_days = 500
price_data = 100 * np.exp(np.cumsum(0.0005 + 0.02 * np.random.randn(n_days)))
volume_data = np.random.lognormal(mean=15, sigma=0.5, size=n_days)

df = pd.DataFrame({"close": price_data, "volume": volume_data})

# Calculate basic features
df["return"] = df["close"].pct_change()
df["log_return"] = np.log(df["close"] / df["close"].shift(1))
Out[16]:
Console
Sample of derived features:
          close    return  log_return
495  147.123179  0.011342    0.011278
496  144.174629 -0.020041   -0.020245
497  143.698663 -0.003301   -0.003307
498  141.274687 -0.016868   -0.017012
499  137.489855 -0.026791   -0.027156

The log returns approximate the percentage change but offer better statistical properties, such as additivity over time, which is beneficial for modeling. When compounding returns over multiple periods, log returns can simply be summed, whereas simple returns require multiplication. This mathematical convenience simplifies many calculations in quantitative finance.

Technical Indicators as Features

The technical indicators we developed in Part V translate directly into ML features:

In[17]:
Code
# Moving averages and momentum features
df["sma_10"] = df["close"].rolling(10).mean()
df["sma_50"] = df["close"].rolling(50).mean()
df["momentum_10"] = df["close"] / df["close"].shift(10) - 1
df["momentum_20"] = df["close"] / df["close"].shift(20) - 1

# Volatility features
df["volatility_20"] = df["return"].rolling(20).std()
df["volatility_60"] = df["return"].rolling(60).std()
df["volatility_ratio"] = df["volatility_20"] / df["volatility_60"]

# Mean reversion features
df["distance_from_sma"] = (df["close"] - df["sma_50"]) / df["sma_50"]
df["zscore_20"] = (df["close"] - df["close"].rolling(20).mean()) / df[
    "close"
].rolling(20).std()

# Volume features
df["volume_sma_10"] = df["volume"].rolling(10).mean()
df["volume_ratio"] = df["volume"] / df["volume_sma_10"]

# RSI (Relative Strength Index)
delta = df["close"].diff()
gain = delta.where(delta > 0, 0).rolling(14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
rs = gain / loss
df["rsi"] = 100 - (100 / (1 + rs))

# Drop rows with NaN values from rolling calculations
df_clean = df.dropna().copy()
Out[18]:
Console
Feature Statistics:
       momentum_10  volatility_20  distance_from_sma  zscore_20  volume_ratio  \
count     440.0000       440.0000           440.0000   440.0000      440.0000   
mean        0.0140         0.0195             0.0275     0.2656        1.0003   
std         0.0585         0.0036             0.0622     1.2669        0.4881   
min        -0.1238         0.0111            -0.1429    -2.7104        0.2885   
25%        -0.0256         0.0168            -0.0132    -0.7677        0.6443   
50%         0.0070         0.0197             0.0236     0.3133        0.8891   
75%         0.0490         0.0221             0.0626     1.2321        1.2450   
max         0.2145         0.0272             0.1766     3.5607        3.4490   

            rsi  
count  440.0000  
mean    53.4437  
std     15.1514  
min     14.1159  
25%     43.1925  
50%     52.8520  
75%     63.0202  
max     89.6331

Each feature captures a different aspect of market behavior. Momentum features measure recent price trends, volatility features quantify uncertainty, mean reversion features identify deviations from typical levels, and volume features reflect trading activity and liquidity.

Feature Selection and Importance

With potentially hundreds of candidate features, selecting the most informative subset is essential. Including irrelevant or redundant features increases noise, computational cost, and overfitting risk.

Correlation analysis identifies redundant features that provide overlapping information:

In[19]:
Code
# Calculate correlation matrix for selected features
feature_subset = [
    "momentum_10",
    "momentum_20",
    "volatility_20",
    "volatility_ratio",
    "distance_from_sma",
    "zscore_20",
    "volume_ratio",
    "rsi",
]
corr_matrix = df_clean[feature_subset].corr()
Out[20]:
Visualization
Annotated heatmap of pairwise correlations among eight financial features, with strong positive clusters among momentum, RSI, z-score, distance-from-average, and volatility measures.
Correlation matrix for eight financial features. Several momentum and mean-reversion features have correlations above 0.8, while the volatility pair correlates at about 0.83; these clusters flag potential redundancy for feature selection or regularization.

The correlation matrix reveals strong relationships between momentum indicators and between volatility measures. High correlation (multicollinearity) implies redundancy, suggesting that the model might not need all these features to capture the underlying signal.

Model-based feature importance provides another perspective, revealing which features contribute most to predictions:

In[21]:
Code
from sklearn.ensemble import RandomForestRegressor

# Create target: next-day return
df_clean["target"] = df_clean["return"].shift(-1)
df_clean = df_clean.dropna()

# Define features to evaluate
feature_subset = [
    "momentum_10",
    "momentum_20",
    "volatility_20",
    "volatility_ratio",
    "distance_from_sma",
    "zscore_20",
    "volume_ratio",
    "rsi",
]

# Prepare features and target
X_features = df_clean[feature_subset].values
y_target = df_clean["target"].values

# Fit random forest to assess feature importance
rf_importance = RandomForestRegressor(
    n_estimators=100, max_depth=5, random_state=42
)
rf_importance.fit(X_features, y_target)

importance_df = pd.DataFrame(
    {
        "Feature": feature_subset,
        "Importance": rf_importance.feature_importances_,
    }
).sort_values("Importance", ascending=False)
Out[22]:
Console
Feature Importance Rankings:
  volume_ratio        : 0.1552
  rsi                 : 0.1460
  distance_from_sma   : 0.1327
  zscore_20           : 0.1259
  volatility_20       : 0.1199
  volatility_ratio    : 0.1145
  momentum_10         : 0.1089
  momentum_20         : 0.0968
Out[23]:
Visualization
Horizontal bar chart ranking eight financial features, with volume ratio highest at about 0.16, RSI second at about 0.15, and momentum 20 lowest below 0.10.
Impurity-based feature importance from the fitted random forest return model. In this sample, volume ratio and RSI rank highest, while the two momentum features rank lowest; correlated inputs can redistribute impurity importance across related variables.

The feature importance rankings guide feature selection, but should be interpreted cautiously. Importance measures can be unstable, especially when features are correlated. Cross-validating feature selection choices tests whether rankings persist across folds.