Back to Community
Quantopian Tutorial with Sample Momentum Algorithm - Lesson 1: The basics of the IDE

2/16/2016 This tutorial is out-dated, please view the new versions here: https://www.quantopian.com/posts/quantopian-tutorials

Hey guys,

We just finished up the first lesson in our Quantopian new user webinar series so thanks to those who joined and for those who didn't, we'll have more in the future and will be posting up a recording to this one shortly so you can see it as well.

We covered the basics of the IDE, what you can and cannot do in Python versus IDE, and a walkthrough of a simple momentum trading strategy

The rest of the tutorial series:

Some notes that you guys will find helpful:

Further Work

For those who are looking for more, take the algorithm I've attached to this post and play around with the moving average periods to create an algorithm with highest % returns

- From 1/1/2007-11/1/2014
- Convert it to use schedule_function instead of get_datetime()
- Use no leverage.

I encourage you to post your answers here and let's see what you get!

Thanks to @David Edwards for the algorithm

Seong

Clone Algorithm
949
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: 560eafda048dc110857cdfbd
There was a runtime error.
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.

24 responses

Thanks Seong! very helpful

Hi Seong, looking forward for the recording. Many thanks!

The recording has been uploaded here: http://bit.ly/1xAHAsp!

Hi, could you please give a hint how not to use leverage? Cannot find a solution. Thank you in advance!

Mikhail,
The attached backtest records the leverage and has it adjusted down to roughly 1. The leverage used is the sum of the absolute values of the numerators in lines 53 & 54, so to constrain the leverage I changed those numerators to +-0.5 instead of +-1.0.

David

Clone Algorithm
59
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: 549c8e1e715970090223ef6b
There was a runtime error.

David, many thanks for your reply and source version provided! If I correctly understood, but control of long/short ratio we are able to tune our risk/reward parametres?

That is the basic idea Mihail, you can vary the market exposure and leverage by adjusting the numerators in line 53/54. If you want to be more long, give the positive weight a larger absolute value than the negative weight. Keep in mind that rounding error does creep in, but you are correct about what is going on there.

I know you are defining rebalance days and date and you are checking it within handle_Data and reassigning but wasn't clear where you are rebalancing after 10 days in algo and how you are restricting it to trade once per day.I know you can use the rebalance by using a counter as well but wanted to understand your logic.thanks

Hi Amit,

I've created a new algorithm that uses schedule_function() which we cover in-depth in the follow up tutorial to this one: Quantopian Tutorial Series Lesson 2.

This should make the rebalance more clear where I have two methods: one to increment the number of days everyday and another that actually does the rebalancing. But here, the rebalancing algorithm checks at the beginning that

context.current_days_counted % 10 (context.rebalance_days) == 0  

before rebalancing. Let me know if you have any questions on that.

Clone Algorithm
949
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: 54a07b5e21dc7808fe4cfe8e
There was a runtime error.

In the D.E. code two backtests upward, the return shows 3.7%.
However since it only utilized about 10% of initial capital, your algo is actually better than it appears to be.
In my opinion that worse representation is not good, others would say that is just fine: https://www.quantopian.com/posts/returns. Any thoughts about that? Feel free to post them there.

I bumped up your buy_weight from .5 to 1.38 to approach full use of initial capital and the output went from 3.7% to almost four times higher, 14.4%.
You can also nearly double that to 28% with these two lines:

def handle_data(context, data):  
    if get_open_orders():  
        return  

I was able to see what was going on because of Run Summary, it is added to this backtest.
Note on the chart your peak was about 37%.
Also I see you calculated the leverage manually, recently Q added context.account.leverage for that to be easier.
Mikhail, I'm not aware of any way to prohibit leverage, it is being considered.
I would like to know what happens on IB if one tries to order more than cash available without a margin account, reject or partial fill. Best for this environment to match that in my opinion.

Clone Algorithm
82
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: 54b799a49f8a690975799811
There was a runtime error.

Dear all

Appreciate some advice. Whats does the following code do? Does it Sort and buy only stocks where its price is just above the slow moving average which in this case is above 200MA? Will it buy stock when current price is below its 200MA but 50MA is still above 200MA?

