Computing Stock Screening Conditions
FinlabDataFrame is FinLab's stock screening tool built on top of Pandas DataFrame, allowing you to build complex screening strategies with concise syntax. If you are not familiar with Pandas, consider reading 10 Minutes to Pandas first.
Quick Reference
| Category | Function | Description |
|---|---|---|
| Basic Operations | +, -, *, /, >, <, &, \|, ~ |
Arithmetic, comparison, logical operations |
| Screening | is_largest(n) |
Select the top n stocks by value |
is_smallest(n) |
Select the bottom n stocks by value | |
average(n) |
Compute n-period moving average | |
| Entry/Exit | hold_until(exit) |
Set entry/exit signals |
is_entry() |
Extract entry points | |
is_exit() |
Extract exit points | |
| Trend | rise(n) |
Whether value is higher than n periods ago |
fall(n) |
Whether value is lower than n periods ago | |
sustain(window, count) |
Condition sustained for multiple periods | |
| Neutralization | neutralize(factors) |
Factor neutralization (regression residuals) |
neutralize_industry() |
Industry neutralization | |
| Other | quantile_row(q) |
Cross-sectional quantile |
groupby_category() |
Group by industry | |
index_str_to_date() |
Convert index format |
Differences from Pandas DataFrame
| Difference | Description |
|---|---|
| Additional Functions | Provides screening-specific functions like is_largest(), sustain(), hold_until(), etc. |
| Auto-alignment | Automatically aligns data of different frequencies (daily, monthly, quarterly) before operations, no manual reindex needed |
Creating a FinlabDataFrame
Method 1: Using finlab.data.get()
Data obtained via finlab.data.get() is automatically in FinlabDataFrame format.
Method 2: Converting a Pandas DataFrame
import pandas as pd
from finlab.dataframe import FinlabDataFrame
df = pd.DataFrame()
df_finlab = FinlabDataFrame(df)
Operators
Similar to pd.DataFrame, operations are performed element-wise at corresponding positions. The following operators are supported:
| Type | Operator | Description |
|---|---|---|
| Arithmetic | + |
Addition |
- |
Subtraction | |
* |
Multiplication | |
/ |
Division | |
| Comparison | > |
Greater than |
>= |
Greater than or equal | |
== |
Equal | |
< |
Less than | |
<= |
Less than or equal | |
| Logical | & |
AND |
\| |
OR | |
~ |
NOT |
Example: Select stocks with closing price between 10 and 100
from finlab import data
close = data.get('price:收盤價')
# Closing price between 10 and 100
between_10_100 = (close > 10) & (close < 100)
Automatic frequency alignment
When operating on data of different frequencies (daily, monthly, quarterly), FinlabDataFrame handles alignment automatically:
| Alignment Step | Method |
|---|---|
| Dates (index) | Union of dates; missing values filled with the most recent data (forward fill) |
| Stocks (columns) | Intersection; stocks without corresponding data are excluded |
Example: Combining daily stock prices with quarterly financial data
from finlab import data
# Get FinlabDataFrame
close = data.get('price:收盤價')
roa = data.get('fundamental_features:ROA稅後息前')
# Intersection of two screening conditions
cond1 = close > 37
cond2 = roa > 0
cond_1_2 = cond1 & cond2
The diagram below shows the signal for stock 1101 (Taiwan Cement). You can see that even though cond1 (daily) and cond2 (quarterly) have different frequencies, they can be combined directly:

Why does the combined data have fewer rows?
Although dates are unioned, the actual data only retains the overlapping time range. For example: if cond1 starts from 2010 and cond2 starts from 2013, the combined data starts from 2013.
Screening Functions
average(n) - Moving Average
Compute the n-period moving average.
from finlab import data
close = data.get('price:收盤價')
sma = close.average(10)
cond = close > sma # Price above 10-day moving average
Visual inspection:
import matplotlib.pyplot as plt
close.loc['2021', '2330'].plot()
sma.loc['2021', '2330'].plot()
cond.loc['2021', '2330'].mul(20).add(500).plot()
plt.legend(['close', 'sma', 'cond'])

is_largest(n) - Top n Stocks by Value
Select the top n stocks by value each day.

from finlab import data
roa = data.get('fundamental_features:ROA稅後息前')
good_stocks = roa.is_largest(10) # Top 10 ROA stocks daily
Output example:
| date | 000116 | 000538 | 000616 | 000700 | 000779 |
|---|---|---|---|---|---|
| 2013-Q1 | False | False | True | False | False |
| 2013-Q2 | False | False | False | False | True |
| 2013-Q3 | False | True | False | False | False |
| 2013-Q4 | False | False | False | True | False |
| 2014-Q1 | True | False | False | False | False |
is_smallest(n) - Bottom n Stocks by Value
Select the bottom n stocks by value each day.
from finlab import data
pb = data.get('price_earning_ratio:股價淨值比')
cheap_stocks = pb.is_smallest(10) # Bottom 10 by P/B ratio
Entry/Exit Signals (hold_until)
The most important strategy syntax for setting entry/exit logic:
from finlab import data
close = data.get('price:收盤價')
buy = close > close.average(5)
sell = close < close.average(20)
position = buy.hold_until(sell)
When the buy signal is True, buy and hold until the sell signal becomes True.

