跳轉到

finlab.dataframe

FinlabDataFrame 是 pandas DataFrame 的擴充類別,專為股票策略開發設計。

使用情境

  • 自動對齊不同頻率的資料(日、週、月、季)
  • 快速進行選股操作(排名、篩選前 N 名)
  • 計算移動平均與技術指標
  • 管理進出場訊號(持倉至停損/停利)
  • 產業中性化(neutralize)

快速範例

自動對齊與選股

from finlab import data

# data.get() 回傳的都是 FinlabDataFrame
close = data.get('price:收盤價')  # 日頻
revenue = data.get('monthly_revenue:當月營收')  # 月頻

# 自動對齊日期(revenue 會自動填補到日頻)
position = (close > close.average(20)) & (revenue > 1e8)

排名與篩選

from finlab import data

close = data.get('price:收盤價')
marketcap = data.get('etl:market_value')

# 選擇市值最小的 30 檔
small_cap = marketcap.is_smallest(30)

# 選擇漲幅最大的 20 檔
momentum = (close / close.shift(20)).is_largest(20)

# 組合策略
position = small_cap & momentum

持倉管理

from finlab import data
from finlab.backtest import sim

close = data.get('price:收盤價')
position = close > close.average(20)

# 持倉至停損 10% 或停利 20%
position_with_stops = position.hold_until(stop_loss=0.1, stop_profit=0.2)

# 回測
report = sim(position_with_stops, resample='M')
report.display()

FinlabDataFrame vs pandas DataFrame

特性 pandas DataFrame FinlabDataFrame
基本操作 ✅ 完全支援 ✅ 完全支援(繼承 pandas)
自動對齊 ❌ 需手動處理 ✅ 自動對齊不同頻率資料
選股方法 ❌ 需自行撰寫 ✅ 內建 is_largest(), is_smallest()
技術指標 ❌ 需額外套件 ✅ 內建 average(), rise(), fall()
持倉管理 ❌ 需自行撰寫 ✅ 內建 hold_until(), exit_when()
產業分析 ❌ 需自行撰寫 ✅ 內建 industry_rank(), neutralize_industry()

API Reference

FinlabDataFrame

finlab.dataframe.FinlabDataFrame

FinlabDataFrame(*args, **kwargs)

Bases: DataFrame

回測語法糖 除了使用熟悉的 Pandas 語法外,我們也提供很多語法糖,讓大家開發程式時,可以用簡易的語法完成複雜的功能,讓開發策略更簡潔! 我們將所有的語法糖包裹在 FinlabDataFrame 中,用起來跟 pd.DataFrame 一樣,但是多了很多功能! 只要使用 finlab.data.get() 所獲得的資料,皆為 FinlabDataFrame 格式, 接下來我們就來看看, FinlabDataFrame 有哪些好用的語法糖吧!

當資料日期沒有對齊(例如: 財報 vs 收盤價 vs 月報)時,在使用以下運算符號: +, -, *, /, >, >=, ==, <, <=, &, |, ~, 不需要先將資料對齊,因為 FinlabDataFrame 會自動幫你處理,以下是示意圖。

steps

以下是範例:cond1cond2 分別為「每天」,和「每季」的資料,假如要取交集的時間,可以用以下語法:

from finlab import data
# 取得 FinlabDataFrame
close = data.get('price:收盤價')
roa = data.get('fundamental_features:ROA稅後息前')

# 運算兩個選股條件交集
cond1 = close > 37
cond2 = roa > 0
cond_1_2 = cond1 & cond2
擷取 1101 台泥 的訊號如下圖,可以看到 cond1cond2 訊號的頻率雖然不相同, 但是由於 cond1cond2FinlabDataFrame,所以可以直接取交集,而不用處理資料頻率對齊的問題。
imageconds

總結來說,FinlabDataFrame 與一般 dataframe 唯二不同之處: 1. 多了一些 method,如df.is_largest(), df.sustain()...等。 2. 在做四則運算、不等式運算前,會將 df1、df2 的 index 取聯集,column 取交集。

