API 參考
FinlabDataFrame
本頁內容13
FinlabDataFrame 是 pandas DataFrame 的擴充類別,專為寫選股策略設計。data.get() 下載的資料多數就是這個型別,所以股價、月營收、財報這些頻率不同的資料可以直接一起運算,不必手動 reindex。它繼承 pandas,pandas 的方法照樣能用,多出來的是自動對齊和這一頁列出的選股方法。
還不熟 data.get() 的話,先看 finlab.data。
選哪個函式
| 你想做的事 | 用這個 |
|---|---|
| 日頻、月頻、季頻資料一起算 | 直接用 + - * / > < & | 運算,會自動對齊 |
| 算 N 期移動平均 | average() |
| 比 n 期前高或低 | rise() / fall() |
| 條件連續成立 N 期 | sustain() |
| 每天挑數值最大或最小的 N 檔 | is_largest() / is_smallest() |
| 每天跨股票排名 | rank(axis=1, pct=True) 或 df.cs.rank() |
| 產業內排名 | industry_rank() 或 df.sector.rank() |
| 持有到出場條件、停損或停利 | hold_until() |
| 遇到條件就出場 | exit_when() |
| 取得每次進場時的價格 | entry_price() |
| 移除產業影響(產業中性化) | neutralize_industry() |
| 按產業分組統計 | groupby_category() 或 df.sector.mean() 等 |
| 縮尾、標準化、分組等因子前處理 | df.cs |
| 建構投資組合權重 | df.weight |
| 只留每個月(或每週、每季)最後一筆 | rebalance() |
不同頻率的資料直接運算
日頻的股價和月頻的營收可以直接放在同一個條件裡:
from finlab import data
close = data.get('price:收盤價') # 日頻
revenue = data.get('monthly_revenue:當月營收') # 月頻
# 自動對齊日期(revenue 會自動填補到日頻)
position = (close > close.average(20)) & (revenue > 1e8)運算前,兩邊的日期取聯集、股票取交集,再依下面的規則對齊:
- 低頻 → 高頻:向前填補(forward fill),月營收在下一次公布前一直沿用上一筆。
- 季報(財報):每家公司的數值從它的財報公告日開始生效。
所以每個日期用到的,都是那一天已經公布的資料:
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
# position 是日頻資料,revenue 已自動填補
position = (close > 100) & (revenue > 1e8)自動對齊由左邊的運算元決定。把一般 pandas DataFrame 放在左邊會得到不同的結果,見文末的常見問題。
算均線與漲跌
average()
計算 n 期移動平均,回傳值仍是 FinlabDataFrame,可以繼續串接其他方法。
from finlab import data
close = data.get('price:收盤價')
ma20 = close.average(20) # 20 日均線
ma60 = close.average(60) # 60 日均線
# 均線交叉策略
golden_cross = ma20 > ma60average(20) 等同於 rolling(20, min_periods=10).mean():時間窗格內可用的數值少於 n / 2 筆時,該股票為 NaN。
rise() / fall()
rise(n) 在數值比 n 期前高時為 True(self > self.shift(n)),fall(n) 則是比 n 期前低。要判斷連續上漲,搭配 sustain()。
from finlab import data
close = data.get('price:收盤價')
rising = close.rise(3) # 比 3 日前高
falling = close.fall(2) # 比 2 日前低
# 連續 3 日都比前一日高
rising_3_days = close.rise().sustain(3)n 預設為 1。
sustain()
判斷條件是否持續成立 N 期。
from finlab import data
close = data.get('price:收盤價')
ma20 = close.average(20)
# 連續 5 日站穩 20 日均線
position = (close > ma20).sustain(5)| 參數 | 型別 | 預設 | 說明 |
|---|---|---|---|
nwindow | int | 必填 | 往回看的期數 |
nsatisfy | int 或 None | None | 期間內至少要成立幾次;None 表示每一期都要成立 |
rise(n) 看的是數值比 n 期前高,sustain(n) 看的是條件連續 n 期為 True;sustain(n, k) 則是最近 n 期中至少 k 次為 True。
用排名挑股票
is_largest() / is_smallest()
每個日期(每一列)挑出數值最大或最小的 n 檔,回傳 Boolean DataFrame,True 表示選中。
from finlab import data
close = data.get('price:收盤價')
marketcap = data.get('etl:market_value')
# 市值最小的 30 檔(小型股)
small_cap = marketcap.is_smallest(30)
# 20 日漲幅最大的 20 檔(動能股)
momentum = (close / close.shift(20)).is_largest(20)
# 小型股中的動能股
position = small_cap & momentum- 每一期(每日或每月)都會重新排名。
- 股票數少於 n 時全部選取。
- 季頻資料會先換成財報公告日,每個日期只比較當天已公布的數值。
只想在符合條件的股票中排名時,用 score[cond](或 score.where(cond))排除不符合的股票:
from finlab import data
close = data.get('price:收盤價')
score = close / close.shift(20) # 20 日動能
cond = close > close.average(60) # 站上 60 日均線
# 不符合 cond 的股票是 NaN,不參與排名;符合的少於 10 檔時就只選這些
position = score[cond].is_largest(10)(cond * score).is_largest(10) 會讓不符合的股票分數為 0。符合的股票少於 10 檔時,這些 0 分股票會依欄位順序(1101、1102、1103…)被補進來。每列用最低分(is_smallest 為最高分)的同分股票補滿名額時,finlab 會發出 TopNPaddingWarning,選股結果不變。
rank()
計算排名,參數與 pd.DataFrame.rank 相同(axis、pct、ascending、method、na_option)。用 axis=1 在每個日期跨股票排名;產業內排名請用 industry_rank()。
from finlab import data
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))valid 參數只讓 True 的格子參與排名,其他格子先設為 NaN,不影響 pct=True 的分母。常見用法是 fillna 之後傳入原始的 notna(),避免新上市或歷史不足的股票扭曲百分位。
沿時間軸排名(axis=0,pandas 的預設)會用到未來的資料,finlab 會發出 LookaheadWarning;請改用 rolling().rank()、expanding().rank() 或 axis=1。
industry_rank()
在每個產業內排名,0 代表產業內最低,1 代表最高。
from finlab import data
close = data.get('price:收盤價')
# 產業內排名(0-1)
industry_rank = close.industry_rank()
# 產業內百分位分數高於 0.7 的股票
position = industry_rank > 0.7| 參數 | 型別 | 預設 | 說明 |
|---|---|---|---|
categories | list[str] 或 None | None | 要排名的分類。None 只用名稱不含 : 的大分類;傳入 categories=[...] 可指定特定分類,包含子分類 |
- 台股(
tw_stock與rotc_stock)的分類來自櫃買中心產業價值鏈資訊平台,透過data.get('security_industry_themes')取得。 - 每個日期在各分類內以
rank(axis=1, pct=True)排名;同時屬於多個分類的股票取各分類百分位的平均。 - 季頻資料會先換成財報公告日,每個日期都和同產業已公布的數值比較。
設定進場與出場
hold_until()
進場訊號為 True 時買進,一直持有到出場條件、停損、停利或移動停利觸發。
from finlab import data
from finlab.backtest import sim
close = data.get('price:收盤價')
position = close > close.average(20)
exit_condition = close < close.average(60)
# 基礎用法:出場條件
position_with_exit = position.hold_until(exit_condition)
# 出場條件 + 停損 10% + 停利 20%
position_with_stops = position.hold_until(
exit=exit_condition,
stop_loss=0.1,
take_profit=0.2,
)
# 出場條件 + 移動停利 10%,且獲利先達 20% 後才啟用
position_with_trail = position.hold_until(
exit=exit_condition,
trail_stop=0.1,
trail_stop_activation=0.2,
)
report = sim(position_with_stops, resample='M')
report.display()hold_until()會延長持倉,直到觸發出場。- 停損、停利以進場價格為基準計算。
trail_stop在報酬從持倉最高點回落指定幅度時出場;trail_stop_activation設定啟用前要先達到的獲利門檻。移動停利的算法見文末常見問題。
| 參數 | 型別 | 預設 | 說明 |
|---|---|---|---|
exit | DataFrame | 必填 | 出場訊號。季頻字串索引(如 2024-Q1)會先轉成財報公告日 |
nstocks_limit | int 或 None | None | 同時持有的檔數上限,None 為不限 |
stop_loss | float 或 DataFrame | -np.inf | 成本價下跌多少出場,例如 0.1。也可傳入日期 × 股票的 DataFrame,每檔每天用不同門檻 |
take_profit | float 或 DataFrame | np.inf | 成本價上漲多少出場,規則同 stop_loss |
trail_stop | float | np.inf | 移動停利幅度,np.inf 為不使用 |
trail_stop_activation | float 或 None | 0 | 啟用移動停利前要先達到的獲利,None 與 0 相同 |
trade_at | str | 'close' | 停損停利的參考價,'close' 或 'open' |
rank | DataFrame 或 None | None | 進場訊號超過 nstocks_limit 時,數值越大越優先進場 |
exit_mode | str | 'state' | 只能用關鍵字指定,'state' 或 'event',見下一段;其他值會拋出 ValueError |
出場訊號是狀態還是事件(exit_mode)
進場訊號只在自己有的日期生效。出場訊號的日期比進場稀疏時(月營收、財報、週/月頻條件),exit 沒有的日期由 exit_mode 決定:
exit_mode | exit 沒有的日期 | 適合的出場條件 |
|---|---|---|
'state'(預設) | 沿用前一筆的值,直到下一筆資料 | 月營收、財報等低頻條件:公布後一直有效,這段期間不會再進場 |
'event' | 視為不出場 | 只列出事件日的資料:在 exit 有資料的日期出場,之後進場訊號為 True 就再買進 |
from finlab import data
close = data.get('price:收盤價')
entries = close > close.average(20)
yoy = data.get('monthly_revenue:去年同月增減(%)')
# 年增率為負就出場,之後公布的年增率不再是負數,才會再進場
position = entries.hold_until(yoy < 0)
# 只在月營收有資料的日期出場
position = entries.hold_until(yoy < 0, exit_mode='event')2.1.0~2.2.0 的預設是 'event';2.1.0 以前(至 2.0.23)和目前的預設是 'state'。要維持 2.1.0~2.2.0 的持股,請加上 exit_mode='event'。
什麼時候兩種模式結果相同
exit 在每個進場日都有一列時,兩種模式結果相同,例如兩者都由同一張日頻價格表算出;季頻字串索引以轉換後的日期比較。rank 或停損停利的價格額外帶進的日期上沒有進場訊號,不影響結果。日頻的 exit 只要少了某個進場日,例如資料晚一天更新或缺一個交易日,那一天 'state' 沿用前一筆,'event' 視為不出場,結果就可能不同。
'event' 只看得到傳進來的 exit 有哪些日期
下列情況傳進 hold_until 的 exit 已經被往後填到比較密的時間軸,填出來的每一列都當成一次事件,'event' 無法還原原本只在公布日出現的出場:
- 和日頻資料運算過的條件,例如
(yoy < 0) | (close < close.average(60)):結果和'state'相同。要當成事件,請直接傳入原本的月頻條件。 - 季報字串索引(如
2024-Q1):index_str_to_date()以所有公司的財報公告日為索引,每家公司的值會延續到其他公司的公告日,所以值為 True 時,'event'在其他公司的公告日也會出場,不是只在自己的公告日。 - lazy 的
exit(data.get(..., lazy=True)或.lazy()算出的條件):lazy 引擎在呼叫hold_until之前,會先把它往後填到 lazy 的時間軸,所以即使指定'event',結果也和'state'相同,而且不會報錯。要事件語意,請傳入 eager 的exit,或先只對低頻的出場條件呼叫.collect():
# eager 的出場條件(取資料時不加 lazy=True)
position = entries.lazy().hold_until(yoy < 0, exit_mode='event').collect()
# 出場條件是 lazy 時,先單獨 collect
yoy_lazy = data.get('monthly_revenue:去年同月增減(%)', lazy=True)
exits = (yoy_lazy < 0).collect()
position = entries.lazy().hold_until(exits, exit_mode='event').collect()exit_when()
遇到特定條件出場:進場訊號由 False 變成 True 時進場,出場訊號為 True 或進場訊號變回 False 時出場。
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 | DataFrame | 必填 | 出場訊號 |
exit_when() 適合自訂的出場條件(如技術指標);要固定比例的停損停利,用 hold_until()。
entry_price()
取得每次進場時的價格(台股還原價)。
from finlab import data
close = data.get('price:收盤價')
position = close.is_largest(10)
cost = position.entry_price('close')| 參數 | 型別 | 預設 | 說明 |
|---|---|---|---|
trade_at | str | 'close' | 'close' 用台股還原收盤價(etl:adj_close),其他值用還原開盤價(etl:adj_open) |
進場價是進場訊號隔一個交易日的還原價;那天停牌時,取之後第一個有價格的交易日。只支援台股。
產業中性化與分組
neutralize_industry()
產業中性化:以產業虛擬變數做橫截面迴歸,回傳殘差,移除產業因子的影響。
from finlab import data
# 原始因子(含產業影響)
factor = data.get('fundamental_features:ROE稅後')
# 產業中性化(移除產業平均)
neutral_factor = factor.neutralize_industry()
# 使用中性化因子
position = neutral_factor.is_largest(30)| 參數 | 型別 | 預設 | 說明 |
|---|---|---|---|
categories | DataFrame 或 None | None | 含 symbol(或 stock_id)與 category 欄的分類表;None 使用台股分類 data.get('security_categories') |
add_const | bool | True | 迴歸是否加常數項;加常數項時會自動拿掉一個產業虛擬變數,避免共線性 |
為什麼要中性化:某些產業天生 ROE 高(如金融業),中性化可避免過度集中單一產業,提升策略穩定性。
groupby_category()
按產業分組,用法類似 pd.DataFrame.groupby()。
from finlab import data
close = data.get('price:收盤價')
industry_mean = close.groupby_category().mean()季頻資料會先換成財報公告日再分組,每個日期的產業統計只包含當天已公布的數值。
neutralize_industry_batch()
一次中性化多個因子。接受因子的 list 或 dict,回傳的容器形狀與輸入一致;日期與股票欄位相同的因子會共用分類對照、日期轉換與向量化的產業運算。搭配 factor_alignment_profile() 可以量測對齊花了多少時間。
from finlab import data
from finlab.dataframe import neutralize_industry_batch, factor_alignment_profile
value = data.get('price_earning_ratio:本益比') # 日頻
quality = data.get('fundamental_features:ROE稅後') # 季頻
neutral = neutralize_industry_batch({"value": value, "quality": quality})
with factor_alignment_profile() as profile:
combined = value + quality
print(profile.to_dict())| 參數 | 型別 | 預設 | 說明 |
|---|---|---|---|
factors | list 或 dict | 必填 | 要中性化的因子 |
categories | DataFrame 或 None | None | 同 neutralize_industry() |
add_const | bool | True | 同 neutralize_industry() |
預設 categories 使用依 market 與目前 data lifecycle 管理的快取;切換 storage、強制更新分類資料及 data.clear() 都會讓它失效。也可手動呼叫 data.clear_category_cache();data.category_cache_stats() 可檢查 raw/mapping 的命中次數。
跨股票轉換:df.cs
df.cs(cross-sectional)在每個日期、跨所有股票做轉換,常用在因子前處理。
from finlab import data
roe = data.get('fundamental_features:ROE稅後')
# 因子預處理:縮尾 + 標準化
clean_roe = roe.cs.winsorize(0.05, 0.95).cs.zscore()
# 分成 5 組回測
group = roe.cs.bucket(5)
long_group = group == 5 # 做多最高組
# 百分位排名選股
position = roe.cs.rank() > 0.8 # 選前 20%| 方法 | 說明 | 範例 |
|---|---|---|
rank() | 百分位排名,範圍 (0, 1] | factor.cs.rank() |
winsorize(lower, upper) | 百分位縮尾 | factor.cs.winsorize(0.05, 0.95) |
bucket(n) | 等分位分組(1..n) | factor.cs.bucket(5) |
zscore() | 標準化(均值 0,標準差 1) | factor.cs.zscore() |
demean() | 去均值 | factor.cs.demean() |
df.cs.rank() 和 pandas 原生的 df.rank(pct=True, axis=1) 效果類似,但語意更清楚。
產業內轉換與聚合:df.sector
df.sector 提供兩類功能:
- 產業內轉換:與
df.cs相同的 5 個方法(rank / winsorize / bucket / zscore / demean),但在同產業內計算。 - 產業內聚合:mean / std / median / sum / min / max / count,回傳產業內的統計量。
from finlab import data
roe = data.get('fundamental_features:ROE稅後')
# 產業內排名選股(各產業內 ROE 前 20%)
position = roe.sector.rank() > 0.8
# 產業內標準化(消除產業差異)
neutralized_roe = roe.sector.zscore()
# 高於產業平均 ROE 的股票
sector_avg_roe = roe.sector.mean()
above_avg = roe > sector_avg_roe| 方法 | 說明 | 範例 |
|---|---|---|
rank() | 產業內百分位排名 | factor.sector.rank() |
winsorize(lower, upper) | 產業內百分位縮尾 | factor.sector.winsorize(0.05, 0.95) |
bucket(n) | 產業內等分位分組 | factor.sector.bucket(3) |
zscore() | 產業內標準化 | factor.sector.zscore() |
demean() | 產業內去均值 | factor.sector.demean() |
mean() | 產業內平均值 | roe.sector.mean() |
std(ddof=1) | 產業內標準差 | roe.sector.std() |
median() | 產業內中位數 | roe.sector.median() |
sum() | 產業內總和 | revenue.sector.sum() |
min() | 產業內最小值 | close.sector.min() |
max() | 產業內最大值 | close.sector.max() |
count() | 產業內有效值數量 | roe.sector.count() |
不加參數時使用台股產業分類(security_categories)。要用自己的分類,呼叫 df.sector(by):by 可以是股票代號對產業名稱的 dict 或 pd.Series,也可以是日期 × 股票代號的 DataFrame(分類會隨時間變);不在對照表裡的股票不參與計算(結果為 NaN)。
my_map = {"2330": "AI供應鏈", "2317": "AI供應鏈", "2882": "金融"}
roe.sector(my_map).rank()df.sector.zscore() 等同於 neutralize_industry() 的效果(產業中性化);df.sector 更有彈性,除了中性化,還能排名、分組、聚合。
建構投資組合權重:df.weight
df.weight 直接對選股結果(Boolean 或數值 DataFrame)建構與調整權重。所有方法都回傳 FinlabDataFrame,可以一路串接。
from finlab import data
from finlab.backtest import sim
marketcap = data.get('etl:market_value')
# 選股
position = marketcap.is_smallest(30)
# 鏈式建構權重
weighted = (
position.weight.inverse_volatility(window=60) # 反波動率加權
.weight.cap_industry(max_weight=0.3) # 產業權重上限 30%
.rebalance('ME') # 只留每月最後一天(換股日)
.weight.limit_turnover(max_turnover=0.3) # 每次換股的換手率上限 30%
.weight.drawdown_control(max_drawdown=0.15) # 回撤控制 15%
)
report = sim(weighted, resample='M')
report.display()| 方法 | 說明 | 範例 |
|---|---|---|
cap_industry(max_weight) | 限制每個產業的最大權重,超額按比例重新分配 | w.weight.cap_industry(0.3) |
clip_by_volume(total_fund, max_participation_ratio, volume, window) | 依日均成交金額限制個股權重 | w.weight.clip_by_volume(1e7) |
inverse_volatility(window, price) | 反波動率加權,低波動股票獲得較高權重 | w.weight.inverse_volatility(60) |
risk_parity(window, price) | 風險平價加權,使每個持股的風險貢獻相等 | w.weight.risk_parity(60) |
correlation(window, price, diversify) | 依相關性調整權重,分散化或集中化 | w.weight.correlation(diversify=True) |
target_volatility(target, window, price) | 縮放權重以達成目標年化波動率 | w.weight.target_volatility(0.15) |
limit_turnover(max_turnover) | 限制相鄰兩列的雙向換手率(日頻部位先 .rebalance() 到換股日) | w.rebalance('ME').weight.limit_turnover(0.3) |
drawdown_control(max_drawdown, price) | 回撤超過門檻時自動縮減曝險 | w.weight.drawdown_control(0.15) |
波動率、相關性、回撤方法的 price,以及 clip_by_volume 的 volume,預設用台股資料(etl:adj_close、台股成交金額)。
組合起來:多重篩選策略
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:ROE稅後')
# 步驟 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.is_largest(20) # 最多 20 檔
# 步驟 5:回測並加入停損停利
report = sim(position, resample='M', stop_loss=0.1, take_profit=0.2)
report.display()
# 步驟 6:分析績效
stats = report.get_stats()
print(f"年化報酬: {stats['cagr']:.2%}")
print(f"夏普率: {stats['daily_sharpe']:.2f}")
print(f"最大回撤: {stats['max_drawdown']:.2%}")FinlabDataFrame 類別
下面是 FinlabDataFrame 本身的說明,以及 average、is_largest、rank、rebalance、lazy、quantile_row、reindex 等內建方法的完整參數。
finlab.dataframe.FinlabDataFrame
FinlabDataFrame()回測語法糖
除了使用熟悉的 Pandas 語法外,我們也提供很多語法糖,讓大家開發程式時,可以用簡易的語法完成複雜的功能,讓開發策略更簡潔!
我們將所有的語法糖包裹在 FinlabDataFrame 中,用起來跟 pd.DataFrame 一樣,但是多了很多功能!
只要使用 finlab.data.get() 所獲得的資料,皆為 FinlabDataFrame 格式,
接下來我們就來看看, FinlabDataFrame 有哪些好用的語法糖吧!
當資料日期沒有對齊(例如: 財報 vs 收盤價 vs 月報)時,在使用以下運算符號:
+, -, *, /, >, >=, ==, <, <=, &, |, ~,
不需要先將資料對齊,因為 FinlabDataFrame 會自動幫你處理,以下是示意圖。
以下是範例:cond1 與 cond2 分別為「每天」,和「每季」的資料,假如要取交集的時間,可以用以下語法:
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 台泥 的訊號如下圖,可以看到 cond1 跟 cond2 訊號的頻率雖然不相同,
但是由於 cond1 跟 cond2 是 FinlabDataFrame,所以可以直接取交集,而不用處理資料頻率對齊的問題。