Advanced Parameters
| Parameter | Description | Example |
|---|---|---|
| stop_loss | Stop-loss percentage | buy.hold_until(sell, stop_loss=0.1) |
| take_profit | Take-profit percentage | buy.hold_until(sell, take_profit=0.2) |
| nstocks_limit | Maximum number of stocks held (sector rotation) | buy.hold_until(sell, nstocks_limit=5) |
| rank | Stock selection priority (positive = descending) | buy.hold_until(sell, rank=-pb) |
Complete Strategy Example
Enter when price > 20-day MA, exit when price < 60-day MA, hold at most 10 stocks, prioritize stocks with lower P/B ratio.
from finlab import data
from finlab.backtest import sim
close = data.get('price:收盤價')
pb = data.get('price_earning_ratio:股價淨值比')
entries = close > close.average(20)
exits = close < close.average(60)
position = entries.hold_until(exits, nstocks_limit=10, rank=-pb)
sim(position)
Value Change Trends
rise(n) - Value is Rising
Check whether the current value is higher than the value n periods ago.

fall(n) - Value is Falling
Check whether the current value is lower than the value n periods ago.
sustain(window, count) - Condition Sustained
Check whether the condition is met at least count times within a window period.
from finlab import data
close = data.get('price:收盤價')
close.rise().sustain(2) # Rising for 2 consecutive days
close.rise().sustain(3, 2) # Rising on 2 out of 3 days
Factor Neutralization
In multi-factor screening, factors may be correlated. For example, small-cap stocks typically have lower P/E ratios, so a "low P/E" factor may simply be selecting small-cap stocks. Neutralization uses cross-sectional regression to remove the influence of specified factors, leaving the pure alpha signal.
neutralize(factors) - Factor Neutralization
Perform cross-sectional regression against specified factors and return residuals (values after removing the factor's influence).
from finlab import data
factor = data.get('price_earning_ratio:本益比')
size = data.get('etl:market_value')
# Remove market cap influence
neutralized = factor.neutralize(size)
Supports neutralizing against multiple factors simultaneously:
beta = data.get('fundamental_features:Beta')
# Pass multiple factors as a list
neutralized = factor.neutralize([size, beta])
# Or use a dict for naming (convenient for debugging)
neutralized = factor.neutralize({
'size': size,
'size2': size ** 2,
'beta': beta,
})
neutralize_industry() - Industry Neutralization
Perform cross-sectional regression using industry dummy variables and return residuals. Automatically uses the security_categories industry classification.
from finlab import data
factor = data.get('price_earning_ratio:本益比')
# Remove industry effects
neutralized = factor.neutralize_industry()
You can also use custom industry classifications:
import pandas as pd
custom_cats = pd.DataFrame({
'stock_id': ['2330', '2317', '1101'],
'category': ['半導體', '電子', '水泥']
})
neutralized = factor.neutralize_industry(categories=custom_cats)
Tip
Neutralized factors can be directly used with rank(), is_largest(), and other screening functions. The selected stocks will better reflect the factor's own effect rather than being dominated by market cap or industry.
Other Useful Functions
quantile_row(q) - Cross-sectional Quantile
Get the quantile value across stocks for each day.
from finlab import data
data.get('price:收盤價').quantile_row(0.9) # 90th percentile of stock prices daily
index_str_to_date() - Index Format Conversion
Convert monthly/quarterly report string indices to their filing deadline dates.
| Before | After |
|---|---|
| 2022-M1 (monthly revenue) | 2022-01-10 |
| 2022-Q1 (quarterly report) | 2022-05-15 |
from finlab import data
data.get('monthly_revenue:當月營收').index_str_to_date()
data.get('financial_statement:現金及約當現金').index_str_to_date()
groupby_category() - Group by Industry
Group data by industry, similar to pd.DataFrame.groupby().
from finlab import data
pe = data.get('price_earning_ratio:股價淨值比')
pe.groupby_category().mean()['半導體'].plot() # Average P/B ratio for semiconductor industry
is_entry() - Extract Entry Points
Extract entry time points from position signals.
from finlab import data
position = data.get('price:收盤價').is_largest(10)
entry_signals = position.is_entry()
is_exit() - Extract Exit Points
Extract exit time points from position signals.