average

average(n)

取 n 筆移動平均

若股票在時間窗格內,有 N/2 筆 NaN,則會產生 NaN。 Args: n (positive-int): 設定移動窗格數。 Returns: (pd.DataFrame): data Examples: 股價在均線之上

from finlab import data
close = data.get('price:收盤價')
sma = close.average(10)
cond = close > sma
只需要簡單的語法,就可以將其中一部分的訊號繪製出來檢查:
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

deadline

deadline()

財務索引轉換成公告截止日

將財務季報 (ex:2022Q1) 從文字格式轉為公告截止日的datetime格式, 通常使用情境為對不同週期的dataframe做reindex,常用於以公告截止日作為訊號產生日。 Returns: (pd.DataFrame): data Examples:

data.get('financial_statement:現金及約當現金').deadline()
data.get('monthly_revenue:當月營收').deadline()

fall

fall(n=1)

數值下降中

取是否比前第n筆低,若符合條件的值則為True,反之為False。 Screen-Shot-2021-10-26-at-6-43-41-AM Args: n (positive-int): 設定比較前第n筆低。 Returns: (pd.DataFrame): data Examples: 收盤價是否低於10日前股價

from finlab import data
data.get('price:收盤價').fall(10)

groupby_category

groupby_category()

資料按產業分群

類似 pd.DataFrame.groupby()的處理效果。 Returns: (pd.DataFrame): data Examples: 半導體平均股價淨值比時間序列

from finlab import data
pe = data.get('price_earning_ratio:股價淨值比')
pe.groupby_category().mean()['半導體'].plot()
pbmean

全球 2020 量化寬鬆加上晶片短缺,使得半導體股價淨值比衝高。

hold_until

hold_until(exit, nstocks_limit=None, stop_loss=-np.inf, take_profit=np.inf, trade_at='close', rank=None)

訊號進出場

這大概是所有策略撰寫中,最重要的語法糖,上述語法中 entries 為進場訊號,而 exits 是出場訊號。所以 entries.hold_until(exits) ,就是進場訊號為 True 時,買入並持有該檔股票,直到出場訊號為 True 則賣出。 Screen-Shot-2021-10-26-at-6-35-05-AM 此函式有很多細部設定,可以讓你最多選擇 N 檔股票做輪動。另外,當超過 N 檔進場訊號發生,也可以按照客制化的排序,選擇優先選入的股票。最後,可以設定價格波動當輪動訊號,來增加出場的時機點。

PARAMETER DESCRIPTION
exit

出場訊號。

TYPE: Dataframe