總結來說,FinlabDataFrame 與一般 dataframe 唯二不同之處:
- 多了一些 method,如
df.is_largest(),df.sustain()...等。 - 在做四則運算、不等式運算前,會將 df1、df2 的 index 取聯集,column 取交集。
與 pd.Series 運算時,依 Series 的 index 判斷對齊方向:日期(或季/月字串)
index 對齊日期;股票代號 index 對齊股票、套用到每一天(& / | 缺少的股票視為
False,四則運算為 NaN);兩者皆符合或皆不符合時會報錯,請改用
df.mul(s, axis=0)(日期)或 df.mul(s, axis=1)(股票)明確指定。
日期布林 Series 擇時:position[cond] 與 position & cond 都保留每個日期,
False 的日期空手。
方法與屬性
fillna
fillna
fillna(value, **kwargs) -> FinlabDataFrame | NoneFill as pandas does, and keep a boolean table boolean when value is a bool.
pandas 3 leaves bool.shift().fillna(False) as object, where ~
gives -1/-2. The value decides, never the later rows: a column turns
back only if every cell is a bool once filled.
參數
valueAnykwargsAny預設{}
reindex
reindex
reindex(*args, **kwargs) -> FinlabDataFramepd.DataFrame.reindex that understands quarterly string indexes.
Reindexing a quarterly ('2013-Q1') FinlabDataFrame onto dates first
converts the quarters to disclosure dates with index_str_to_date(),
so df.reindex(close.index, method='ffill') gives each stock's
latest disclosed value instead of all NaN or a TypeError.
參數
argsAny預設()kwargsAny預設{}
align
align
align(other, *args, **kwargs) -> tuple[FinlabDataFrame, pd.DataFrame | pd.Series]pd.DataFrame.align that converts a quarterly side to dates first.
參數
otherpd.DataFrame | pd.SeriesargsAny預設()kwargsAny預設{}
where
where
where(cond, *args, **kwargs) -> FinlabDataFramepd.DataFrame.where that converts a quarterly frame to dates first.
With a date-indexed cond, roe.where(cond) works like
roe.index_str_to_date().where(cond) instead of raising
"putmask: mask and data must be the same size".
FinlabDataFrame date conditions carry their last observation forward
to dates missing from the condition, including disclosure timestamps.
Plain pandas conditions retain pandas' exact-date alignment.
參數
condAnyargsAny預設()kwargsAny預設{}
mask
mask
mask(cond, *args, **kwargs) -> FinlabDataFramepd.DataFrame.mask that converts a quarterly frame to dates first.
參數
condAnyargsAny預設()kwargsAny預設{}
corrwith
corrwith
corrwith(other, *args, **kwargs) -> pd.SeriesCompute pairwise correlations with pandas alignment and arithmetic.
參數
otherpd.DataFrame | pd.SeriesargsAny預設()kwargsAny預設{}
rolling
rolling
rolling(*args, **kwargs) -> Anypd.DataFrame.rolling; numeric frames compute their columns concurrently.
參數
argsAny預設()kwargsAny預設{}
average
average
average(n) -> FinlabDataFrame取 n 筆移動平均
若股票在時間窗格內,有 N/2 筆 NaN,則會產生 NaN。
參數
npositive - int設定移動窗格數。
回傳
pd.DataFramedata
範例
股價在均線之上
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'])
is_largest
is_largest
is_largest(n) -> FinlabDataFrame取每列前 n 筆大的數值
若符合 True ,反之為 False 。用來篩選每天數值最大的股票。

