예제 #1
0
    def test_portfolio_with_benchmark(self):
        price_weight = self.price.iloc[[0, 5], :].copy()
        price_weight = price_weight.apply(lambda ts: ts / ts.sum(), axis=1)
        equal_weight = pd.DataFrame(0.2,
                                    index=self.index[[0, 5]],
                                    columns=self.ticker)

        # Add benchmark at initiation:
        price_weight_port = bt.portfolio(share=self.share,
                                         price=self.price,
                                         name='Price Weight',
                                         benchmark=self.weight,
                                         benchmark_name='Equal Weight')
        assert_frame_equal(price_weight_port.weight, price_weight)
        assert_frame_equal(price_weight_port.benchmark.weight, equal_weight)
        self.assertEqual(price_weight_port.name, 'Price Weight')
        self.assertEqual(price_weight_port.benchmark.name, 'Equal Weight')

        # Set benchmark after initiation:
        price_weight_port = bt.portfolio(share=self.share,
                                         price=self.price,
                                         name='Price Weight')
        equal_weight_port = bt.portfolio(weight=self.weight,
                                         price=self.price,
                                         name='Equal Weight')
        price_weight_port.set_benchmark(equal_weight_port)
        assert_frame_equal(price_weight_port.weight, price_weight)
        assert_frame_equal(price_weight_port.benchmark.weight, equal_weight)
        self.assertEqual(price_weight_port.name, 'Price Weight')
        self.assertEqual(price_weight_port.benchmark.name, 'Equal Weight')
예제 #2
0
    def test_portfolio_backtest_with_benchmark(self):
        # No trading status:
        price_weight_port = bt.portfolio(share=self.share,
                                         price=self.price,
                                         name='Price Weight',
                                         benchmark=self.weight,
                                         benchmark_name='Equal Weight')
        result = pd.DataFrame()
        port_value_ts = self.asset_values_1_rebal_share.sum(axis=1)
        bm_value_ts = self.asset_values_1_rebal.sum(axis=1)
        result['Price Weight'] = port_value_ts / port_value_ts[0]
        result['Equal Weight'] = bm_value_ts / bm_value_ts[0]
        result['Difference'] = result['Price Weight'] - result['Equal Weight']
        assert_frame_equal(price_weight_port.backtest(), result)

        # With trading status:
        price_weight_port = bt.portfolio(share=self.share,
                                         price=self.price,
                                         trading_status=self.trading_status,
                                         name='Price Weight',
                                         benchmark=self.weight,
                                         benchmark_name='Equal Weight')
        result = pd.DataFrame()
        port_value_ts = self.asset_values_1_rebal_share_tst.sum(axis=1)
        bm_value_ts = self.asset_values_1_rebal_tst.sum(axis=1)
        result['Price Weight'] = port_value_ts / port_value_ts[0]
        result['Equal Weight'] = bm_value_ts / bm_value_ts[0]
        result['Difference'] = result['Price Weight'] - result['Equal Weight']
        assert_frame_equal(price_weight_port.backtest(), result)
예제 #3
0
    def test_portfolio_set_price_and_trading_status(self):
        # Normal setting price and trading status:
        port = bt.portfolio(weight=self.weight)
        port.set_price(price=self.price, trading_status=self.trading_status)
        assert_frame_equal(port.price, self.price)
        assert_frame_equal(port.trading_status, self.trading_status)

        # Setting at initiation:
        port = bt.portfolio(weight=self.weight,
                            price=self.price,
                            trading_status=self.trading_status)
        assert_frame_equal(port.price, self.price)
        assert_frame_equal(port.trading_status, self.trading_status)

        # Independent NA prices and trading status:
        price = self.price.copy()
        price.iloc[:5, 4] = np.nan
        expect_status = self.trading_status.copy()
        expect_status.iloc[:5, 4] = False
        port = bt.portfolio(weight=self.weight)
        port.set_price(price=price, trading_status=self.trading_status)
        assert_frame_equal(port.price, price)
        assert_frame_equal(port.trading_status, expect_status)

        # Out range trading status:
        out_range_status = self.trading_status.copy()
        out_range_status['Extra Ticker'] = True
        out_range_status.loc[pd.to_datetime('2020-01-20'), :] = False
        expect_status = self.trading_status
        port = bt.portfolio(weight=self.weight)
        port.set_price(price=self.price, trading_status=out_range_status)
        assert_frame_equal(port.trading_status, expect_status)
