API 參考
finlab.market
本頁內容6
市場物件(Market)告訴回測引擎三件事:去哪裡拿開盤價、收盤價、成交量等價格資料,績效要跟哪個指數比(例如台股用加權指數、美股用 S&P 500),以及這個市場的規則(開收盤時間與時區、漲跌幅限制、交易成本、特殊股票類別)。FinLab 內建台股、美股個股、美股 ETF 與基金、興櫃四個市場;要回測其他資料,例如加密貨幣、期貨或自己的 CSV,就繼承 Market 寫一個新的。
透過市場物件,回測引擎可以:
- 取得正確的價格資料
- 計算正確的報酬率
- 顯示對照指數的績效比較
- 支援實盤交易的時間判斷
選哪個市場
| 你要回測的是 | 用這個 |
|---|---|
| 台股上市櫃(預設) | 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())from finlab import data, backtest
from finlab.markets.us import USMarket
# 美股回測
us_close = data.get('us_price:adj_close')
us_position = us_close > us_close.average(50)
report = backtest.sim(us_position, resample='W', market=USMarket())
# 美股 ETF 與基金回測
data.set_market('us_fund')
etf_close = data.get('price:adj_close')[['SPY', 'QQQ']]
etf_position = etf_close.pct_change(126).is_largest(1)
report = backtest.sim(etf_position, resample='M')美股個股使用 USMarket 與 us_price;SPY、QQQ 等美股 ETF 與基金使用 USFundMarket 與 us_fund_price。data.set_market('us_fund') 之後的 data.get('price:adj_close') 會讀取 us_fund_price:adj_close。
內建市場對比
| 特性 | TWMarket | USMarket | USFundMarket | ROTCMarket |
|---|---|---|---|---|
市場代碼(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/Taipei | US/Eastern | US/Eastern | Asia/Taipei |
| 收盤時間 | 15:00 | 16:00 | 16:00 | 15:00 |
| 預設手續費/交易稅 | 0.1425%/0.3% | 0/0 | 0/0 | 0.1425%/0.3% |
| 漲跌幅限制 | 10% | 無 | 無 | 無 |
| 特殊股票類別 | 處置股/全額交割 | 無 | 無 | 無 |
| 資料來源 | finlab.data | finlab.data | finlab.data | finlab.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() -> strReturns 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.
回傳
strstrThe frequency of the data.
get_name
get_name
get_name() -> strReturns 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.Seriespd.Series: 時間序列的報酬率。
可能的錯誤
ExceptionTypeDescription of conditions under which the exception is raised.
範例
| date | 0050 |
|---|---|
| 2007-04-23 | 100 |
| 2007-04-24 | 100.1 |
| 2007-04-25 | 99 |
| 2007-04-26 | 98.3 |
| 2007-04-27 | 99.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_pricestr | pd.Series | pd.DataFrame價格類型或自訂資料
- str: 可選 'open', 'close', 'high', 'low', 'volume'
- pd.Series: 自訂價格序列(單一股票或時間序列)
- pd.DataFrame: 自訂價格資料(多檔股票)
adjbool預設True是否使用還原股價計算。僅對價格資料有效,volume 會忽略此參數。
回傳
pd.DataFrame價格或成交量數據,index 為日期,columns 為股票代號。
範例
回傳格式範例:
| date | 0015 | 0050 | 0051 | 0052 |
|---|---|---|---|---|
| 2007-04-23 | 9.54 | 57.85 | 32.83 | 38.4 |
| 2007-04-24 | 9.54 | 58.1 | 32.99 | 38.65 |
| 2007-04-25 | 9.52 | 57.6 | 32.8 | 38.59 |
| 2007-04-26 | 9.59 | 57.7 | 32.8 | 38.6 |
| 2007-04-27 | 9.55 | 57.5 | 32.72 | 38.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.DataFrameindex 為股票代號,欄位
announced為公告日、pd.DataFramedelisted為終止上市(櫃)日。同一代號可以有多筆。
get_trading_price
get_trading_price
get_trading_price(name, adj) -> pd.DataFrame取得回測用價格數據
參數
namestr選擇回測之還原股價以收盤價或開盤價計算,預設為'close'。可選 'open'、'close'、'high'、'low'、'open_close_avg'、'high_low_avg'、或 'price_avg'。
adjbool預設True
回傳
pd.DataFrame價格數據
market_close_at_timestamp
market_close_at_timestamp
market_close_at_timestamp(timestamp) -> pd.TimestampReturns the timestamp of the market close of the given timestamp.
參數
timestampdatetimeThe timestamp to find the market close to.
回傳
datetimepd.TimestampThe 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 | NoneReturns the timezone of the market.
回傳
datetime.timezone | Nonedatetime.timezone: The timezone of the market.
default_fee_ratio
default_fee_ratio
default_fee_ratio() -> floatReturns the default fee ratio for this market.
回傳
floatfloatThe default fee ratio.
default_tax_ratio
default_tax_ratio
default_tax_ratio() -> floatReturns the default tax ratio for this market.
回傳
floatfloatThe default tax ratio.
get_odd_lot
get_odd_lot
get_odd_lot() -> intReturns the odd lot size of the market.
回傳
intintThe odd lot size of the market.
get_board_lot_size
get_board_lot_size
get_board_lot_size() -> intReturns the board lot size of the market.
回傳
intintThe 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()。
下一步
這一頁有幫到你嗎?