Guides
Machine learning
On this page6
This page takes you through stock selection with machine learning: merge features → generate labels → train a model → backtest. finlab.ml shapes the data the way models expect and wraps qlib's LightGBM, XGBoost, CatBoost and other models behind one interface.
Install
Install FinLab with its Qlib dependencies (including TA-Lib and model libraries on supported platforms):
pip install 'finlab[qlib]'finlab[qlib] includes LightGBM, XGBoost, CatBoost, and other model dependencies; PyTorch is also installed on supported platforms.
Install or update model libraries individually
See each library's official instructions:
Build an ML strategy in four steps
Merge features
finlab.ml.feature.combine() merges features from multiple sources (technical, fundamental, custom) into one table and resamples them with resample. This merges the P/B and P/E ratios of S&P 500 stocks, computed from SEC filings, into a weekly feature set:
from finlab import data
from finlab.ml import feature as mlf
data.set_market('us')
with data.us_universe(index='SP500'):
raw_close = data.get('price:close') # unadjusted price for valuation
market_value = data.get('etl:us_market_value')
eps = data.get('us_income_statement:eps_diluted')
equity = data.get('us_balance_sheet:total_stockholders_equity')
ttm_eps = eps.rolling(4).sum() # Trailing-12-month EPS
features = mlf.combine(
{
# Ratios only where the denominator is positive
'pb': market_value / equity[equity > 0],
'pe': raw_close / ttm_eps[ttm_eps > 0],
},
resample='W',
)
features.head()The result is indexed by (datetime, instrument), for example (2024-01-07, 'AAPL'), with one column per feature: pb and pe.
Generate labels
Labels are the answers the model learns to predict, such as each stock's return over the next week. The functions in finlab.ml.label take the features' index and return a pd.Series with the same index, so labels pair with features row by row. Use the same resample as in combine() so the dates line up:
from finlab.ml import label as mll
# Next-week returns as labels
labels = mll.return_percentage(features.index, resample='W', period=1)For other labels (day-trading return, MAE/MFE, excess return), see Choose a different label below.
Split the data and train a model
Split by date: train on data before 2023 and predict afterwards. Train only on rows where both the features and the label are present:
import finlab.ml.qlib as q
complete = features.notna().all(axis=1) & labels.notna()
is_train = features.index.get_level_values('datetime') < '2023-01-01'
X_train = features[complete & is_train]
y_train = labels[complete & is_train]
X_test = features[~is_train].dropna()
model = q.LGBModel()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)model.predict() returns a table of predictions with one row per date and one column per stock.
Backtest
Pick the 50 stocks with the highest predictions each period and backtest them with sim to get the strategy's returns and risk metrics:
from finlab.backtest import sim
position = y_pred.is_largest(50)
sim(position, resample='4W')Add technical indicator features
finlab.ml.feature exposes ta_names and ta for generating TA-Lib indicator features.
Generate indicator names: ta_names
ta_names generates a list of TA-Lib indicator names, one per parameter configuration.
- n: random parameter configurations per indicator.
n=10yields up to 10 variants per TA-Lib indicator; indicators without parameters appear once.
from finlab.ml import feature as mlf
mlf.ta_names(n=1)['talib.HT_DCPERIOD__real__',
'talib.HT_DCPHASE__real__',
'talib.HT_PHASOR__quadrature__',
'talib.HT_PHASOR__inphase__',
'talib.HT_SINE__sine__',
'talib.HT_SINE__leadsine__'
...
]Compute indicator values: ta
ta computes the actual values for a list of names produced by ta_names.
resample: optional; resamples the computed indicator values to a given frequency.
from finlab.ml import feature as mlf
mlf.ta(
[
'talib.HT_DCPERIOD__real__',
'talib.HT_DCPHASE__real__',
'talib.HT_PHASOR__quadrature__',
],
resample='W',
)With resample='W' the dates move to the weekly period labels (Sundays), one row per symbol per week.
Merge technical indicators into one feature set
Pass the output of ta() to combine(). This draws three random parameter sets each for RSI, MACD and ATR:
from finlab import data
from finlab.ml import feature as mlf
data.set_market('us')
# Three random parameter sets each for RSI, MACD and ATR
names = [
name
for name in mlf.ta_names(n=3)
if name.startswith(('talib.RSI__', 'talib.MACD__', 'talib.ATR__'))
]
with data.us_universe(index='SP500'):
technical = mlf.combine({'talib': mlf.ta(names)}, resample='W')The result has the same (datetime, instrument) index with one column per indicator output, named like talib.RSI__real__timeperiod__37__ and talib.MACD__macdsignal__fastperiod__48__slowperiod__104__signalperiod__36__. Indicators are computed from the prices of the active market and universe.
Choose a different label
finlab.ml.label provides various return and risk labels:
| Function | Label | resample | period |
|---|---|---|---|
daytrading_percentage | Return from the next trading day's open to its close (a day-trading target) | Yes, default 'D' | No |
return_percentage | Return over the next N periods, for medium- to long-term performance | Yes | Yes |
maximum_adverse_excursion | MAE: maximum adverse movement during the holding period | Not supported yet | Yes |
maximum_favorable_excursion | MFE: maximum favorable movement during the holding period | Not supported yet | Yes |
excess_over_median | Excess return over the market-wide median return for the same period | Yes | Yes |
excess_over_mean | Excess return over the market-wide mean return for the same period | Yes | Yes |
resample: must match theresampleused incombineto align time periods.period: the number of future periods; the length of a period is set byresample.
from finlab.ml import label as mll
# Labels for the weekly features built above; use the same resample
label = mll.daytrading_percentage(features.index, resample='W')
label = mll.return_percentage(features.index, resample='W', period=1)
label = mll.maximum_adverse_excursion(features.index, period=1)
label = mll.maximum_favorable_excursion(features.index, period=1)
label = mll.excess_over_median(features.index, resample='W', period=1)
label = mll.excess_over_mean(features.index, resample='W', period=1)Each label is a pd.Series with the same (datetime, instrument) index as the features. Ensure the index and market settings are correct; labels can be combined with features directly for model training.
Switch to another model
WrapperModel adapts LightGBM, XGBoost, CatBoost, linear models, TabNet, DNN and others into a uniform fit/predict interface, so you can focus on features and strategy without getting bogged down in model details. Every model is used the same way; only the line that creates it changes:
import finlab.ml.qlib as q
model = q.XGBModel() # any model from the table below
model.fit(X_train, y_train)
y_pred = model.predict(X_test)| Model | Wraps |
|---|---|
LGBModel | LightGBM |
XGBModel | XGBoost |
DEnsmbleModel | Double Ensemble |
CatBoostModel | CatBoost |
LinearModel | A linear model |
TabnetModel | TabNet |
DNNModel | A deep neural network |
Try several models: get_models
get_models() retrieves the available models, making multi-model experiments easy:
import finlab.ml.qlib as q
# Get all available models
models = q.get_models()
# Print all model names
print(list(models.keys()))
# Select and instantiate a model, e.g., LightGBM
model = models['LGBModel']()
# Assuming X_train, y_train, X_test are already prepared
model.fit(X_train, y_train)
# Predict using the trained model
y_pred = model.predict(X_test)Next steps
- ML strategy workflow The full path from features to a live ML strategy.
- ML stock-selection example A complete ML strategy you can run as is.
- Factor analysis Check which factors work before you train.
- finlab.ml Full parameters of the feature, label and model functions.
Was this page helpful?