예제 #4
0
    def test_portfolio_performance_return(self):
        # NO rebalance:
        port = bt.portfolio(weight=self.weight.iloc[[0], :], price=self.price)
        port_value = self.asset_values_no_rebal.sum(axis=1).values
        expect_daily_ret = [0] + list(
            log(port_value[i + 1] / port_value[i])
            for i in range(len(self.index) - 1))
        expect_daily_ret = pd.Series(expect_daily_ret, index=self.index)
        expect_total_ret = [0] + list(
            log(port_value[i + 1] / port_value[0])
            for i in range(len(self.index) - 1))
        expect_total_ret = pd.Series(expect_total_ret, index=self.index)
        assert_series_equal(port.port_daily_ret, expect_daily_ret)
        assert_series_equal(port.port_total_ret, expect_total_ret)
        port_value_ts = self.asset_values_no_rebal.sum(axis=1)
        assert_series_equal(port.port_total_value,
                            port_value_ts / port_value_ts[0])

        # 1 rebalance:
        port = bt.portfolio(weight=self.weight, price=self.price)
        port_value = self.asset_values_1_rebal.sum(axis=1).values
        expect_daily_ret = [0] + list(
            log(port_value[i + 1] / port_value[i])
            for i in range(len(self.index) - 1))
        expect_daily_ret = pd.Series(expect_daily_ret, index=self.index)
        expect_total_ret = [0] + list(
            log(port_value[i + 1] / port_value[0])
            for i in range(len(self.index) - 1))
        expect_total_ret = pd.Series(expect_total_ret, index=self.index)
        assert_series_equal(port.port_daily_ret, expect_daily_ret)
        assert_series_equal(port.port_total_ret, expect_total_ret)
        port_value_ts = self.asset_values_1_rebal.sum(axis=1)
        assert_series_equal(port.port_total_value,
                            port_value_ts / port_value_ts[0])
예제 #5
0
    def test_portfolio_set_price(self):
        # Normal setting:
        port = bt.portfolio(weight=self.weight)
        port.set_price(price=self.price)
        expect_status = pd.DataFrame(True,
                                     index=self.index,
                                     columns=self.ticker)
        assert_frame_equal(port.price, self.price)
        assert_frame_equal(port.trading_status, expect_status)

        # Setting at initiation:
        port = bt.portfolio(weight=self.weight, price=self.price)
        expect_status = pd.DataFrame(True,
                                     index=self.index,
                                     columns=self.ticker)
        assert_frame_equal(port.price, self.price)
        assert_frame_equal(port.trading_status, expect_status)

        # Price and trading status cannot be set directly
        with self.assertRaises(AttributeError):
            port.price = self.price
        with self.assertRaises(AttributeError):
            port.trading_status = self.trading_status

        # Try masking out untradable prices:
        price = self.price.where(self.trading_status, other=np.nan)
        port = bt.portfolio(weight=self.weight)
        port.set_price(price=price)
        assert_frame_equal(port.price, price)
        assert_frame_equal(port.trading_status, self.trading_status)
예제 #6
0
    def test_portfolio_backtest(self):
        # Default setting, name = 'Portfolio'
        port = bt.portfolio(weight=self.weight, price=self.price)
        port_value_ts = self.asset_values_1_rebal.sum(axis=1)
        port_value_ts = port_value_ts / port_value_ts[0]
        assert_frame_equal(port.backtest(),
                           port_value_ts.to_frame(name='Portfolio'))

        # Setting portfolio name to 'Equal Weight'
        name = 'Equal Weight'
        port = bt.portfolio(weight=self.weight, price=self.price, name=name)
        assert_frame_equal(port.backtest(), port_value_ts.to_frame(name=name))
예제 #7
0
    def test_portfolio_drift_weight(self):
        # NO rebalance:
        port = bt.portfolio(weight=self.weight.iloc[[0], :], price=self.price)
        expect = self.asset_values_no_rebal.copy()
        expect = expect.apply(lambda ts: ts / ts.sum(), axis=1)
        assert_frame_equal(port.ex_weight, expect)

        # 1 rebalance:
        port = bt.portfolio(weight=self.weight, price=self.price)
        expect = self.asset_values_1_rebal.copy()
        expect = expect.apply(lambda ts: ts / ts.sum(), axis=1)
        assert_frame_equal(port.ex_weight, expect)
예제 #8
0
    def test_portfolio_drift_weight_with_trading_status(self):
        # NO rebalance:
        port = bt.portfolio(weight=self.weight.iloc[[0], :],
                            price=self.price,
                            trading_status=self.trading_status)
        expect = self.asset_values_no_rebal_tst.apply(lambda ts: ts / ts.sum(),
                                                      axis=1)
        assert_frame_equal(port.ex_weight, expect)

        # 1 rebalance:
        port = bt.portfolio(weight=self.weight,
                            price=self.price,
                            trading_status=self.trading_status)
        expect = self.asset_values_1_rebal_tst.apply(lambda ts: ts / ts.sum(),
                                                     axis=1)
        assert_frame_equal(port.ex_weight, expect)
