文件
開啟 Studio

API 參考

finlab.market

本頁內容6

市場物件(Market)告訴回測引擎三件事:去哪裡拿開盤價、收盤價、成交量等價格資料,績效要跟哪個指數比(例如台股用加權指數、美股用 S&P 500),以及這個市場的規則(開收盤時間與時區、漲跌幅限制、交易成本、特殊股票類別)。FinLab 內建台股、美股個股、美股 ETF 與基金、興櫃四個市場;要回測其他資料,例如加密貨幣、期貨或自己的 CSV,就繼承 Market 寫一個新的。

透過市場物件,回測引擎可以:

  1. 取得正確的價格資料
  2. 計算正確的報酬率
  3. 顯示對照指數的績效比較
  4. 支援實盤交易的時間判斷

選哪個市場

你要回測的是用這個
台股上市櫃(預設)TWMarket,data.set_market('tw')
美股個股USMarket,data.set_market('us')
SPY、QQQ 等美股 ETF 與基金USFundMarket,data.set_market('us_fund')
興櫃股票ROTCMarket,data.set_market('rotc')
換成含息的對照指數繼承內建市場,覆寫 get_benchmark()
FinLab 資料庫以外的價格(加密貨幣、期貨、CSV)繼承 Market,至少實作 get_name() 與 get_price()

data.set_market() 會同時選好 data.get() 的資料和 backtest.sim() 的市場,設定之後通常不用再傳 market=。

使用內建市場

台股是預設市場,不傳 market 就是 TWMarket():

from finlab import data, backtest
from finlab.markets.tw import TWMarket

# 台股回測(預設)
tw_close = data.get('price:收盤價')
tw_position = tw_close > tw_close.average(20)
report = backtest.sim(tw_position, resample='M')
# 等同於
report = backtest.sim(tw_position, resample='M', market=TWMarket())

內建市場對比

特性TWMarketUSMarketUSFundMarketROTCMarket
市場代碼(data.set_market)'tw''us''us_fund''rotc'
市場名稱(get_name())'tw_stock''us_stock''us_fund''rotc_stock'
資料頻率每日('1D')每日('1D')每日('1D')每日('1D')
對照指數加權股價報酬指數(含息)S&P 500 價格指數(^GSPC,不含股息)S&P 500 價格指數(^GSPC,不含股息)加權股價報酬指數(含息)
時區Asia/TaipeiUS/EasternUS/EasternAsia/Taipei
收盤時間15:0016:0016:0015:00
預設手續費/交易稅0.1425%/0.3%0/00/00.1425%/0.3%
漲跌幅限制10%無無無
特殊股票類別處置股/全額交割無無無
資料來源finlab.datafinlab.datafinlab.datafinlab.data

興櫃股票轉上市、上櫃時,ROTCMarket 在掛牌第一天以上市櫃的價格出場,不用興櫃最後一天的價格。

改用含息的對照指數

美股、日股、港股、韓股、英股(含各自的 ETF 市場)的對照指數是 world_index:adj_close 的價格指數(^GSPC、^N225、^HSI、^KS11、^FTSE),不含股息;策略用的卻是含息的還原價。所以跟台股(對照含息的報酬指數)不同,海外回測的對照線偏低、alpha 偏高,差距大約是該市場的股息殖利率。

要用含息的對照,繼承市場類別、讓 get_benchmark() 回傳代表 ETF 的還原價:

from finlab import data
from finlab.markets.us import USMarket

class USMarketWithDividends(USMarket):
    @staticmethod
    def get_benchmark():
        return data.get('us_fund_price:adj_close')['SPY']

自訂市場

最簡單的自訂市場只需要實作兩個方法:get_name() 回傳市場名稱,get_price() 回傳價格 DataFrame。其他方法都是選用的,依需求覆寫。

加密貨幣

import pandas as pd
from finlab import backtest
from finlab.market import Market

class CryptoMarket(Market):
    @staticmethod
    def get_name():
        return 'crypto'

    def get_price(self, trade_at_price='close', adj=True):
        df = pd.read_csv('crypto_close.csv', index_col=0, parse_dates=True)
        return df

    @staticmethod
    def get_benchmark():
        df = pd.read_csv('crypto_close.csv', index_col=0, parse_dates=True)
        return df['BTC']  # 使用 BTC 當對照

# 使用
report = backtest.sim(position, market=CryptoMarket())

期貨(含槓桿)

import pandas as pd
from finlab import backtest
from finlab.market import Market

class FuturesMarket(Market):
    def __init__(self, leverage=10):
        self.leverage = leverage
        self.prices = pd.read_csv('futures_price.csv', index_col=0, parse_dates=True)

    @staticmethod
    def get_name():
        return 'futures'

    def get_price(self, trade_at_price='close', adj=True):
        price = self.prices[trade_at_price]
        daily_return = price.pct_change() * self.leverage
        leveraged_price = (1 + daily_return).fillna(1).cumprod() * 100
        return leveraged_price

