Skip to content

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:

imageconds

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'])

sma

is_largest(n) - Top n Stocks by Value

Select the top n stocks by value each day.

is-largest

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.

Screen-Shot-2021-10-26-at-6-35-05-AM

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)

rise(n) - Value is Rising

Check whether the current value is higher than the value n periods ago.

Screen-Shot-2021-10-26-at-6-43-41-AM

from finlab import data
data.get('price:收盤價').rise(10)  # Higher than 10 days ago?

fall(n) - Value is Falling

Check whether the current value is lower than the value n periods ago.

from finlab import data
data.get('price:收盤價').fall(10)  # Lower than 10 days 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
pbmean

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.

from finlab import data

position = data.get('price:收盤價').is_largest(10)
exit_signals = position.is_exit()