Or does it sort and pick strong stocks that has a very strong 50 MA compare to a stock with weak 50MA? Bec the 50 mean is much greater than the 200 day resulting in a bigger difference.

Many thks

#: If the 50 mean is greater than the 200 day mean, add it to the buy  
#: Vice versa for the shorts  
buys = diff[diff > 0]  
sells = diff[diff < 0]

#: Select securities just above and below the slow moving average (the diff)
buys.sort()
sells.sort(ascending=False)
buys = buys.iloc[:buy_length] if buy_weight != 0 else None
sells = sells.iloc[:short_length] if short_weight != 0 else None

In these lines, you are deciding what stocks to buy and sell. If the difference is positive, it buys. If it's negative, the stock will be sold.

#: If the 50 mean is greater than the 200 day mean, add it to the buy  
#: Vice versa for the shorts  
buys = diff[diff > 0]  
sells = diff[diff < 0]  

The difference is calculated on line 38 as the ratio between the 50-day moving average and the 200-day moving average
diff = past_50_day_mean / past_200_day_mean - 1

So the algorithm will buy if the short moving average (in this case 50 days) is greater than the longer moving average (200 days), suggesting the stock price is trending upward and you ride the wave.

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.

Thk you very much

I did a test on the code from 2011 to date and why is the nos of transaction so low? I tried increasing the nos of stock short and long to total 20 and increasing the universe to be between the top 90% and the top 100% of stocks by Dollar Volume

But there is only 8 transaction and i am puzzled?

Thank you

Clone Algorithm
4
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: 553ae0cf914e240d1d627d26
There was a runtime error.

Maybe code in the example did work correctly previously but now I get transactions only for the first few weeks or so. to fix I changed if context.rebalance_date == None or get_datetime()== next_date: to
if context.rebalance_date == None or get_datetime()>= next_date:

Is it user error on my part, or does this algorithm only trade in the very beginning? The rebalance doesn't seem to be happening for me.

Alx,

Are you running backtests directly from the clone?

Bogdan Kasp solution is good, because the original algo is stopped running on the first next_day which was set on Saturday or Sunday (or stock exchange holiday).
From such date get_datetime() == next_date condition was false permanently because next_date was skipped and never set new date again.

Here's the newest backtest that uses schedule_function to clear up any rebalance date confusion

Clone Algorithm
949
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: 560eafda048dc110857cdfbd
There was a runtime error.

Here's the newest version that uses schedule_function to clear up any date confusion.

Clone Algorithm
949
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: 560eafda048dc110857cdfbd
There was a runtime error.

The latest version of this algorithm is bailing out on lines 33-34:

if get_open_orders():  
    return  

There are orders that stay open and never close. This algorithm will execute a set of trades in the first tick and then early-out for the rest of the run.

Jack,

To prevent that from happening, you can cancel all orders at the end of the day like:


from datetime import datetime, timedelta  
import numpy as np

def initialize(context):  
    #: Settinng our universe to be between the top 99.5% and the top 100% of stocks by Dollar Volume  
    set_universe(universe.DollarVolumeUniverse(floor_percentile=99.5, ceiling_percentile=100))  
    #: Setting the number of stocks that we want to long and the number of stocks that we want to short  
    context.stocks_to_long = 5  
    context.stocks_to_short = 5  
    schedule_function(func=rebalance,  
                      date_rule=date_rules.every_day(),  
                      time_rule=time_rules.market_open())  
    schedule_function(func=day_counter,  
                      date_rule=date_rules.every_day(),  
                      time_rule=time_rules.market_close())  
    schedule_function(func=close_orders,  
                      date_rule=date_rules.every_day(),  
                      time_rule=time_rules.market_close(minutes=15))  
    context.rebalance_days = 10  
    context.current_days_counted = 0  
def close_orders(context, data):  
    open_orders = get_open_orders()  
    # open_orders is a dictionary keyed by sid, with values that are lists of orders.  
    if open_orders:  
        # iterate over the dictionary  
        for security, orders in open_orders.iteritems():  
            # iterate over the orders  
            for oo in orders:  
                cancel_order(oo)  
                log.info("Canceling order for %s" % security.symbol)