# 使用 10 倍槓桿
report = backtest.sim(position, market=FuturesMarket(leverage=10))

什麼時候需要自訂市場

場景說明範例
加密貨幣24 小時交易,無漲跌幅限制BTC, ETH, BNB
期貨有槓桿效果,需模擬槓桿報酬台指期、美股期貨
海外股票不在 FinLab 資料庫的市場陸股、歐股
自訂資料從 CSV、API 載入價格私有資料、模擬資料
混合市場同時回測多個市場的組合台股 + 美股 + 加密貨幣

Market 類別

內建的 TWMarket、USMarket、USFundMarket 等都是 Market 的子類別。子類別只要設定類別變數 _price_table(例如 'us_price')與 _adj_prefix(例如 'us_price:adj_'),就會繼承一個從 finlab.data 讀價格的 get_price();資料格式不同的市場(例如 TWMarket)自己覆寫 get_price()。

類別

finlab.market.Market

Market()

市場類別 假如希望開發新的交易市場套用到回測系統,可以繼承 finlab.market.Market 來實做新類別。

方法與屬性

get_freq
函式

get_freq

get_freq() -> str

Returns the frequency of the data. Used to determine how to resample the data when the data is not daily. The freq will be saved in finlab.core.report.

回傳

strstr

The frequency of the data.

get_name
函式

get_name

get_name() -> str

Returns the name of the market data source.

This function is used to get the name of the market data source.

get_benchmark
函式

get_benchmark

get_benchmark() -> pd.Series

設定對標報酬率的時間序列

這個函數用於設定對標報酬率的時間序列。

回傳

pd.Series

pd.Series: 時間序列的報酬率。

可能的錯誤

ExceptionType

Description of conditions under which the exception is raised.

範例

date0050
2007-04-23100
2007-04-24100.1
2007-04-2599
2007-04-2698.3
2007-04-2799.55
get_asset_id_to_name
函式

get_asset_id_to_name

get_asset_id_to_name() -> dict[str, str]

設定對標報酬率的時間序列

回傳

dict

股號與股名對照表,ex:{'2330':'台積電'}

get_price
函式

get_price

get_price(trade_at_price, adj) -> pd.DataFrame

取得回測用價格或成交量數據

Subclasses that define _price_table and _adj_prefix class variables inherit this default implementation automatically. Markets with different schemas (e.g. TWMarket) should override.

參數

trade_at_price str | pd.Series | pd.DataFrame

價格類型或自訂資料

  • str: 可選 'open', 'close', 'high', 'low', 'volume'
  • pd.Series: 自訂價格序列(單一股票或時間序列)
  • pd.DataFrame: 自訂價格資料(多檔股票)
adj bool 預設 True

是否使用還原股價計算。僅對價格資料有效,volume 會忽略此參數。

回傳

pd.DataFrame

價格或成交量數據,index 為日期,columns 為股票代號。

範例

回傳格式範例:

date0015005000510052
2007-04-239.5457.8532.8338.4
2007-04-249.5458.132.9938.65
2007-04-259.5257.632.838.59
2007-04-269.5957.732.838.6
2007-04-279.5557.532.7238.4

詳細實作可參考 finlab.markets.tw.TWMarket.get_price()

get_market_value
函式

get_market_value

get_market_value() -> pd.DataFrame

取得回測用市值數據

回傳

pd.DataFrame

市值數據,其中 index 為日期,而 columns 是股票代號。

get_delisting_notices
函式

get_delisting_notices

get_delisting_notices() -> pd.DataFrame

交易所公告的下市預告

回測會在公告日之後的第一個交易日賣出持股,之後也不再買進。沒有公告的標的, 若價格提早結束,則在最後一筆報價出場。

回傳

pd.DataFrame

index 為股票代號,欄位 announced 為公告日、

pd.DataFrame

delisted 為終止上市(櫃)日。同一代號可以有多筆。

get_trading_price
函式

get_trading_price

get_trading_price(name, adj) -> pd.DataFrame

取得回測用價格數據

參數

name str

選擇回測之還原股價以收盤價或開盤價計算,預設為'close'。可選 'open'、'close'、'high'、'low'、'open_close_avg'、'high_low_avg'、或 'price_avg'。

adj bool 預設 True

回傳

pd.DataFrame

價格數據

market_close_at_timestamp
函式

market_close_at_timestamp

market_close_at_timestamp(timestamp) -> pd.Timestamp

Returns the timestamp of the market close of the given timestamp.

參數

timestamp datetime

The timestamp to find the market close to.

回傳

datetimepd.Timestamp

The timestamp of the closest market close.

get_reference_price
函式

get_reference_price

get_reference_price() -> dict[str, float]

Returns the most recent reference price of the market.

Uses the unadjusted close, the price orders actually trade at.

回傳

dict[str, float]

dict[str, float]: The most recent reference price per symbol.

market_open_time
函式

market_open_time

market_open_time() -> tuple[int, int]

Returns (hour, minute) of market open in local timezone.

