Machine Learning Techniques for Trading › Feature Engineering for Finance
Quantitative FinanceAll chapters 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.
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))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.027156The 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:
# 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()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.6331Each 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:
# 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()
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:
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)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

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.