예제 #9
0
    def test_portfolio_end_date(self):
        # Defulat end date: last date of price
        port = bt.portfolio(weight=self.weight, price=self.price)
        self.assertEqual(port.end_date, self.index[-1])

        # Set end date at initiation:
        end_date = pd.to_datetime('2020-01-08')
        port = bt.portfolio(weight=self.weight,
                            price=self.price,
                            end_date=end_date)
        self.assertEqual(port.end_date, end_date)
        # Change end date after initiation:
        port = bt.portfolio(weight=self.weight, price=self.price)
        self.assertEqual(port.end_date, self.index[-1])
        port.end_date = end_date
        self.assertEqual(port.end_date, end_date)
예제 #10
0
    def test_portfolio_performance_plot(self):
        from pandas.plotting import register_matplotlib_converters
        register_matplotlib_converters()

        # Portfolio without benchamrk:
        price_weight_port = bt.portfolio(
            share=self.share,
            name='Price Weight',
            price=self.price,
        )
        price_weight_port.backtest()
        performance_plot = price_weight_port.performance_plot(
        )  # figure object
        dates = performance_plot.axes[0].lines[0].get_xdata()
        total_vales = performance_plot.axes[0].lines[0].get_ydata()
        plot_ts = pd.Series(total_vales, index=dates)
        expect_ts = self.asset_values_1_rebal_share.sum(axis=1)
        expect_ts = expect_ts / expect_ts[0]
        assert_series_equal(plot_ts, expect_ts)

        # Portfolio with benchmark:
        price_weight_port = bt.portfolio(share=self.share,
                                         name='Price Weight',
                                         benchmark=self.weight,
                                         benchmark_name='Equal Weight',
                                         price=self.price)
        price_weight_port.backtest()
        performance_plot = price_weight_port.performance_plot(
        )  # figure object
        # Plot 1: portfolio values and benchmark values
        dates = performance_plot.axes[0].lines[0].get_xdata()
        port_vales = performance_plot.axes[0].lines[0].get_ydata()
        bm_vales = performance_plot.axes[0].lines[1].get_ydata()
        plot_port_ts = pd.Series(port_vales, index=dates)
        plot_bm_ts = pd.Series(bm_vales, index=dates)
        expect_port_ts = self.asset_values_1_rebal_share.sum(axis=1)
        expect_port_ts = expect_port_ts / expect_port_ts[0]
        expect_bm_ts = self.asset_values_1_rebal.sum(axis=1)
        expect_bm_ts = expect_bm_ts / expect_bm_ts[0]
        assert_series_equal(plot_port_ts, expect_port_ts)
        assert_series_equal(plot_bm_ts, expect_bm_ts)
        # Plot 2: Value differences
        diff_values = performance_plot.axes[1].lines[0].get_ydata()
        plot_diff_ts = pd.Series(diff_values, index=dates)
        expect_diff_ts = expect_port_ts - expect_bm_ts
        assert_series_equal(plot_diff_ts, expect_diff_ts)
예제 #11
0
    def test_portfolio_performance_metrics(self):
        # No trading status:
        price_weight_port = bt.portfolio(share=self.share,
                                         price=self.price,
                                         name='Price Weight')
        daily_ret = price_weight_port.port_daily_ret
        performance_df = pd.DataFrame(index=['Price Weight'])
        performance_df['Return'] = daily_ret.sum()
        performance_df['Volatility'] = daily_ret.std() * sqrt(len(daily_ret))
        performance_df[
            'Sharpe'] = performance_df['Return'] / performance_df['Volatility']
        hist_value = np.exp(daily_ret.cumsum())
        previous_peak = hist_value.cummax()
        performance_df['MaxDD'] = max(1 - hist_value / previous_peak)
        assert_series_equal(price_weight_port.period_return,
                            performance_df['Return'])
        assert_series_equal(price_weight_port.period_volatility,
                            performance_df['Volatility'])
        assert_series_equal(price_weight_port.period_sharpe_ratio,
                            performance_df['Sharpe'])
        assert_series_equal(price_weight_port.period_maximum_drawdown,
                            performance_df['MaxDD'])

        # No trading status:
        price_weight_port = bt.portfolio(share=self.share,
                                         price=self.price,
                                         name='Price Weight',
                                         trading_status=self.trading_status)
        daily_ret = price_weight_port.port_daily_ret
        performance_df = pd.DataFrame(index=['Price Weight'])
        performance_df['Return'] = daily_ret.sum()
        performance_df['Volatility'] = daily_ret.std() * sqrt(len(daily_ret))
        performance_df[
            'Sharpe'] = performance_df['Return'] / performance_df['Volatility']
        hist_value = np.exp(daily_ret.cumsum())
        previous_peak = hist_value.cummax()
        performance_df['MaxDD'] = max(1 - hist_value / previous_peak)
        assert_series_equal(price_weight_port.period_return,
                            performance_df['Return'])
        assert_series_equal(price_weight_port.period_volatility,
                            performance_df['Volatility'])
        assert_series_equal(price_weight_port.period_sharpe_ratio,
                            performance_df['Sharpe'])
        assert_series_equal(price_weight_port.period_maximum_drawdown,
                            performance_df['MaxDD'])
