Feature Engineering: Building the Right Inputs for Your Alpha Model
Feature engineering transforms raw market data into candidate inputs for alpha models. Good features capture economically meaningful relationships. Bad features capture noise.
In ML-based quant finance, feature engineering is the process of transforming raw market data (prices, volumes, fundamentals, alternative data) into candidate inputs for predictive models. The quality of your features determines the ceiling of your model’s performance.
The Feature Engineering Pipeline
Raw Data → Preprocessing → Feature Generation → Feature Selection → Alpha Model
Each stage matters. Garbage in, garbage out at every stage.
Categories of Features
1. Price-Based Features
These derive from price time series:
Returns and log returns:
def compute_returns(prices):
return prices.pct_change()
def compute_log_returns(prices):
return np.log(prices / prices.shift(1))
Momentum features:
def momentum(prices, periods=[20, 60, 120, 252]):
mom = {}
for p in periods:
mom[f'mom_{p}d'] = prices / prices.shift(p) - 1
return pd.DataFrame(mom)
Volatility features:
def rolling_volatility(returns, window=20):
return returns.rolling(window).std() * np.sqrt(252)
2. Fundamental Features
From financial statements:
def fundamental_features(fundamentals):
features = pd.DataFrame()
features['pe_ratio'] = fundamentals['price'] / fundamentals['earnings_per_share']
features['book_to_market'] = fundamentals['book_value'] / fundamentals['market_cap']
features['roe'] = fundamentals['net_income'] / fundamentals['book_value']
features['debt_to_equity'] = fundamentals['total_debt'] / fundamentals['total_equity']
features['revenue_growth'] = fundamentals['revenue'].pct_change(4) # YoY
features['earnings_surprise'] = fundamentals['actual_earnings'] - fundamentals['expected_earnings']
return features
3. Alternative Data Features
Increasingly, alpha comes from alternative data:
def alternative_features(web_data, sentiment_model):
features = pd.DataFrame()
# Web traffic features
features['page_views_z'] = (web_data['page_views'] - web_data['page_views'].mean()) / web_data['page_views'].std()
features['search_volume_z'] = (web_data['search_index'] - web_data['search_index'].mean()) / web_data['search_index'].std()
# Sentiment features
features['news_sentiment'] = sentiment_model.predict(web_data['news_headlines'])
features['social_sentiment'] = sentiment_model.predict(web_data['social_posts'])
return features
Key Principles
Principle 1: Economic Intuition First
Before you run ML, ask: why would this feature predict returns?
A feature with economic intuition is more likely to be robust than one discovered via brute-force search. The feature should reflect a market inefficiency that makes economic sense.
Principle 2: Stationarity
Returns are stationary. Prices are non-stationary (they trend). Most ML models assume stationary inputs.
Always use returns, not prices, as the basis for features. Or difference the price series to make it stationary.
Principle 3: Normalization
Different features have different scales. A P/E ratio might range from 5 to 50. A momentum return might range from -0.5 to +0.5. Normalize features before feeding them to ML models:
from sklearn.preprocessing import StandardScaler
def normalize_features(features):
scaler = StandardScaler()
return pd.DataFrame(
scaler.fit_transform(features),
index=features.index,
columns=features.columns
)
Principle 4: Avoid Data Leakage
Your feature at time t must only use information available at time t. Common leakage sources:
- Using earnings announced after the fiscal quarter end but included in the same-period dataset
- Using intraday prices when your strategy trades on close-to-close returns
- Using future analyst estimates that weren’t available at the time of prediction
Feature Selection
With dozens or hundreds of candidate features, you need to select which ones to use. Methods:
1. Univariate IC screening. Compute IC between each feature and the label. Keep features with highest IC. Fast but ignores feature interactions.
2. Recursive feature elimination. Train model, remove least-important feature, repeat. More accurate but slower.
3. L1 regularization (Lasso). ML model with L1 penalty automatically sets irrelevant feature weights to zero.
from sklearn.linear_model import LassoCV
def lasso_selection(X, y, alphas=None):
model = LassoCV(cv=5, alphas=alphas)
model.fit(X, y)
selected = X.columns[model.coef_ != 0]
return selected, model
Common Pitfalls
Data mining noise. With 500 features and 5 years of daily data, you’ll find features that predict in-sample by pure chance. Always use out-of-sample testing or holdout sets.
Overfitting to market cap. Many features are just proxies for market cap (small caps behave differently from large caps). Include market cap as a control variable or test features separately for small and large caps.
Look-ahead bias in fundamental data. Reported fundamentals are often revised. Using “as-reported” data without adjusting for revisions introduces look-ahead bias. Use point-in-time data or wait for the data to be confirmed.
References
- López de Prado, Advances in Financial Machine Learning, Ch. 3. Wiley, 2018. ISBN: 978-1-119-48208-6.
- Ang, Asset Management, Ch. 9. Oxford University Press, 2014. ISBN: 978-0-19-995932-3.
- Aronson, Evidence-Based Technical Analysis, Ch. 8. Wiley, 2006. ISBN: 978-0-471-75671-9.