finlab.market
市場物件(Market)定義了回測系統如何取得價格資料、對照指數、市場特性等資訊。FinLab 內建支援台股、美股、興櫃,也可以透過繼承 Market 類別自訂市場。
市場物件的概念
每個市場有不同的特性:
- 價格資料來源: 從哪裡取得開盤價、收盤價、成交量等資料
- 對照指數: 用於績效比較的基準(如台股用加權指數、美股用 S&P 500)
- 交易時間: 開盤、收盤時間與時區
- 市場規則: 漲跌幅限制、交易成本、特殊股票類別
透過市場物件,回測引擎可以:
- 取得正確的價格資料
- 計算正確的報酬率
- 顯示對照指數的績效比較
- 支援實盤交易的時間判斷
內建市場對比
| 特性 | TWMarket | USMarket | ROTCMarket |
|---|---|---|---|
| 市場名稱 | 'tw_stock' |
'us_stock' |
'rotc_stock' |
| 資料頻率 | 每日('1d') |
每日('1d') |
每日('1d') |
| 對照指數 | 加權指數 | S&P 500 | (無) |
| 時區 | Asia/Taipei | US/Eastern | Asia/Taipei |
| 收盤時間 | 15:00 | 16:00 | 14:00 |
| 漲跌幅限制 | 10% | 無 | 無 |
| 特殊股票類別 | 處置股/全額交割 | 無 | 無 |
| 資料來源 | finlab.data |
finlab.data |
finlab.data |
使用情境
1. 使用內建市場
from finlab import data, backtest
from finlab.markets.tw import TWMarket
from finlab.markets.us import USMarket
# 台股回測(預設)
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())
# 美股回測
us_close = data.get('etl:us_adj_close')
us_position = us_close > us_close.average(50)
report = backtest.sim(us_position, resample='W', market=USMarket())
2. 自訂市場 - 加密貨幣
from finlab.market import Market
import pandas as pd
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())
3. 自訂市場 - 期貨(含槓桿)
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 類別完整 API 說明
- 必須實作 vs 選用的方法
- 如何處理多時區
- 如何實作市場休市邏輯
- 從 CSV、API、FinLab 資料庫混合載入資料
- 3 個實戰案例(黃金 ETF、全球市場、加密貨幣 4H)
API Reference
finlab.market.Market
Bases: ABC
市場類別
假如希望開發新的交易市場套用到回測系統,可以繼承 finlab.market.Market 來實做新類別。
get_asset_id_to_name
staticmethod
設定對標報酬率的時間序列
Returns:
(dict): 股號與股名對照表,ex:{'2330':'台積電'}
get_benchmark
staticmethod
設定對標報酬率的時間序列
這個函數用於設定對標報酬率的時間序列。
| RETURNS | DESCRIPTION |
|---|---|
Series
|
pd.Series: 時間序列的報酬率。 |
| RAISES | DESCRIPTION |
|---|---|
ExceptionType
|
Description of conditions under which the exception is raised. |
Examples:
| 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_board_lot_size
staticmethod
Returns the board lot size of the market.
| RETURNS | DESCRIPTION |
|---|---|
int
|
The board lot size of the market.
TYPE:
|
get_freq
staticmethod
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.
Returns: str: The frequency of the data.
get_market_value
staticmethod
取得回測用市值數據
| RETURNS | DESCRIPTION |
|---|---|
DataFrame
|
市值數據,其中 index 為日期,而 columns 是股票代號。 |
get_name
staticmethod
Returns the name of the market data source.
This function is used to get the name of the market data source.
get_odd_lot
staticmethod
Returns the odd lot size of the market.
| RETURNS | DESCRIPTION |
|---|---|
int
|
The odd lot size of the market.
TYPE:
|
get_price
abstractmethod
取得回測用價格或成交量數據
| PARAMETER | DESCRIPTION |
|---|---|
trade_at_price
|
價格類型或自訂資料 - str: 可選 'open', 'close', 'high', 'low', 'volume' - pd.Series: 自訂價格序列(單一股票或時間序列) - pd.DataFrame: 自訂價格資料(多檔股票)
TYPE:
|
adj
|
是否使用還原股價計算。僅對價格資料有效,volume 會忽略此參數。
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
DataFrame
|
價格或成交量數據,index 為日期,columns 為股票代號。 |
Note
必須實作的 trade_at_price 值:
- 'open', 'close', 'high', 'low': 基本價格資料(必須實作)
- 用於
get_trading_price()計算組合價格 -
用於回測的交易價格
-
'volume': 成交量資料(強烈建議實作)
LiquidityAnalysis: 流動性分析需要計算成交金額ml/qlib.py: 機器學習特徵生成portfolio_sync_manager.py: 實時 portfolio 管理-
若不實作,相關分析功能會失效
-
pd.Series, pd.DataFrame: 自訂資料(建議支援)
- 用於彈性回測,允許傳入自訂價格資料
- Series 會自動轉換為單欄 DataFrame
Examples:
回傳格式範例:
| 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 |
實作範例:
def get_price(self, trade_at_price, adj=True):
# 處理自訂資料
if isinstance(trade_at_price, pd.Series):
return trade_at_price.to_frame()
if isinstance(trade_at_price, pd.DataFrame):
return trade_at_price
# 處理字串
if isinstance(trade_at_price, str):
if trade_at_price == 'volume':
return data.get('your_volume_data') # 必須實作
# 處理 open/close/high/low
if adj:
return data.get(f'adj_{trade_at_price}')
else:
return data.get(trade_at_price)
raise ValueError(f'Unsupported trade_at_price: {trade_at_price}')
詳細實作可參考 finlab.markets.tw.TWMarket.get_price()
get_reference_price
Returns the most recent reference price of the market.
| RETURNS | DESCRIPTION |
|---|---|
Dict[str, float]
|
pandas.Series: The most recent reference price of the market. |
get_trading_price
取得回測用價格數據
| PARAMETER | DESCRIPTION |
|---|---|
name
|
選擇回測之還原股價以收盤價或開盤價計算,預設為'close'。可選 'open'、'close'、'high'、'low'、'open_close_avg'、'high_low_avg'、或 'price_avg'。
TYPE:
|
Returns: (pd.DataFrame): 價格數據
market_close_at_timestamp
Returns the timestamp of the market close of the given timestamp.
| PARAMETER | DESCRIPTION |
|---|---|
timestamp
|
The timestamp to find the market close to.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
datetime
|
The timestamp of the closest market close. |
market_open_time
staticmethod
Returns (hour, minute) of market open in local timezone.
快速開發技巧
最簡單的自訂市場只需實作兩個方法:
get_name(): 回傳市場名稱get_price(): 回傳價格 DataFrame
其他方法都是選用的,可根據需求實作。
常見錯誤
get_price()回傳的 DataFrame 必須 index 為 DatetimeIndex- columns 為股票代號(str)
- 缺失值建議使用 forward fill 處理
常見問題
Q: 如何處理調整後價格(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)
Q: 為什麼需要 get_benchmark()?
對照指數用於績效比較,在 report.display() 時會顯示策略 vs 對照指數的報酬曲線。如果不需要對照,回傳空 Series 即可:
Q: 可以在 get_price() 中使用 finlab.data.get() 嗎?
可以!這是混合使用 FinLab 資料的標準做法:
from finlab import data
class MixedMarket(Market):
def get_price(self, trade_at_price='close', adj=True):
tw_close = data.get('price:收盤價')[['2330', '2317']]
us_close = data.get('etl:us_adj_close')[['AAPL', 'TSLA']]
return pd.concat([tw_close, us_close], axis=1)
Q: 如何模擬交易成本?
交易成本在 backtest.sim() 函數中設定,不在 Market 物件:
# 加密貨幣通常手續費較低,稅率為 0
report = backtest.sim(
position,
market=CryptoMarket(),
fee_ratio=0.001, # 0.1% 手續費
tax_ratio=0 # 無交易稅
)