Skip to content

Backtesting (External Data)

pip install FinMind

Initialization: set backtest stock id and time period

import numpy as np
import pandas as pd
from FinMind import strategies
from FinMind.data import DataLoader
from FinMind.strategies.base import Strategy
from ta.momentum import StochasticOscillator

data_loader = DataLoader()
# data_loader.login_by_token(api_token='token') # optional
obj = strategies.BackTest(
    stock_id="0056",
    start_date="2018-01-01",
    end_date="2019-01-01",
    trader_fund=500000.0,
    fee=0.001425,
    data_loader=data_loader,
)
obj.stock_price

The backtest will be calculated using the following data

        date stock_id  Trading_Volume  Trading_money   open    max    min  close  spread  Trading_turnover  CashEarningsDistribution  StockEarningsDistribution
0    2018-01-02     2330        18055269     4188555408  231.5  232.5  231.0  232.5     3.0            9954.0                       0.0                        0.0
1    2018-01-03     2330        31706091     7504382512  236.0  238.0  235.5  237.0     4.5           13633.0                       0.0                        0.0
2    2018-01-04     2330        29179613     6963192636  240.0  240.0  236.5  239.5     2.5           10953.0                       0.0                        0.0
3    2018-01-05     2330        23721255     5681934695  240.0  240.0  238.0  240.0     0.5            8659.0                       0.0                        0.0
4    2018-01-08     2330        21846692     5281823362  242.0  242.5  240.5  242.0     2.0           10251.0                       0.0                        0.0
..          ...      ...             ...            ...    ...    ...    ...    ...     ...               ...                       ...                        ...
729  2020-12-25     2330        12581145     6449612552  514.0  515.0  510.0  511.0     1.0           14988.0                       0.0                        0.0
730  2020-12-28     2330        19262886     9890545245  512.0  515.0  509.0  515.0     4.0           16673.0                       0.0                        0.0
731  2020-12-29     2330        20151736    10370562545  515.0  517.0  513.0  515.0     0.0           17186.0                       0.0                        0.0
732  2020-12-30     2330        46705107    24306881615  516.0  525.0  514.0  525.0    10.0           33173.0                       0.0                        0.0
733  2020-12-31     2330        30326332    15989936054  526.0  530.0  524.0  530.0     5.0           25134.0                       0.0                        0.0

Design Strategy

