pmorissette / bt

bt - flexible backtesting for Python
http://pmorissette.github.io/bt
MIT License
2.28k stars 429 forks source link

algos selectthese and weightspecified for various specified dates? #2

Open sivananth opened 9 years ago

sivananth commented 9 years ago

Hi Phil, I'm not very familiar with python, but bt seems simple enough for my purposes. I want to specify Date,ticker,weight for different dates. I'll also provide the prices for those tickers....it should simply use the specified tickers and weights for the different dates and just do the back-testing (in other words, i only need the back-test engine, don't need its selection and weighting routines, for starts). I searched the documentation, but couldn't find something that would meet my purposes. can you point me to the right modules?

Thank you very much.

pmorissette commented 9 years ago

Hey @sivananth,

The SMA Crossover Strategy section on the Examples page might be what you are looking for (http://pmorissette.github.io/bt/examples.html).

You basically provide a DataFrame containing the weights to the WeighTarget algo and this algo will set the target weights. Then all you need is the Rebalance algo to actually execute the weight changes.

I just modified the code to support target weight DataFrames that are not of the same size as the data (prices).

With this change, you can create a monthly rebalancing strategy by providing a DataFrame with monthly dates and target weights.

Here is a quick example I threw together;


# pull in data
data = bt.get('aapl,msft')

# setup a target weight DataFrame
# this DataFrame need not be the same size as the data
# for each row with data, the target weights will be set
tw = data.to_monthly()
# setting all weights to 0.5 (equal weights for both securities)
tw[:] = 0.5

# here we use WeighTarget in combination with Rebalance
# WeighTarget sets the weights, Rebalance rebalances the portfolio
# I set it up with equal weights here but you can get creative (above SMA, etc.)
s = bt.Strategy('ewmr', [bt.algos.WeighTarget(tw), bt.algos.Rebalance()])
t = bt.Backtest(s, data)
res = bt.run(t)

# you can examine the security weights here - you will see that the weights are roughly 0.5 on the last day of each month
weights = res.backtests['ewmr'].security_weights

Please remember to update bt as I have just made some changes.

Let me know if this helps, Phil

penilessquant commented 9 years ago

Hi Phil, I am trying to modify this so that it selects the top 2 based on momentum from a group of securities, and then weight them 70/30 monthly. I tried the following code but it seems to ignore the momentum part, what I am doing wrong here?

import bt
import numpy as np

data = bt.get('aapl,msft,c,gs,ge')

tw = data.to_monthly()
tw[:] = 0
tw.loc[:,'aapl'] = 0.7
tw.loc[:,'msft'] = 0.3

s = bt.Strategy('aaa', [bt.algos.SelectAll(),
                        bt.algos.SelectMomentum(1),
                        bt.algos.WeighTarget(tw),
                        bt.algos.Rebalance()])
t = bt.Backtest(s, data)
res = bt.run(t)

res.display()
res.plot()
res.plot_security_weights()
pmorissette commented 9 years ago

Hey @penilessquant,

If I understand your strategy correctly, you want to select the top 2 securities based on price momentum and set the weight of the best performing security to 70% and the weight of the second best to 30%. If that is indeed the case, then bt currently does not have an algo to set weights as such.

However, you can easily add your own algo. Here is a quick one just to give you an idea:

class OrderedWeights(bt.Algo):
    def __init__(self, weights):
        self.target_weights = weights

    def __call__(self, target):
        target.temp['weights'] = dict(zip(target.temp['selected'], self.target_weights))
        return True

So now you can use this algo to test your strategy like this:

data = bt.get('aapl,msft,c,gs,ge')

s = bt.Strategy('s', [bt.algos.RunMonthly(),
                      bt.algos.SelectAll(),
                      bt.algos.SelectMomentum(n=2),
                      OrderedWeights([0.7, 0.3]),
                      bt.algos.Rebalance()])

t = bt.Backtest(s, data)
res = bt.run(t)

Hope this helps. Let me know if you have any questions.

P

penilessquant commented 9 years ago

Thank you, that is perfect! Still just beginning to learn how to use bt, but its proving to be a really elegant way of doing backtesting, really appreciate what you have done in creating this library.

penilessquant commented 9 years ago

Sorry, its me again. I am trying to take the above strategy and 'chunk' it, I am still taking the top 2 momentum stock, and weighting them 70/30, but instead of doing this for my entire wealth at each rebalance point, I am only rebalancing 1/3 of my current exposure. For example, I might have 70% to MSFT and 30% to AAPL, and now I arrive at my next rebalance point and momentum says that it should now be MSFT and GE. I should have (70%)* (2/3) of MSFT, (30%) * (2/3) of AAPL and then another 70%* (1/3) of MSFT and 30% * (1/3) of GE after that rebalance since I am rebalancing away 1/3 of my current allocation.

I have got the code below which seems to work, but it's rather inelegant and if I want to change the 'chunking factor' to rebalance for example 1/10 of my allocation or to compare different chunk factors, it becomes really unwieldy. I am sure I am missing a neat solution here.


import bt
import pandas as pd

class RunOnDataFrameDates(bt.Algo):
    def __init__(self, dates):
        self.dates = pd.to_datetime(dates)

    def __call__(self, target):
        return target.now in self.dates

class OrderedWeights(bt.Algo):
    def __init__(self, weights):
        self.target_weights = weights

    def __call__(self, target):
        target.temp['weights'] = dict(zip(target.temp['selected'], self.target_weights))
        return True

data = bt.get('aapl,msft,c,gs,ge')
date_mm = data.to_monthly().index.values
group0 = date_mm[::3]
group1 = date_mm[1::3]
group2 = date_mm[2::3]

mom0 = bt.Strategy('mom0', [RunOnDataFrameDates(group0),
                        bt.algos.SelectAll(),
                        bt.algos.SelectMomentum(2,pd.DateOffset(months=10)),
                        OrderedWeights([0.7, 0.3]),
                        bt.algos.Rebalance()])

mom1 = bt.Strategy('mom1', [RunOnDataFrameDates(group1),
                        bt.algos.SelectAll(),
                        bt.algos.SelectMomentum(2,pd.DateOffset(months=10)),
                        OrderedWeights([0.7, 0.3]),
                        bt.algos.Rebalance()])

mom2 = bt.Strategy('mom2', [RunOnDataFrameDates(group2),
                        bt.algos.SelectAll(),
                        bt.algos.SelectMomentum(2,pd.DateOffset(months=10)),
                        OrderedWeights([0.7, 0.3]),
                        bt.algos.Rebalance()])                   

master = bt.Strategy('master', [bt.algos.RunMonthly(),
                                bt.algos.SelectAll(),
                                bt.algos.WeighEqually(),
                                bt.algos.Rebalance()],
                    [mom0,mom1,mom2])
t = bt.Backtest(master, data)
res = bt.run(t)

res.display()
pmorissette commented 9 years ago

Hey @penilessquant,

I don't have time to whip something up right now but I would look at the implementation of the Rebalance algo. You should be able to write your own version that would only rebalance a portion of the position. This is where the capital allocation logic takes place so it would make sense to add your strategy logic there.

Hope this helps, Phil

penilessquant commented 9 years ago

Hi Phil, I hope you don't mind me posting a reply here, especially since I am new to python.

I had a good think about it, and what I was trying to do was to rebalance the oldest unrebalanced chunk. i.e say I am breaking the portfolio into 3 buckets, on the first month, I pick the momentum strategy as above and lets say I get gs and msft as the stocks to pick, now the next month I get aapl and c, in the following month, what I want to do is to only rebalance the gs and msft, i.e. I am not rebalancing 1/3 of the whole portfolio, but the oldest 1/3. This would require some kind of a memory in the Rebalance algo.

Looking at some of your examples, I realised that I can use plain vanilla bt, and just wrap my strategy into a helper function, the below seems to do what I want it to do ( except for the first couple of time periods when the 'chunking' hasn't had enough time periods to kick in, I will read more of the code and hopefully will be able to figure out how to throw away that part of the backtest )

import bt
import pandas as pd

class RunOnDataFrameDates(bt.Algo):
    def __init__(self, dates):
        self.dates = pd.to_datetime(dates)

    def __call__(self, target):
        return target.now in self.dates

class OrderedWeights(bt.Algo):
    def __init__(self, weights):
        self.target_weights = weights

    def __call__(self, target):
        target.temp['weights'] = dict(zip(target.temp['selected'], self.target_weights))
        return True

def getStrategySet(date_list, numbuckets, nummonths, stratname):
    strat_list = []
    for x in range(0, numbuckets):
        momname = stratname + 'mom' + str(x)
        mom0 = bt.Strategy(momname, [RunOnDataFrameDates(date_list[x::numbuckets]),
                        bt.algos.SelectAll(),
                        bt.algos.SelectMomentum(2,pd.DateOffset(months=nummonths)),
                        OrderedWeights([0.7, 0.3]),
                        bt.algos.Rebalance()])
        strat_list.append(mom0)
    return strat_list

def momentum_algo(data, date_mm, numbuckets,nummonths, name):
    strat_list = getStrategySet(date_mm, numbuckets,nummonths,name)
    s = bt.Strategy(name, [bt.algos.RunMonthly(),
                           bt.algos.SelectAll(),
                           bt.algos.WeighEqually(),
                           bt.algos.Rebalance()],
                    strat_list)
    return bt.Backtest(s, data)

data = bt.get('aapl,msft,c,gs,ge')
date_mm = data.to_monthly().index.values

mom2 = momentum_algo(data,date_mm,2,10, '2buckets')
mom3 = momentum_algo(data,date_mm,3,10, '3buckets')
mom4 = momentum_algo(data,date_mm,4,10, '4buckets')

res = bt.run(mom2,mom3,mom4)

res.display()