tzinfo
函式

tzinfo

tzinfo() -> datetime.timezone | None

Returns the timezone of the market.

回傳

datetime.timezone | None

datetime.timezone: The timezone of the market.

default_fee_ratio
函式

default_fee_ratio

default_fee_ratio() -> float

Returns the default fee ratio for this market.

回傳

floatfloat

The default fee ratio.

default_tax_ratio
函式

default_tax_ratio

default_tax_ratio() -> float

Returns the default tax ratio for this market.

回傳

floatfloat

The default tax ratio.

get_odd_lot
函式

get_odd_lot

get_odd_lot() -> int

Returns the odd lot size of the market.

回傳

intint

The odd lot size of the market.

get_board_lot_size
函式

get_board_lot_size

get_board_lot_size() -> int

Returns the board lot size of the market.

回傳

intint

The board lot size of the market.

Market.get_industry()

回傳股票代號對產業的對照表。

from finlab.markets.us import USMarket

industry = USMarket.get_industry()  # 讀 us_company_profile 的 industry 欄

基底類別回傳 {}。

沒覆寫時的預設值

方法回傳基底類別預設
get_name()市場資料來源名稱,存進報告(例如 'us_stock')'auto'
get_freq()資料頻率,用來重新取樣非日頻資料,存進報告'1D'
get_benchmark()對照指數的價格序列(日期為 index),報告拿策略跟它比空 Series,不顯示對照;USMarket 回傳 data.get('world_index:adj_close')['^GSPC']
get_market_value()市值,日期 × 股票空 DataFrame
get_asset_id_to_name()代號對名稱,用來標示報告裡的持股{};USMarket 對應公司名稱,例如 {'AAPL': 'Apple Inc.'}
get_delisting_notices()交易所的下市預告空表
market_open_time()當地時間的開盤 (時, 分)(9, 0);USMarket:(9, 30)
market_close_at_timestamp(timestamp=None)指定時間當天的收盤時間,預設用最後一個價格日期最後價格日期加上價格日期之間的最小間隔;USMarket:US/Eastern 16:00
tzinfo()市場時區None
default_fee_ratio()sim() 預設的手續費率0.001425;USMarket:0
default_tax_ratio()sim() 預設的交易稅率0.003;USMarket:0
get_reference_price()每檔股票最近一天的收盤價(dict)get_price('close', adj=False) 的最後一列
get_odd_lot()零股單位1
get_board_lot_size()整股單位1

get_trading_price(name) 建立在 get_price() 上,name 可以是 'open'、'close'、'high'、'low'、'open_close_avg'、'high_low_avg' 或 'price_avg'(開高低收的平均),其他名稱會丟出 ValueError。get_price() 傳入 Series 時會轉成單欄 DataFrame,傳入 DataFrame 則原樣回傳。

常見問題

如何處理調整後價格(adj=True)?
def get_price(self, trade_at_price='close', adj=True):
    if adj:
        # 回傳考慮除權息的調整後價格
        return pd.read_csv('adj_close.csv', index_col=0, parse_dates=True)
    else:
        # 回傳原始收盤價
        return pd.read_csv('raw_close.csv', index_col=0, parse_dates=True)
為什麼需要 get_benchmark()?

對照指數用於績效比較,在 report.display() 時會顯示策略 vs 對照指數的報酬曲線。如果不需要對照,回傳空 Series 即可:

@staticmethod
def get_benchmark():
    return pd.Series(dtype=float, index=pd.DatetimeIndex([]))  # 不顯示對照指數
可以在 get_price() 中使用 finlab.data.get() 嗎?

可以,這是混合使用 FinLab 資料的標準做法:

import pandas as pd
from finlab import data
from finlab.market import Market

class MixedMarket(Market):
    def get_price(self, trade_at_price='close', adj=True):
        tw_close = data.get('price:收盤價')[['2330', '2317']]
        us_close = data.get('us_price:adj_close')[['AAPL', 'TSLA']]
        return pd.concat([tw_close, us_close], axis=1)
如何模擬交易成本?

交易成本在 backtest.sim() 中設定,不在 Market 物件:

# 加密貨幣通常手續費較低,稅率為 0
report = backtest.sim(
    position,
    market=CryptoMarket(),
    fee_ratio=0.001,  # 0.1% 手續費
    tax_ratio=0,  # 無交易稅
)

沒有傳 fee_ratio、tax_ratio 時,sim() 用市場的 default_fee_ratio() 與 default_tax_ratio()。

下一步

  • 自訂市場物件 必須實作與選用的方法、多時區與休市邏輯、混合 CSV/API/FinLab 資料,以及黃金 ETF、全球市場、加密貨幣 4H 三個實戰案例。
  • 歷史回測 sim() 的換股頻率、停損停利與交易成本設定。
  • 完整策略開發流程 從研究到實盤的實戰步驟。

原始碼:TWMarket、USMarket。

↑↓ 選擇 Enter 開啟 Esc 關閉