class ShortSaleMarginPurchaseRatio(Strategy):
    """
    summary:
        Strategy concept: A higher short-sale-to-margin-purchase ratio means retail investors are bearish.
        When institutional investors are net buyers and the stock price tends to rise, we can take the
        opposite action of most retail investors by selling, and vice versa.
        Strategy rules: short-sale-to-margin-purchase ratio >= 30% and institutional investors net buy, sell
                       short-sale-to-margin-purchase ratio < 30% and institutional investors net sell, buy
    """

    ShortSaleMarginPurchaseTodayRatioThreshold = 0.3

    def load_taiwan_stock_margin_purchase_short_sale(self):
        self.TaiwanStockMarginPurchaseShortSale = (
            self.data_loader.taiwan_stock_margin_purchase_short_sale(
                stock_id=self.stock_id,
                start_date=self.start_date,
                end_date=self.end_date,
            )
        )
        self.TaiwanStockMarginPurchaseShortSale[
            ["ShortSaleTodayBalance", "MarginPurchaseTodayBalance"]
        ] = self.TaiwanStockMarginPurchaseShortSale[
            ["ShortSaleTodayBalance", "MarginPurchaseTodayBalance"]
        ].astype(
            int
        )
        self.TaiwanStockMarginPurchaseShortSale[
            "ShortSaleMarginPurchaseTodayRatio"
        ] = (
            self.TaiwanStockMarginPurchaseShortSale["ShortSaleTodayBalance"]
            / self.TaiwanStockMarginPurchaseShortSale[
                "MarginPurchaseTodayBalance"
            ]
        )

    def load_institutional_investors_buy_sell(self):
        self.InstitutionalInvestorsBuySell = (
            self.data_loader.taiwan_stock_institutional_investors(
                stock_id=self.stock_id,
                start_date=self.start_date,
                end_date=self.end_date,
            )
        )
        self.InstitutionalInvestorsBuySell[["sell", "buy"]] = (
            self.InstitutionalInvestorsBuySell[["sell", "buy"]]
            .fillna(0)
            .astype(int)
        )
        self.InstitutionalInvestorsBuySell = (
            self.InstitutionalInvestorsBuySell.groupby(
                ["date", "stock_id"], as_index=False
            ).agg({"buy": np.sum, "sell": np.sum})
        )
        self.InstitutionalInvestorsBuySell["diff"] = (
            self.InstitutionalInvestorsBuySell["buy"]
            - self.InstitutionalInvestorsBuySell["sell"]
        )

    def create_trade_sign(self, stock_price: pd.DataFrame) -> pd.DataFrame:
        stock_price = stock_price.sort_values("date")
        self.load_taiwan_stock_margin_purchase_short_sale()
        self.load_institutional_investors_buy_sell()
        stock_price = pd.merge(
            stock_price,
            self.InstitutionalInvestorsBuySell[["stock_id", "date", "diff"]],
            on=["stock_id", "date"],
            how="left",
        ).fillna(0)
        stock_price = pd.merge(
            stock_price,
            self.TaiwanStockMarginPurchaseShortSale[
                ["stock_id", "date", "ShortSaleMarginPurchaseTodayRatio"]
            ],
            on=["stock_id", "date"],
            how="left",
        ).fillna(0)
        stock_price.index = range(len(stock_price))
        stock_price["signal"] = 0
        sell_mask = (
            stock_price["ShortSaleMarginPurchaseTodayRatio"]
            >= self.ShortSaleMarginPurchaseTodayRatioThreshold
        ) & (stock_price["diff"] > 0)
        stock_price.loc[sell_mask, "signal"] = -1
        buy_mask = (
            stock_price["ShortSaleMarginPurchaseTodayRatio"]
            < self.ShortSaleMarginPurchaseTodayRatioThreshold
        ) & (stock_price["diff"] < 0)
        stock_price.loc[buy_mask, "signal"] = 1
        return stock_price

Backtest simulated trading

obj.add_strategy(ShortSaleMarginPurchaseRatio)
obj.simulate()
obj.final_stats

output

MeanProfit          187013.454352
MaxLoss             -17592.160000
FinalProfit         716596.810000
MeanProfitPer           37.400000
FinalProfitPer         143.320000
MaxLossPer              -3.520000
AnnualReturnPer         34.500000
AnnualSharpRatio         1.430000
dtype: float64

Trade Details

 obj.trade_detail

output

    stock_id        date  EverytimeProfit  RealizedProfit  UnrealizedProfit  board_lot  hold_cost  hold_volume  signal    tax       fee  trade_price  trader_fund
0       2330  2018-01-03             0.00            0.00              0.00       1000     0.0000            0       0  0.003  0.001425        236.0   500000.000
1       2330  2018-01-04             0.00            0.00              0.00       1000     0.0000            0       0  0.003  0.001425        240.0   500000.000
2       2330  2018-01-05             0.00            0.00              0.00       1000     0.0000            0       0  0.003  0.001425        240.0   500000.000
3       2330  2018-01-08             0.00            0.00              0.00       1000     0.0000            0      -1  0.003  0.001425        242.0   500000.000
4       2330  2018-01-09             0.00            0.00              0.00       1000     0.0000            0      -1  0.003  0.001425        242.0   500000.000
..       ...         ...              ...             ...               ...        ...        ...          ...     ...    ...       ...          ...          ...
728     2330  2020-12-25        692703.01       160992.91         531710.10       1000   245.8705         2000       0  0.003  0.001425        514.0    47251.925
729     2330  2020-12-28        688720.71       160992.91         527727.80       1000   245.8705         2000       0  0.003  0.001425        512.0    47251.925
730     2330  2020-12-29        694694.16       160992.91         533701.25       1000   245.8705         2000       0  0.003  0.001425        515.0    47251.925
731     2330  2020-12-30        696685.31       160992.91         535692.40       1000   245.8705         2000       0  0.003  0.001425        516.0    47251.925
732     2330  2020-12-31        716596.81       160992.91         555603.90       1000   245.8705         2000       0  0.003  0.001425        526.0    47251.925

Visualization

obj.plot()

backtesting