예제 #12
0
    def test_portfolio_weight_with_trading_status(self):
        # Weights on untradables:
        port = bt.portfolio(weight=self.weight,
                            price=self.price,
                            trading_status=self.trading_status)
        expect = pd.DataFrame(0.25,
                              index=self.index[[0, 5]],
                              columns=self.ticker)
        expect.iloc[0, 0] = 0
        expect.iloc[1, 1] = 0
        assert_frame_equal(port.weight, expect)

        # Weights sum 0 from untradables:
        weight = pd.DataFrame(0.25,
                              index=self.index[[0, 5]],
                              columns=self.ticker)
        weight.iloc[0, 0] = 0
        weight.iloc[1, :] = [0, 1, 0, 0, 0]
        port = bt.portfolio(weight=weight,
                            price=self.price,
                            trading_status=self.trading_status)
        expect = weight.iloc[[0], :]
        assert_frame_equal(port.weight, expect)
예제 #13
0
    def test_portfolio_weight(self):
        # Noraml equal weigt:
        port = bt.portfolio(weight=self.weight, price=self.price)
        expect = pd.DataFrame(0.2,
                              index=self.index[[0, 5]],
                              columns=self.ticker)
        assert_frame_equal(port.weight, expect)

        # Weights of row sum==zeros:
        weight = pd.DataFrame(0.2,
                              index=self.index[[0, 5]],
                              columns=self.ticker)
        weight.iloc[1, :] = 0
        port = bt.portfolio(weight=weight, price=self.price)
        expect = weight.iloc[[0], :]
        assert_frame_equal(port.weight, expect)
        weight = pd.DataFrame(0.2,
                              index=self.index[[0, 5]],
                              columns=self.ticker)
        weight.iloc[0, :] = 0
        port = bt.portfolio(weight=weight, price=self.price)
        expect = weight.iloc[[1], :]
        assert_frame_equal(port.weight, expect)

        # Out range weight:
        out_range_weight = self.weight.copy()
        out_range_weight['Extra Ticker'] = 1
        out_range_weight.loc[pd.to_datetime('2020-01-20'), :] = 1
        port = bt.portfolio(weight=out_range_weight, price=self.price)
        expect = pd.DataFrame(0.2,
                              index=self.index[[0, 5]],
                              columns=self.ticker)
        assert_frame_equal(port.weight, expect)

        # Weight cannot be set after initiation:
        with self.assertRaises(AttributeError):
            port.weight = self.weight
예제 #14
0
    def test_portfolio_from_share(self):
        # No trading status:
        price_1 = [1, 10, 26, 1, 11]
        price_2 = [6, 5, 6, 21, 1 + 10 * (1 + sin(5 / 9 * 2 * pi))]
        weight_value = [[i / sum(price_1) for i in price_1],
                        [j / sum(price_2) for j in price_2]]
        expect = pd.DataFrame(weight_value,
                              index=self.index[[0, 5]],
                              columns=self.ticker)
        port = bt.portfolio(share=self.share, price=self.price)
        assert_frame_equal(port.weight, expect)

        # With trading status:
        price_1 = [0, 10, 26, 1, 11]
        price_2 = [6, 0, 6, 21, 1 + 10 * (1 + sin(5 / 9 * 2 * pi))]
        weight_value = [[i / sum(price_1) for i in price_1],
                        [j / sum(price_2) for j in price_2]]
        port = bt.portfolio(share=self.share,
                            price=self.price,
                            trading_status=self.trading_status)
        expect = pd.DataFrame(weight_value,
                              index=self.index[[0, 5]],
                              columns=self.ticker)
        assert_frame_equal(port.weight, expect)
