Back to Community
Multi-strategy example

A first attempt to implement a multi-strategy multi-instrument backtest.
I am not a coder and I am just starting out with python here. Feel free to correct or improve.

The idea is to built a "template" that can "weight" different strategies without coding the internals of the strategies.
For example, assuming we have a Trend Following and a Mean Reverting strategy on can allocate 30-70 when VIX rises above a threshold and 70-30 when VIX comes back down.

Unfortunately I don't know how to code daily indicators to work with minute bars, so:
The existing strategies work best for EOD trading (if you clone and run the daily backtest you will see what I mean).
Keep in mind that this is implemented knowing full well what has worked in the past (in other words this is "manually" optimized), so past performance is no indication of future performance.

http://sanzprophet.blogspot.com/

Clone Algorithm
148
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Backtest ID: 509bcebbbeb39219f0000392
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.
19 responses

@Sanz, awesome share!

In this example you're running two strategies in parallel - have you considered combinations where one strategy is a signal or factor that is input into the other? For example, instead of the TAA triggering a trade, maybe it just gates the execution of the MeanRev strategy.

Your code is quite readable, and I like that you added classes for the strategies. Some free (and worth every penny) few python tips for you:

  • Python uses camel case for classes, but lowercase with underscores for everything else. Single word functions are all lower.
  • method signatures separate parameters with comma-space
  • classes and methods are typically documented with docstrings -- triple quoted strings. Code level comments use the hashes.

For example, the beginning of your TAA description would be like this:

class StrategyTAA(Strategy):  
    """TAA stands for Tactical Allocation Strategy (aka. Faber's Ivy Portfolio)  
    On the first day of the month we look at each stock. If it is above it's 200day Moving average  
    we buy. If it's below we sell it. We allocate equally to each stock"""  
    def trade(self, data, context):  
        # do trade here...  
Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

Thanks John.

Nice site, by the way.
Yes, one strategy could trigger the other. There's many ways to use multi-strategy trading.
a. instead of finding uncorrelated assets to trade in a portfolio, create uncorrelated strategies and trade those.
b. Judge conditions and based on these deploy the appropriate of many strategies available as well as control their parameters.
c. Run all the strategies parallel "silently" (as if they traded) and trade the best ones (adaptive)
d. Once you have a strategy with a set of parameters, deploy multiple copies of it with different parameters to increase robustness (not performance) of the base strategy.