參數
npositive - int設定每列前 n 筆大的數值。
回傳
pd.DataFramedata
範例
每季 ROA 前 10 名的股票
from finlab import data
roa = data.get('fundamental_features:ROA稅後息前')
good_stocks = roa.is_largest(10)只在符合條件的股票中排名:用 score[cond] 排除不符合的股票,
不要用 cond * score。後者讓不符合的股票分數為 0,符合的股票
少於 n 檔時會被補進來,並發出 TopNPaddingWarning。
top = score[cond].is_largest(10)is_smallest
is_smallest
is_smallest(n) -> FinlabDataFrame取每列前 n 筆小的數值
若符合 True ,反之為 False 。用來篩選每天數值最小的股票。
參數
npositive - int設定每列前 n 筆小的數值。
回傳
pd.DataFramedata
範例
股價淨值比最小的 10 檔股票
from finlab import data
pb = data.get('price_earning_ratio:股價淨值比')
cheap_stocks = pb.is_smallest(10)只在符合條件的股票中排名時用 pb[cond].is_smallest(10)。
每列同為最高值的股票多於剩餘名額、只能依欄位順序挑選時,
會發出 TopNPaddingWarning。
rank
rank
rank(*args, valid, **kwargs) -> FinlabDataFrameCross-sectional or time-series ranking.
參數
validDataFrame or Series of bool只有 valid 為 True 的 cell 才參與排名。 valid 為 False/NaN 的 cell 在排名前設為 NaN, 因此不影響 pct=True 的分母。 常見用法:fillna 後傳入原始 notna() mask, 避免新上市或歷史不足的股票污染百分位排名。
argsobject預設()kwargsobject預設{}
rise
rise
rise(n) -> FinlabDataFrame數值上升中
取是否比前第n筆高,若符合條件的值則為True,反之為False。