def day_counter(context, data):  
    """  
    Increments our day counter at the end of day, every day  
    """  
    context.current_days_counted += 1  
def rebalance(context, data):  
    """  
    The logic for rebalancing our algorithm  
    """  
    if get_open_orders():  
        return  
    #: A quick check to see that we're only rebalancing every X days, defined by  
    #: context.rebalance_days  
    if context.current_days_counted % context.rebalance_days != 0:  
        return  
    #: Getting 200 days worth of historical data  
    #: If you wanted an intraday strategy based of minutely data you could change '1d' to '1m'  
    historical_data = history(200, '1d', 'price')

    #: Getting the difference between the 50 day mean and the 200 day mean  
    past_50_day_mean = historical_data.tail(50).mean()  
    past_200_day_mean = historical_data.mean()  
    diff = past_50_day_mean / past_200_day_mean - 1

    #: Cleaning up our diffs by removing any NaNs and sorting it in ascending order  
    diff = diff.dropna()  
    diff.sort()

    #: Recording the stocks that we want to buy and the stocks that we want to sell  
    #: If the 50 mean is greater than the 200 day mean, add it to the buy  
    #: Vice versa for the shorts  
    buys = diff[diff > 0]  
    sells = diff[diff < 0]

    #: Create weights for our securities  
    buy_length = min(context.stocks_to_long, len(buys))  
    short_length = min(context.stocks_to_short, len(sells))  
    buy_weight = 1.0/buy_length if buy_length != 0 else 0  
    short_weight = -1.0/short_length if short_length != 0 else 0 

    #: Select securities just above and below the slow moving average (the diff)  
    buys.sort()  
    sells.sort(ascending=False)  
    buys = buys.iloc[:buy_length] if buy_weight != 0 else None  
    sells = sells.iloc[:short_length] if short_weight != 0 else None

    #: Define a 2% stoploss for each security  
    stops =  historical_data.iloc[-1] * 0.02

    #: Iterate through each security in data  
    for sym in data:

        #: If the security exists in our sells.index then sell  
        if sells is not None and sym in sells.index:  
            log.info('SHORT: %s'%sym.symbol)  
            order_target_percent(sym, short_weight,  
                             stop_price=data[sym].price - stops[sym])

        #: If the security instead, exists in our buys index, buy  
        elif buys is not None and sym in buys.index:  
            log.info('LONG: %s'%sym.symbol)  
            order_target_percent(sym, buy_weight,  
                                 stop_price=data[sym].price + stops[sym])

        #: If the security is in neither list, exit any positions we might have in that security  
        else:  
            order_target(sym, 0)

    record(wlong=buy_weight, wshort=short_weight)  

def handle_data(context, data):  
    pass

Thanks Seong.

One rookie mistake I was making was running the algorithm in Daily mode. This prevented any orders from being filled.

I've pasted the above into an algorithm and ran it through the backtester to share with others.

Looked a little fishy, so I went back to the original algo: https://www.quantopian.com/posts/momentum-strategy-with-a-dynamic-universe

The original algo placed the order short order with "+ stops[sym]" and placed the long order with "- stops[sym]". I made this change along with changing the order type to limit orders:

        #: If the security exists in our sells.index then sell  
        if sells is not None and sym in sells.index:  
            log.info('SHORT: %s'%sym.symbol)  
            order_target_percent(sym, short_weight,  
                             limit_price=data[sym].price + stops[sym])

        #: If the security instead, exists in our buys index, buy  
        elif buys is not None and sym in buys.index:  
            log.info('LONG: %s'%sym.symbol)  
            order_target_percent(sym, buy_weight,  
                                 limit_price=data[sym].price - stops[sym])  

But this only improved the total return to -2%.

Clone Algorithm
4
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: 568b32ac3c7a011177c2752c
There was a runtime error.

Sorry for bringing this post up. Is there any way that I could fix the sound issue on the first video? There is a long delay between the actions and vioce recording.

@Zachary check out the latest tutorials here (with more lessons!): https://www.quantopian.com/posts/quantopian-tutorials