예제 #15
0
    def test_portfolio_daily_ret(self):
        price = pd.DataFrame(index=self.index)
        price['Normal'] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]  # Normal case
        price['Suspension'] = [1, 2, 3, np.nan, np.nan, np.nan, 4, 5, 6,
                               7]  # Temporary suspension
        price['Delisting'] = [1, 2, 3, 4, 5] + [np.nan] * 5  # Delisting
        price['Late'] = [np.nan] * 5 + [1, 2, 3, 4, 5]  # Late listing:

        expect = pd.DataFrame(index=self.index)
        expect['Normal'] = [np.nan] + [log((i + 1) / i) for i in range(1, 10)]
        expect['Suspension'] = [np.nan] + [log(2 / 1), log(
            3 / 2)] + [0.] * 3 + [
                log(4 / 3), log(5 / 4),
                log(6 / 5), log(7 / 6)
            ]
        expect['Delisting'] = [np.nan
                               ] + [log((i + 1) / i)
                                    for i in range(1, 5)] + [0.] * 5
        expect['Late'] = [np.nan] * 6 + [log((i + 1) / i) for i in range(1, 5)]

        port = bt.portfolio(weight=self.weight, price=price)
        assert_frame_equal(port.daily_ret, expect)
예제 #16
0
    def test_portfolio_performance_metrics_with_benchmark(self):
        # No trading status:
        price_weight_port = bt.portfolio(share=self.share,
                                         name='Price Weight',
                                         benchmark=self.weight,
                                         benchmark_name='Equal Weight',
                                         price=self.price)
        performance_df = pd.DataFrame()

        for i in ['Price Weight', 'Equal Weight', 'Active']:
            if i == 'Price Weight':
                daily_ret = price_weight_port.port_daily_ret
            elif i == 'Equal Weight':
                daily_ret = price_weight_port.benchmark.port_daily_ret
            elif i == 'Active':
                daily_ret = price_weight_port.port_daily_ret - price_weight_port.benchmark.port_daily_ret

            performance_df.loc[i, 'Return'] = daily_ret.sum()
            performance_df.loc[i, 'Volatility'] = daily_ret.std() * sqrt(
                len(daily_ret))
            performance_df.loc[i, 'Sharpe'] = performance_df.loc[
                i, 'Return'] / performance_df.loc[i, 'Volatility']
            if i != 'Active':
                hist_value = np.exp(daily_ret.cumsum())
            else:
                hist_value = price_weight_port.port_total_value - price_weight_port.benchmark.port_total_value
            previous_peak = hist_value.cummax()
            performance_df.loc[i,
                               'MaxDD'] = max(1 - hist_value / previous_peak)

        assert_series_equal(price_weight_port.period_return,
                            performance_df['Return'])
        assert_series_equal(price_weight_port.period_volatility,
                            performance_df['Volatility'])
        assert_series_equal(price_weight_port.period_sharpe_ratio,
                            performance_df['Sharpe'])
        assert_series_equal(price_weight_port.period_maximum_drawdown,
                            performance_df['MaxDD'])

        # No trading status:
        price_weight_port = bt.portfolio(share=self.share,
                                         name='Price Weight',
                                         benchmark=self.weight,
                                         benchmark_name='Equal Weight',
                                         price=self.price,
                                         trading_status=self.trading_status)
        performance_df = pd.DataFrame()

        for i in ['Price Weight', 'Equal Weight', 'Active']:
            if i == 'Price Weight':
                daily_ret = price_weight_port.port_daily_ret
            elif i == 'Equal Weight':
                daily_ret = price_weight_port.benchmark.port_daily_ret
            elif i == 'Active':
                daily_ret = price_weight_port.port_daily_ret - price_weight_port.benchmark.port_daily_ret

            performance_df.loc[i, 'Return'] = daily_ret.sum()
            performance_df.loc[i, 'Volatility'] = daily_ret.std() * sqrt(
                len(daily_ret))
            performance_df.loc[i, 'Sharpe'] = performance_df.loc[
                i, 'Return'] / performance_df.loc[i, 'Volatility']
            if i != 'Active':
                hist_value = np.exp(daily_ret.cumsum())
            else:
                hist_value = price_weight_port.port_total_value - price_weight_port.benchmark.port_total_value
            previous_peak = hist_value.cummax()
            performance_df.loc[i,
                               'MaxDD'] = max(1 - hist_value / previous_peak)

        assert_series_equal(price_weight_port.period_return,
                            performance_df['Return'])
        assert_series_equal(price_weight_port.period_volatility,
                            performance_df['Volatility'])
        assert_series_equal(price_weight_port.period_sharpe_ratio,
                            performance_df['Sharpe'])
        assert_series_equal(price_weight_port.period_maximum_drawdown,
                            performance_df['MaxDD'])