參數
npositive - int預設1設定比較前第n筆高。
回傳
pd.DataFramedata
範例
收盤價是否高於10日前股價
from finlab import data
data.get('price:收盤價').rise(10)fall
fall
fall(n) -> FinlabDataFrame數值下降中
取是否比前第n筆低,若符合條件的值則為True,反之為False。

參數
npositive - int預設1設定比較前第n筆低。
回傳
pd.DataFramedata
範例
收盤價是否低於10日前股價
from finlab import data
data.get('price:收盤價').fall(10)rebalance
rebalance
rebalance(freq) -> FinlabDataFrameDownsample to keep only the last observation per period.
Equivalent to self.resample(freq).last(), except that a period
labelled before its last row ('MS', 'QS') is dated at that row.
參數
freqstr | pd.Index | listPandas frequency string, e.g.
'ME'(month-end),'W'(weekly),'QE'(quarter-end), or explicit dates to select withreindex.
pct_change
pct_change
pct_change(periods, fill_method, *args, **kwargs) -> FinlabDataFrameFractional change without filling missing values by default.
This follows pandas 3 on every supported pandas version. To carry the
last valid price across a gap, use frame.ffill().pct_change().
Explicit arguments are passed through to pandas.
參數
periodsint預設1fill_methodstr | NoneargsAny預設()kwargsAny預設{}
lazy
lazy
lazy() -> LazyWideWrap this DataFrame into a lazy computation handle.
Returns a LazyWide proxy that records operations without
executing them. Call .rebalance(freq) or .collect()
on the result to trigger execution at specific dates only.
Example::
close = data.get('price:收盤價')
position = (
close.lazy()
.rolling(60).mean()
.rank(axis=1, pct=True)
.is_largest(10)
.rebalance('M')
)quantile_row
quantile_row
quantile_row(c) -> pd.Series股票當天數值分位數
取得每列c定分位數的值。
參數
cpositive - int設定每列 n 定分位數的值。
回傳
pd.DataFramedata
範例
取每日股價前90%分位數
from finlab import data
data.get('price:收盤價').quantile_row(0.9)常見問題
為什麼一般 pandas DataFrame 放在左邊,結果不一樣?
自動對齊由左邊的運算元決定。一般 pd.DataFrame 在左邊時,+ - * / // % ** & | ^ 會用 pandas 規則:日期不向前填補、股票取聯集、結果是一般 DataFrame(比較運算 > < == 不受影響)。兩邊日期或股票不同時會發出 PandasLeftOperandWarning。
import pandas as pd
from finlab import data
from finlab.dataframe import FinlabDataFrame
close = data.get('price:收盤價')
mask = pd.DataFrame(True, index=close.index[:1], columns=close.columns)
mask & (close > 0) # pandas 對齊:只有第一天是 True,會警告
(close > 0) & FinlabDataFrame(mask) # FinlabDataFrame 對齊:向前填補到每一天請把 FinlabDataFrame 放在左邊,或用 FinlabDataFrame(df) 包起來。
如何串接多個 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)is_largest() 和 rank() + 閾值有什麼差別?
is_largest(30) 永遠選 30 檔,持股數固定,適合固定資金配置;rank(pct=True, axis=1) > 0.8 選前 20%,持股數會隨股票數浮動,適合依市場狀況調整。
from finlab import data
close = data.get('price:收盤價')
position1 = close.is_largest(30) # 固定數量
position2 = close.rank(pct=True, axis=1) > 0.8 # 固定比例移動停利(trail_stop)如何運作?
一般多單、使用 trade_at_price="close" 且未啟用 touched_exit 時,從最高價回落的幅度是以進場成本為分母,而不是以最高價為分母:(持倉最高價 - 目前價格) / 進場成本 > trail_stop 時產生出場訊號。公式採嚴格不等式,實際邊界仍可能受浮點誤差影響。
這是出場訊號的門檻,不是保證成交價:實際成交日期與價格仍依 sim() 的成交設定決定。touched_exit=True 改用盤中高低價及觸價規則;預設 retain_cost_when_rebalance=False 在每次實際執行再平衡時,連繼續持有的股票也重設進場成本與最高價,而 resample=None 時任何一檔股票的部位變動都會觸發整本再平衡,見再平衡時機與停損基準。
移動停利可透過 sim(trail_stop=...) 在回測引擎中處理,也可在 hold_until(trail_stop=...) 產生部位時先轉成出場訊號。若希望獲利先達指定門檻後才啟用移動停利,可加上 trail_stop_activation。
import pandas as pd
from finlab import data
from finlab.backtest import sim
close = data.get('price:收盤價')
position = close > close.average(20)
# 範例:進場價 100 元
# - stop_loss=0.1: 跌破 90 元出場
# - take_profit=0.2: 漲到 120 元出場(固定停利)
# - trail_stop=0.2: 從最高價回落超過進場成本的 20% 時產生出場訊號
# 情境:進場 100 → 漲到 150 → 回落到 120
# - take_profit=0.2: 漲到 120 即出場,不會等到 150
# - trail_stop=0.2: 門檻為 150 - 0.2 * 100 = 130(不是 120),跌破 130 才產生訊號
# 固定停利
report_fixed = sim(position, resample='M', stop_loss=0.1, take_profit=0.2)
# 移動停利
report_trailing = sim(position, resample='M', stop_loss=0.1, trail_stop=0.2)
# 在 hold_until 階段先產生移動停利出場訊號
position_trailing = position.hold_until(
exit=pd.DataFrame(False, index=position.index, columns=position.columns),
trail_stop=0.2,
trail_stop_activation=0.3,
)如何在 FinlabDataFrame 上使用 pandas 方法?
FinlabDataFrame 完全相容 pandas,所有 pandas 方法都能用,運算結果仍是 FinlabDataFrame(保留擴充功能)。
from finlab import data
close = data.get('price:收盤價')
close.rolling(20).mean()
close.fillna(0)
close.dropna()下一步
pandas 本身的用法請看 pandas 官方文件。
這一頁有幫到你嗎?