Skip to content

finlab.market

The Market object defines how the backtesting system retrieves price data, benchmark indices, market characteristics, and other information. FinLab has built-in support for US stocks and ETFs, as well as Taiwan stocks and the Taiwan Emerging Stock Board. You can also create custom markets by inheriting the Market class.

Market Object Concept

Each market has different characteristics:

  • Price data source: Where to get open, close, volume, and other price data
  • Benchmark index: Benchmark for performance comparison (e.g., the S&P 500 for US stocks)
  • Trading hours: Open/close times and timezone
  • Market rules: Price limits, transaction costs, special stock categories

Through market objects, the backtesting engine can:

  1. Retrieve the correct price data
  2. Calculate correct returns
  3. Display performance comparison against the benchmark
  4. Support time-based decisions for live trading

Built-in Market Comparison

Feature USMarket USFundMarket TWMarket ROTCMarket
Market code (data.set_market) 'us' 'us_fund' 'tw' 'rotc'
Market name (get_name()) 'us_stock' 'us_fund' 'tw_stock' 'rotc_stock'
Data frequency Daily ('1D') Daily ('1D') Daily ('1D') Daily ('1D')
Benchmark S&P 500 (^GSPC) S&P 500 (^GSPC) Taiwan weighted return index Taiwan weighted return index
Timezone US/Eastern US/Eastern Asia/Taipei Asia/Taipei
Market close 16:00 16:00 15:00 15:00
Default fee / tax 0 / 0 0 / 0 0.1425% / 0.3% 0.1425% / 0.3%
Price limit None None 10% None
Data source finlab.data finlab.data finlab.data finlab.data

data.set_market() selects the market for both data.get() and backtest.sim(), so after data.set_market('us') you rarely need to pass market= yourself.

Use Cases

1. Using Built-in Markets

from finlab import data, backtest
from finlab.markets.us import USFundMarket, USMarket

# US stock backtest: set_market('us') selects USMarket automatically
data.set_market('us')
close = data.get('price:adj_close')[['AAPL', 'MSFT', 'NVDA']]
position = close > close.average(50)
report = backtest.sim(position, resample='W')
# Equivalent to
report = backtest.sim(position, resample='W', market=USMarket())

# US ETF backtest
data.set_market('us_fund')
etf_close = data.get('price:adj_close')
etf_position = etf_close[['SPY', 'QQQ']].pct_change(126).is_largest(1)
report = backtest.sim(etf_position, resample='M', market=USFundMarket())

Taiwan stocks are the package default (data.set_market('tw'), TWMarket); everything above works the same way there.

2. Custom Market - Cryptocurrency

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']  # Use BTC as benchmark


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

3. Custom Market - Futures (with leverage)

Warning

This leveraged-price example resets exposure daily and omits per-lot costs, rounding, margin and rolls. For Taiwan single-stock futures with real contract lots, see TWStockFuturesMarket stage one.

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


# Use 10x leverage
report = backtest.sim(position, market=FuturesMarket(leverage=10))

Common Custom Market Scenarios

Scenario Description Examples
Cryptocurrency 24-hour trading, no price limits BTC, ETH, BNB
Futures Leverage effect, need to simulate leveraged returns E-mini S&P 500, stock futures
Other markets Markets not in the FinLab database European or Canadian stocks
Custom data Load prices from CSV, API Private data, simulated data
Mixed markets Backtest combinations of multiple markets US stocks + ETFs + Crypto

Detailed Guide

See Custom Market Object Guide for:

  • Complete Market class API documentation
  • Required vs optional methods
  • How to handle multiple timezones
  • How to implement market holiday logic
  • Loading data from CSV, API, FinLab database (mixed)
  • 3 practical examples (Gold ETF, Global Market, Crypto 4H)

API Reference

Market

class Market

Base class for markets. To backtest a new market, inherit from finlab.market.Market and override the methods below. USMarket, USFundMarket, TWMarket and the other built-in markets are subclasses.

A subclass that sets the class variables _price_table (for example 'us_price') and _adj_prefix (for example 'us_price:adj_') inherits a working get_price() that reads prices from finlab.data. Markets with a different schema override get_price().

Data methods

get_price()
def get_price(self, trade_at_price: str | pd.Series | pd.DataFrame, adj: bool = True) -> pd.DataFrame

Return the prices or volume used by the backtest.

Parameter Type Description
trade_at_price str, pd.Series or pd.DataFrame 'open', 'close', 'high', 'low' or 'volume'; or custom price data. A Series is returned as a one-column DataFrame, a DataFrame is returned unchanged.
adj bool Use adjusted prices. Ignored for 'volume'.

Returns: pd.DataFrame indexed by date with one column per symbol.