nstocks_limit (int)`

輪動檔數上限,預設為None。

stop_loss

價格波動輪動訊號,預設為None,不生成輪動訊號。範例:0.1,代表成本價下跌 10% 時產生出場訊號。

TYPE: float DEFAULT: -inf

take_profit

價格波動輪動訊號,預設為None,不生成輪動訊號。範例:0.1,代表成本價上漲 10% 時產生出場訊號。

TYPE: float DEFAULT: inf

trade_at

價格波動輪動訊號參考價,預設為'close'。可選 closeopen

TYPE: str DEFAULT: 'close'

rank

當天進場訊號數量超過 nstocks_limit 時,以 rank 數值越大的股票優先進場。

TYPE: Dataframe DEFAULT: None

RETURNS DESCRIPTION
DataFrame

data

Examples:

價格 > 20 日均線入場, 價格 < 60 日均線出場,最多持有10檔,超過 10 個進場訊號,則以股價淨值比小的股票優先選入。

from finlab import data
from finlab.backtest import sim

close = data.get('price:收盤價')
pb = data.get('price_earning_ratio:股價淨值比')

sma20 = close.average(20)
sma60 = close.average(60)

entries = close > sma20
exits = close < sma60

#pb前10小的標的做輪動
position = entries.hold_until(exits, nstocks_limit=10, rank=-pb)
sim(position)

index_str_to_date

index_str_to_date()

財務月季報索引格式轉換

將以下資料的索引轉換成datetime格式:

財務季報 (ex:2022-Q1) 從文字格式轉為財報電子檔資料上傳日。

通常使用情境為對不同週期的dataframe做reindex,常用於以公告截止日作為訊號產生日。

RETURNS DESCRIPTION
DataFrame

data

Examples:

data.get('financial_statement:現金及約當現金').index_str_to_date()

industry_rank

industry_rank(categories=None)

計算產業 ranking 排名,0 代表產業內最低,1 代表產業內最高 Args: categories (list of str): 欲考慮的產業,ex: ['貿易百貨', '雲端運算'],預設為全產業,請參考 data.get('security_industry_themes') 中的產業項目。 Examples: 本意比產業排名分數

from finlab import data

pe = data.get('price_earning_ratio:本益比')
pe_rank = pe.industry_rank()
print(pe_rank)

is_entry

is_entry()

進場點

取進場訊號點,若符合條件的值則為True,反之為False。 Returns: (pd.DataFrame): data Examples: 策略為每日收盤價前10高,取進場點。

from finlab import data
data.get('price:收盤價').is_largest(10).is_entry()

is_exit

is_exit()

出場點

取出場訊號點,若符合條件的值則為 True,反之為 False。 Returns: (pd.DataFrame): data Examples: 策略為每日收盤價前10高,取出場點。

from finlab import data
data.get('price:收盤價').is_largest(10).is_exit()

is_largest

is_largest(n)

取每列前 n 筆大的數值

若符合 True ,反之為 False 。用來篩選每天數值最大的股票。

is-largest Args: n (positive-int): 設定每列前 n 筆大的數值。 Returns: (pd.DataFrame): data Examples: 每季 ROA 前 10 名的股票

from finlab import data

roa = data.get('fundamental_features:ROA稅後息前')
good_stocks = roa.is_largest(10)

is_smallest

is_smallest(n)

取每列前 n 筆小的數值

若符合 True ,反之為 False 。用來篩選每天數值最小的股票。 Args: n (positive-int): 設定每列前 n 筆小的數值。 Returns: (pd.DataFrame): data Examples: 股價淨值比最小的 10 檔股票

from finlab import data

pb = data.get('price_earning_ratio:股價淨值比')
cheap_stocks = pb.is_smallest(10)

neutralize

neutralize(neutralizers, add_const=True)

Multi-factor cross-sectional neutralization

Performs cross-sectional regression to neutralize factors from the data. The residuals from regressing self on the neutralizers are returned.

PARAMETER DESCRIPTION
neutralizers

Factor(s) to neutralize against. Can be: - A single DataFrame - A list of DataFrames - A dict of DataFrames (keys are factor names)

TYPE: Union[DataFrame, list[DataFrame], dict[str, DataFrame]]

add_const

Whether to add a constant term to the regression. Default True.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
FinlabDataFrame

Neutralized data (regression residuals)

Examples:

Neutralize a factor against size:

from finlab import data

factor = data.get('price_earning_ratio:本益比')
size = data.get('fundamental_features:市值')

neutralized = factor.neutralize(size)

Neutralize against multiple factors using a list:

neutralized = factor.neutralize([size, beta])

Neutralize against multiple factors using a dict:

neutralized = factor.neutralize({
    'size': size,
    'size2': size ** 2,
    'beta': beta,
})

neutralize_industry

neutralize_industry(categories=None, add_const=True)

Industry neutralization using dummy variables

Performs cross-sectional regression to neutralize industry effects from the data. Each stock is assigned to an industry, and the factor is regressed on industry dummy variables. The residuals (industry-neutral factor) are returned.

PARAMETER DESCRIPTION
categories

Optional DataFrame with 'stock_id' and 'category' columns. If not provided, uses data.get('security_categories').

TYPE: DataFrame DEFAULT: None

add_const

Whether to add a constant term to the regression. Default True. Note: When using industry dummies, adding a constant creates multicollinearity, so one industry dummy is automatically dropped when add_const=True.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
FinlabDataFrame

Industry-neutralized data (regression residuals)

Examples:

Neutralize a factor against industry:

from finlab import data

factor = data.get('price_earning_ratio:本益比')
neutralized = factor.neutralize_industry()

Using custom categories:

custom_cats = pd.DataFrame({
    'stock_id': ['2330', '2317', '1101'],
    'category': ['半導體', '電子', '水泥']
})
neutralized = factor.neutralize_industry(categories=custom_cats)

quantile_row

quantile_row(c)

股票當天數值分位數

取得每列c定分位數的值。 Args: c (positive-int): 設定每列 n 定分位數的值。 Returns: (pd.DataFrame): data Examples: 取每日股價前90%分位數

from finlab import data
data.get('price:收盤價').quantile_row(0.9)

rank

rank(*args, valid=None, **kwargs)

Cross-sectional or time-series ranking.

PARAMETER DESCRIPTION
valid

只有 valid 為 True 的 cell 才參與排名。 valid 為 False/NaN 的 cell 在排名前設為 NaN, 因此不影響 pct=True 的分母。 常見用法:fillna 後傳入原始 notna() mask, 避免新上市或歷史不足的股票污染百分位排名。

TYPE: DataFrame or Series of bool DEFAULT: None

rise

rise(n=1)

數值上升中

取是否比前第n筆高,若符合條件的值則為True,反之為False。 Screen-Shot-2021-10-26-at-6-43-41-AM Args: n (positive-int): 設定比較前第n筆高。 Returns: (pd.DataFrame): data Examples: 收盤價是否高於10日前股價

from finlab import data
data.get('price:收盤價').rise(10)

sustain

sustain(nwindow, nsatisfy=None)

持續 N 天滿足條件

取移動 nwindow 筆加總大於等於nsatisfy,若符合條件的值則為True,反之為False。

PARAMETER DESCRIPTION
nwindow

設定移動窗格。

TYPE: positive - int

nsatisfy

設定移動窗格計算後最低滿足數值。

TYPE: positive - int DEFAULT: None

Returns: (pd.DataFrame): data Examples: 收盤價是否連兩日上漲

from finlab import data
data.get('price:收盤價').rise().sustain(2)

FinlabDataFrame 主要方法

選股與排名

方法 說明 範例
is_largest(n) 選擇最大的 N 個值 marketcap.is_largest(30)
is_smallest(n) 選擇最小的 N 個值 pe_ratio.is_smallest(20)
rank() 計算排名(支援產業排名) close.rank(pct=True)
industry_rank() 產業內排名 close.industry_rank()

技術指標

方法 說明 範例
average(n) N 期移動平均 close.average(20)
rise(n) 連續 N 期上漲 close.rise(3)
fall(n) 連續 N 期下跌 close.fall(2)
sustain(n) 條件持續 N 期 (close > ma20).sustain(5)

持倉管理

方法 說明 範例
hold_until() 持倉至停損/停利 position.hold_until(stop_loss=0.1)
exit_when() 遇到條件出場 position.exit_when(close < ma20)
entry_price() 取得進場價格 position.entry_price('close')

產業分析

方法 說明 範例
neutralize_industry() 產業中性化 factor.neutralize_industry()
groupby_category() 按產業分組 close.groupby_category().mean()

average()

計算移動平均。

使用範例

from finlab import data

close = data.get('price:收盤價')

# 20 日均線
ma20 = close.average(20)

# 60 日均線
ma60 = close.average(60)

# 均線交叉策略
golden_cross = ma20 > ma60

與 pandas rolling() 的差異

  • average(20) 等同於 rolling(20).mean()
  • 更簡潔易讀
  • 回傳值仍為 FinlabDataFrame,可繼續串接其他方法

is_largest() / is_smallest()

選擇排名前/後 N 名。

使用範例

from finlab import data

close = data.get('price:收盤價')
marketcap = data.get('etl:market_value')

# 選擇市值最小的 30 檔(小型股)
small_cap = marketcap.is_smallest(30)

# 選擇漲幅最大的 20 檔(動能股)
momentum = (close / close.shift(20)).is_largest(20)

# 組合:小型股中的動能股
position = small_cap & momentum

注意事項

  • is_largest(n) 每期(每日/每月)都會重新排名
  • 若股票數少於 N,則全選
  • 回傳 Boolean DataFrame(True 表示選中)

rank()

計算排名(支援百分比排名)。

使用範例

from finlab import data

close = data.get('price:收盤價')
marketcap = data.get('etl:market_value')

# 計算百分位排名(0-1)
cap_rank = marketcap.rank(pct=True, axis=1)

# 選擇市值前 30%
position = cap_rank > 0.7

# 或使用 is_largest(更直觀)
position = marketcap.is_largest(int(len(marketcap.columns) * 0.3))

industry_rank()

產業內排名。

使用範例

from finlab import data

close = data.get('price:收盤價')

# 計算產業內排名(0-1)
industry_rank = close.industry_rank()

# 選擇各產業漲幅前 30%
position = industry_rank > 0.7

產業分類來源

  • 使用 TEJ 產業分類
  • 自動處理產業變更

rise() / fall()

判斷連續上漲/下跌。

使用範例

from finlab import data

close = data.get('price:收盤價')

# 連續 3 日上漲
rising = close.rise(3)

# 連續 2 日下跌
falling = close.fall(2)

# 策略:連續上漲後反手做空
position = ~rising  # 反向訊號

sustain()

判斷條件持續 N 期。

使用範例

from finlab import data

close = data.get('price:收盤價')
ma20 = close.average(20)

# 連續 5 日站穩 20 日均線
sustained_above_ma = (close > ma20).sustain(5)

# 策略
position = sustained_above_ma

sustain vs rise

  • rise(n): 連續 n 期數值上漲
  • sustain(n): 連續 n 期條件為 True

hold_until()

持倉至停損/停利觸發。

使用範例

from finlab import data
from finlab.backtest import sim

close = data.get('price:收盤價')
position = close > close.average(20)

# 基礎用法:停損 10%
position_with_sl = position.hold_until(stop_loss=0.1)

# 停損 10% + 停利 20%
position_with_stops = position.hold_until(stop_loss=0.1, stop_profit=0.2)

# 追蹤停利(trailing stop)
position_trailing = position.hold_until(stop_loss=0.1, stop_profit=0.2, trailing_stop=True)

# 回測
report = sim(position_with_stops, resample='M')
report.display()

重要概念

  • hold_until()延長持倉至觸發條件
  • 停損/停利以進場價格為基準計算
  • trailing_stop=True 時,停利點會隨最高價上移

exit_when()

遇到特定條件出場。

使用範例

from finlab import data

close = data.get('price:收盤價')
ma20 = close.average(20)
ma60 = close.average(60)

# 進場:短均線向上突破長均線
entry = ma20 > ma60

# 出場:短均線向下跌破長均線
exit_signal = ma20 < ma60

# 持倉至出場訊號
position = entry.exit_when(exit_signal)

exit_when vs hold_until

  • exit_when(): 自訂出場條件(如技術指標)
  • hold_until(): 固定停損/停利比例

neutralize_industry()

產業中性化,移除產業因子影響。

使用範例

from finlab import data

# 原始因子(含產業影響)
factor = data.get('fundamental_features:股東權益報酬率')

# 產業中性化(移除產業平均)
neutral_factor = factor.neutralize_industry()

# 使用中性化因子
position = neutral_factor.is_largest(30)

為什麼要中性化?

  • 某些產業天生 ROE 高(如金融業)
  • 中性化可避免過度集中單一產業
  • 提升策略穩定性

完整範例:多重篩選策略

from finlab import data
from finlab.backtest import sim

# 步驟 1:載入資料
close = data.get('price:收盤價')
marketcap = data.get('etl:market_value')
revenue = data.get('monthly_revenue:當月營收')
roe = data.get('fundamental_features:股東權益報酬率')

# 步驟 2:定義篩選條件
cond1 = marketcap.is_smallest(100)  # 小型股
cond2 = (revenue.average(3) / revenue.average(12)) > 1.2  # 營收成長
cond3 = roe.rank(pct=True, axis=1) > 0.7  # ROE 前 30%
cond4 = (close > close.average(20)).sustain(3)  # 連續 3 日站穩 20MA

# 步驟 3:組合條件
position = cond1 & cond2 & cond3 & cond4

# 步驟 4:加入停損停利
position = position.hold_until(stop_loss=0.1, stop_profit=0.2)

# 步驟 5:限制持股數
position = position.is_largest(20)  # 最多 20 檔

# 步驟 6:回測
report = sim(position, resample='M')
report.display()

# 步驟 7:分析績效
metrics = report.get_metrics()
print(f"年化報酬: {metrics['annual_return']:.2%}")
print(f"夏普率: {metrics['daily_sharpe']:.2f}")
print(f"最大回撤: {metrics['max_drawdown']:.2%}")

常見問題

Q: FinlabDataFrame 如何自動對齊不同頻率的資料?

from finlab import data

# 日頻資料
close = data.get('price:收盤價')  # 每天更新
print(close.index[0])  # 2010-01-04

# 月頻資料
revenue = data.get('monthly_revenue:當月營收')  # 每月 10 號公告
print(revenue.index[0])  # 2010-01-10

# 直接運算會自動對齊(revenue 向前填補到日頻)
position = (close > 100) & (revenue > 1e8)
# position 是日頻資料,revenue 已自動填補

對齊規則: - 低頻 → 高頻:向前填補(forward fill) - 季報(財報):自動延遲至財報公告日

Q: 如何串接多個 FinlabDataFrame 方法?

from finlab import data

close = data.get('price:收盤價')

# 串接方法(類似 pandas method chaining)
position = (
    close
    .average(20)           # 計算 20MA
    .rank(pct=True, axis=1)  # 排名
    .is_largest(30)        # 取前 30 名
)

# 等同於
ma20 = close.average(20)
ranked = ma20.rank(pct=True, axis=1)
position = ranked.is_largest(30)

Q: is_largest() 和 rank() + 閾值有什麼差別?

from finlab import data

close = data.get('price:收盤價')

# 方法 1:is_largest(固定數量)
position1 = close.is_largest(30)  # 永遠選 30 檔

# 方法 2:rank + 閾值(固定比例)
position2 = close.rank(pct=True, axis=1) > 0.8  # 選前 20%(數量會變)

# 差異:
# - 方法 1:持股數固定(適合固定資金配置)
# - 方法 2:持股數浮動(適合市場狀況調整)

Q: hold_until() 的 trailing_stop 如何運作?

from finlab import data

close = data.get('price:收盤價')
position = close > close.average(20)

# 範例:進場價 100 元
# - stop_loss=0.1: 跌破 90 元出場
# - stop_profit=0.2, trailing_stop=False: 漲到 120 元出場(固定)
# - stop_profit=0.2, trailing_stop=True: 從最高價回落 20% 出場(動態)

# 情境:
# 進場 100 → 漲到 150 → 回落到 120
# - trailing_stop=False: 未觸發(未到 120)
# - trailing_stop=True: 觸發(從 150 回落 20% = 120)

position_fixed = position.hold_until(stop_loss=0.1, stop_profit=0.2, trailing_stop=False)
position_trailing = position.hold_until(stop_loss=0.1, stop_profit=0.2, trailing_stop=True)

Q: 如何在 FinlabDataFrame 上使用 pandas 方法?

from finlab import data

close = data.get('price:收盤價')

# FinlabDataFrame 完全兼容 pandas
close.rolling(20).mean()  # pandas 方法
close.fillna(0)           # pandas 方法
close.dropna()            # pandas 方法

# 所有 pandas 方法都可用
# 運算結果仍為 FinlabDataFrame(保留擴充功能)

參考資源