What I actually wanted to do was for these End of Day strategies (because that's what they are) to trigger Intra-day strategies that split up the orders and buy on certain rules (wnap rules or liquidity rules, etc) throughout the "buy" day.
I assume we cannot implement daily (or other custom) bars, just yet.

Hi Sanz,

While we have both day and minute bars, we don't have a way for you to stream both through your algorithm. However, you can approximate this by aggregating daily bars in your script when you run a minute bar backtest. Below, I put together a quick example of accumulating the bars in the script. Maybe you can try using it in your multi-strat simulations.

# defaultdict is a dictionary that can have a default value for a missing key  
# so you needn't hassle with initializing for every key (sid in our case)  
from collections import defaultdict

# a simple data object to accumulate the bar data.  
class Bar(object):  
    def __init__(self):  
        self.volume = 0  
        self.price = None  
        self.datetime = None

def initialize(context):  
    context.sids = [sid(3766)]  
    context.day_bars = defaultdict(Bar)  
    context.previousday = None  
def handle_data(data, context):  
    for cur_sid in context.sids:  
        event = data[cur_sid]  
        bar = context.day_bars[cur_sid]  
        # filter out events that are old (if a stock doesn't trade in a bar  
        # the prior bar is repeated. datetime field is the telltale)  
        if bar.datetime is not None and bar.datetime >= event.datetime:  
            continue

        bar.volume += event.volume  
        bar.price = event.price  
        bar.datetime = event.datetime  
    thisday = data[sid(3766)].datetime.day  
    #only trade on new day  
    if context.previousday is not None and thisday != context.previousday:  
        # implement calls to strategies with the bars in context.day_bars  
        pass  
    context.previousday = thisday  

Ok. I read up on Python and it seems that deque might be useful to keep track of a rolling historical window of daily or hourly prices.
Turns out Jonathan Kamens has already gone that route:
https://www.quantopian.com/posts/python-classes-implementing-true-range-and-average-true-range-indicators
I will try to see if I can implement a n-day Average and n-day standard dev from his example.

This might be a stupid question, but in the graph above, how is it that the benchmark went up in 2008 when the market crashed?

Eric - Your question wasn't stupid at all. It alerted us to a bug in our backtester, and we spent most of the day scrambling to correct it. Thank you very much for noticing and pointing it out to us. You can read more about the bug here.

Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

Hi,
I think it is very intersting and useful to be able to backtest multiple strategy switching.
I have a feature request : being able to define sub portfolio of the global portfolio object so that strategies can trade in their own portfolio and be able t access their own pnl . Obviously still be able to access whole portfolio .
Dan, what do you think ? Is that on the roadmap ?

Thanks

Hello Quentin,

That's an interesting idea. For the moment, I'd recommend doing that kind of portfolio management inside your code. We have some rudimentary portfolio limits today - like the amount of capital you start with. Everything beyond that, like your margin requirements, your position limits, etc. are done within the code you write. For now, it's probably best to extend your code to segment your portfolio.

We definitely have a lot of work to do to make that portfolio management easier in the future.

Dan

It's an old strategy and I thought I make it compatible with the current version of the backtester and 3 years later.. it still perform reasonably... any more improvements?

PB

Clone Algorithm
118
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Backtest ID: 55826f640f28fc12503db3c2
There was a runtime error.

apologies, this the out of sample one

Clone Algorithm
118
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Backtest ID: 55827f63618329125b44e43c
There was a runtime error.

@Peter Neat! This is a great example for people wanting to make multi-strategy algos.

Hi Peter,

Thanks for updating this algo. Since you asked, the next step/improvement, in terms of framework, would be to track performance of each individual strategy and adjust weights accordingly.

Hi
For testing pourpose I clone the last algo but the performance (without mod) is much higher 427%... where I'm wrong?
Also, I modify the deprecated code of the second strategy and the performance change again.... 284%?
Apologize me I'm a Phyton and english newbe, but I can't solve myself.
Thanks in advance.

.. this is the code changed around line 80 (new sma, sma200, stdev):

                price_hist1 = data.history(cur_sid, 'price', 5, '1d')  
                price_hist2 = data.history(cur_sid, 'price', 200, '1d')  
                stddev_hist = data.history(cur_sid, 'price', 10, '1d')[:-1]  
                sma = price_hist1.mean()  
                sma200 = price_hist2.mean()  
                stdev = stddev_hist.std()  


                price = data[cur_sid].price  
                #stdev=data[cur_sid].stddev(10)  
                #sma= data[cur_sid].mavg(5)  
                #sma200=data[cur_sid].mavg(200)  

Ummm nobody is interested...

Then I attach a exact clone of the algo of Peter (18 june) .. his return is 2,84, mine today is 4,27 why??????!!!
If I'm wrong, fine, I will fix it. But I want to be sure, before to go on, not a bug is present on the return calculation.

Ciao

Clone Algorithm
8
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Backtest ID: 57f9f340c67b6d104a2d5772
There was a runtime error.

It's very odd indeed Michele. could be caused with Q2 release and I'm happy to look into it to see whether I get the same result and see what the difference is. Have you checked the backtest analyser in the Notebook environment? I would run both backtest through it and see where it deviates.

Hi Peter I'm glad you answer

This is the modified version correcting the deprecated code (still missing line 46 (I dont know how to do it) and 137 (the backtest become very very slow))
and the return this time is 354%!!!
I'm not able to finish the correction to see if is the deprecated code the problem..

Clone Algorithm
0
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Backtest ID: 57fcec11dc81c313852dff4e
There was a runtime error.

I Fixed it but I must say there were some other problems with the algo as it was overleveraged (x2). So Not only adjusted to Q2 code but also made the leverage around 1 (to make algo's comparable I always publish them with leverage of 1) and made sure it functions on 10K as well. The algo looked better then it was... although it generates an alpha at 0.12 which you can isolate by hedging and leverage the hell out of it

Clone Algorithm
34
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Backtest ID: 57ff76222af89312d7e95625
There was a runtime error.

Thanks Peter
Tuning the algo the performance will increase.
I like very much this algo: is multiple then ready to grow, simple, perfect to me to learn python and Q platform.

Cool, keep us posted on the improvements!