In [1]:
import numpy as np
import pandas as pd

import bt
import matplotlib.pyplot as plt

%matplotlib inline

In [2]:
np.random.seed(0)
returns =  np.random.normal(0.08/12,0.2/np.sqrt(12),12*10)
pdf = pd.DataFrame(
    np.cumprod(1+returns),
    index = pd.date_range(start="2008-01-01",periods=12*10,freq="m"),
    columns=['foo']
)

pdf.plot()


Out[2]:
<matplotlib.axes._subplots.AxesSubplot at 0x218fe970c18>

In [6]:
runMonthlyAlgo = bt.algos.RunMonthly()
rebalAlgo = bt.algos.Rebalance()

class Signal(bt.Algo):

    """
    
    Mostly copied from StatTotalReturn
    
    Sets temp['Signal'] with total returns over a given period.

    Sets the 'Signal' based on the total return of each
    over a given lookback period.

    Args:
        * lookback (DateOffset): lookback period.
        * lag (DateOffset): Lag interval. Total return is calculated in
            the inteval [now - lookback - lag, now - lag]

    Sets:
        * stat

    Requires:
        * selected

    """

    def __init__(self, lookback=pd.DateOffset(months=3),
                 lag=pd.DateOffset(days=0)):
        super(Signal, self).__init__()
        self.lookback = lookback
        self.lag = lag

    def __call__(self, target):
        selected = 'foo'
        t0 = target.now - self.lag
        
        if target.universe[selected].index[0] > t0:
            return False
        prc = target.universe[selected].loc[t0 - self.lookback:t0]
        
        
        trend = prc.iloc[-1]/prc.iloc[0] - 1
        signal = trend > 0.
        
        if signal:
            target.temp['Signal'] = 1.
        else:
            target.temp['Signal'] = 0.
            
        return True

signalAlgo = Signal(pd.DateOffset(months=12),pd.DateOffset(months=1))
    
class WeighFromSignal(bt.Algo):

    """
    Sets temp['weights'] from the signal.
    Sets:
        * weights

    Requires:
        * selected

    """

    def __init__(self):
        super(WeighFromSignal, self).__init__()

    def __call__(self, target):
        selected = 'foo'
        if target.temp['Signal'] is None:
            raise(Exception('No Signal!'))
        
        target.temp['weights'] = {selected : target.temp['Signal']}
        return True
    
weighFromSignalAlgo = WeighFromSignal()

In [7]:
s = bt.Strategy(
    'example1',
    [
        runMonthlyAlgo,
        signalAlgo,
        weighFromSignalAlgo,
        rebalAlgo
    ]
)

t = bt.Backtest(s, pdf, integer_positions=False, progress_bar=True)
res = bt.run(t)


example1
0% [############################# ] 100% | ETA: 00:00:00

In [8]:
res.plot_security_weights()



In [9]:
t.positions


Out[9]:
foo
2008-01-30 0.000000
2008-01-31 0.000000
2008-02-29 0.000000
2008-03-31 0.000000
2008-04-30 0.000000
2008-05-31 0.000000
2008-06-30 0.000000
2008-07-31 0.000000
2008-08-31 0.000000
2008-09-30 0.000000
2008-10-31 0.000000
2008-11-30 0.000000
2008-12-31 0.000000
2009-01-31 0.000000
2009-02-28 0.000000
2009-03-31 515222.105135
2009-04-30 515222.105135
2009-05-31 515222.105135
2009-06-30 515222.105135
2009-07-31 515222.105135
2009-08-31 515222.105135
2009-09-30 515222.105135
2009-10-31 515222.105135
2009-11-30 515222.105135
2009-12-31 515222.105135
2010-01-31 515222.105135
2010-02-28 515222.105135
2010-03-31 515222.105135
2010-04-30 515222.105135
2010-05-31 515222.105135
... ...
2015-07-31 631321.251898
2015-08-31 631321.251898
2015-09-30 631321.251898
2015-10-31 631321.251898
2015-11-30 631321.251898
2015-12-31 631321.251898
2016-01-31 631321.251898
2016-02-29 631321.251898
2016-03-31 631321.251898
2016-04-30 631321.251898
2016-05-31 631321.251898
2016-06-30 631321.251898
2016-07-31 631321.251898
2016-08-31 631321.251898
2016-09-30 631321.251898
2016-10-31 631321.251898
2016-11-30 631321.251898
2016-12-31 631321.251898
2017-01-31 631321.251898
2017-02-28 631321.251898
2017-03-31 631321.251898
2017-04-30 631321.251898
2017-05-31 631321.251898
2017-06-30 631321.251898
2017-07-31 631321.251898
2017-08-31 631321.251898
2017-09-30 631321.251898
2017-10-31 631321.251898
2017-11-30 631321.251898
2017-12-31 631321.251898

121 rows × 1 columns


In [11]:
res.prices.tail()


Out[11]:
example1
2017-08-31 240.302579
2017-09-30 255.046653
2017-10-31 254.464421
2017-11-30 265.182603
2017-12-31 281.069771

In [12]:
res.stats


Out[12]:
example1
start 2008-01-30 00:00:00
end 2017-12-31 00:00:00
rf 0
total_return 1.8107
cagr 0.109805
max_drawdown -0.267046
calmar 0.411186
mtd 0.0599103
three_month 0.102033
six_month 0.22079
ytd 0.879847
one_year 0.879847
three_year 0.406395
five_year 0.227148
ten_year 0.109805
incep 0.109805
daily_sharpe 3.29955
daily_sortino 6.35287
daily_mean 2.44859
daily_vol 0.742097
daily_skew 0.307861
daily_kurt 1.41446
best_day 0.137711
worst_day -0.14073
monthly_sharpe 0.723148
monthly_sortino 1.39289
monthly_mean 0.117579
monthly_vol 0.162594
monthly_skew 0.301545
monthly_kurt 1.37901
best_month 0.137711
worst_month -0.14073
yearly_sharpe 0.503939
yearly_sortino 5.01927
yearly_mean 0.14814
yearly_vol 0.293964
yearly_skew 2.3175
yearly_kurt 5.89495
best_year 0.879847
worst_year -0.0885428
avg_drawdown -0.0912547
avg_drawdown_days 369.714
avg_up_month 0.0643407
avg_down_month -0.0129277
win_year_perc 0.555556
twelve_month_win_perc 0.46789