Implement at least 'open', 'close', 'high' and 'low': they are the trading prices and feed get_trading_price(). Implementing 'volume' is strongly recommended: liquidity analysis, finlab.ml.qlib features and PortfolioSyncManager need it. Supporting custom Series/DataFrame input lets users backtest with their own prices.

get_trading_price()
def get_trading_price(self, name: str, adj: bool = True) -> pd.DataFrame

Return the prices used to fill trades. name is 'open', 'close', 'high', 'low', 'open_close_avg', 'high_low_avg' or 'price_avg' (the average of open, close, high and low). Built on get_price(); raises ValueError for other names.

get_benchmark()
@staticmethod
def get_benchmark() -> pd.Series

Return the benchmark price series (date index) that reports compare the strategy against. The base class returns an empty Series, which hides the benchmark. USMarket returns data.get('world_index:adj_close')['^GSPC'], the S&P 500.

get_market_value()
@staticmethod
def get_market_value() -> pd.DataFrame

Return market values indexed by date with one column per symbol. The base class returns an empty DataFrame.

get_asset_id_to_name()
@staticmethod
def get_asset_id_to_name() -> dict[str, str]

Return a mapping from symbol to display name, used to label holdings in reports. USMarket maps each symbol to its company name, for example {'AAPL': 'Apple Inc.'}. The base class returns {}.

get_industry()
@staticmethod
def get_industry() -> dict[str, str]

Return a mapping from symbol to industry. USMarket reads the industry column of us_company_profile. The base class returns {}.

get_delisting_notices()
@staticmethod
def get_delisting_notices() -> pd.DataFrame

Return delisting notices announced by the exchange: one row per notice, indexed by symbol, with columns announced (announcement date) and delisted (delisting date). A symbol can have several rows. The backtest sells a holding on the first trading day after the announcement and stops buying it. Symbols without a notice exit at their last quote when their prices end early. The base class returns an empty table.

Market rules

Method Returns Base class
get_name() Name of the market data source, saved in reports (e.g. 'us_stock') 'auto'
get_freq() Data frequency used to resample non-daily data, saved in the report '1D'
market_open_time() (hour, minute) of the market open in local time (9, 0); USMarket: (9, 30)
market_close_at_timestamp(timestamp=None) Market close for the given timestamp; defaults to the last price date Last price date plus the smallest gap between price dates; USMarket: 16:00 US/Eastern
tzinfo() Market timezone None
default_fee_ratio() Default commission ratio used by sim() 0.001425; USMarket: 0
default_tax_ratio() Default transaction tax ratio used by sim() 0.003; USMarket: 0
get_reference_price() Most recent close per symbol as a dict Last row of get_price('close', adj=False)
get_odd_lot() Odd-lot size 1
get_board_lot_size() Board-lot size 1

Quick Development Tips

The simplest custom market only requires implementing two methods:

  1. get_name(): Return market name
  2. get_price(): Return price DataFrame

All other methods are optional and can be implemented as needed.

Common Errors

  • get_price() must return a DataFrame with DatetimeIndex as index
  • columns should be stock tickers (str)
  • Missing values should be handled with forward fill

FAQ

Q: How do I handle adjusted prices (adj=True)?

def get_price(self, trade_at_price='close', adj=True):
    if adj:
        # Return adjusted prices accounting for dividends and splits
        return pd.read_csv('adj_close.csv', index_col=0, parse_dates=True)
    else:
        # Return raw closing prices
        return pd.read_csv('raw_close.csv', index_col=0, parse_dates=True)

Q: Why is get_benchmark() needed?

The benchmark index is used for performance comparison. When report.display() is called, it shows the strategy vs benchmark return curve. If no benchmark is needed, return an empty Series:

@staticmethod
def get_benchmark():
    return pd.Series(
        dtype=float, index=pd.DatetimeIndex([])
    )  # Do not display benchmark

Q: Can I use finlab.data.get() inside get_price()?

Yes. This is the standard way to combine FinLab datasets, for example US stocks and ETFs in one market:

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


class StocksAndEtfsMarket(Market):
    def get_price(self, trade_at_price='close', adj=True):
        stocks = data.get('us_price:adj_close')[['AAPL', 'MSFT']]
        etfs = data.get('us_fund_price:adj_close')[['SPY', 'QQQ']]
        return pd.concat([stocks, etfs], axis=1)

Q: How do I simulate transaction costs?

Transaction costs are set in the backtest.sim() function, not in the Market object:

from finlab.markets.us import USMarket

# A broker that charges 0.05% commission per trade; U.S. stocks have no transaction tax
report = backtest.sim(
    position,
    resample='W',
    market=USMarket(),
    fee_ratio=0.0005,
    tax_ratio=0,
)

Resources