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
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
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())
    
    context.rebalance_days = 10
    context.current_days_counted = 0
    
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

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
67
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
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
    
    
    context.rebalance_date = None
    context.rebalance_days = 10

        
        
            
def handle_data(context, data):
    record_leverage(context, data)
    
    if context.rebalance_date != None:
        next_date = context.rebalance_date + timedelta(days=context.rebalance_days)
    
    if context.rebalance_date == None or get_datetime() == next_date:
        context.rebalance_date = get_datetime()
    else:
        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 = 0.5 / buy_length if buy_length != 0 else 0 
    short_weight = -0.5 / 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 record_leverage(context, data):
    P = context.portfolio    
    market_value = sum(data[i].price * abs(P.positions[i].amount) for i in data)    
    record(leverage=market_value / max(P.portfolio_value, 1))
          
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
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
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())
    
    context.rebalance_days = 10
    context.current_days_counted = 0
    
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
    """
    #: 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

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
108
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
from datetime import datetime, timedelta
import numpy as np

def initialize(context):
    # Setting universe between top 99.5% and 100% of stocks by Dollar Volume
    set_universe(
        universe.DollarVolumeUniverse(floor_percentile=99.5, ceiling_percentile=100))
    # Setting the number of stocks to long and to short
    context.stocks_to_long  = 5
    context.stocks_to_short = 5
    context.rebalance_date  = None
    context.rebalance_days  = 10

    schedule_function(summary, date_rules.every_day(), time_rules.market_close())
            
def handle_data(context, data):
    if get_open_orders():
        return
    
    record_leverage(context, data)
    
    if context.rebalance_date != None:
        next_date = context.rebalance_date + timedelta(days=context.rebalance_days)
    
    if context.rebalance_date == None or get_datetime() == next_date:
        context.rebalance_date = get_datetime()
    else:
        return
    
    # Getting 200 days worth of historical data
    # For an intraday strategy based off minutely data, change '1d' to '1m'
    historical_data = history(200, '1d', 'price')

    # Difference between the 50-day mean and 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 diffs by removing any NaNs and sorting in ascending order
    diff = diff.dropna()
    diff.sort()

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

    # Weights for securities
    buy_length   = min(context.stocks_to_long,  len(buys))
    short_length = min(context.stocks_to_short, len(sells))
    #buy_weight  = 0.5 / buy_length    if buy_length   != 0 else 0 
    buy_weight   = 1.38 / buy_length   if buy_length   != 0 else 0 
    short_weight = -0.5 / 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 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 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 in that security
        else:
            order_target(sym, 0)

    #record(wlong=buy_weight, wshort=short_weight)

def record_leverage(context, data):
    P = context.portfolio    
    market_value = sum(data[i].price * abs(P.positions[i].amount) for i in data)    
    record(leverage = market_value / max(P.portfolio_value, 1))
    record(leverage2 = context.account.leverage)
    

def summary(context, data):
    '''
        Summary processing

        https://www.quantopian.com/posts/run-summary
    '''
    #  - - - - - - - -  Options  - - - - - - - -
    cancel_partials  = 1  # Cancel orders after being partially filled.
    daily_live       = 1  # Log summary at end of each day when live.
    drawdown_returns = 1  # Custom chart returns based on profit/drawdown.
                          #  At 1/2015 Q returns are profit/init_cash.
                          #  Custom chart only accepts 5 items so watch for that.
    filter_zeros     = 0  # 0 or 1 to filter out those with no buy and no sell.
                          # In get_fundamentals for example there can be over
                          #   a thousand stocks processed with many not traded.
    leverage_alert   = 1  # Log new lowest cash points reached.
    percent_results  = 0  # Express results like 270.1% instead of x2.701

    if 'books' not in context:
        '''
            Preparation. Initialize one time.
        '''
        cash = context.portfolio.starting_cash
        context.books = {   # Starting cash value from GUI or live restart...
            'cash_low'      : cash,
            'shares'        : 0,       # Overall number of shares owned.
            'count_buy'     : 0,       # Overall buy count, number of shares.
            'count_sell'    : 0,       # Overall sell count.
            'cnt_buy_evnts' : 0,       # Overall buy events count.
            'cnt_sel_evnts' : 0,
            'summary_print' : 0,       # Use to force print when you like.
            'commissions'   : 0,       # Commissions.
            'sids_seen'     : [],      # For set_universe since dynamic.
            'orders'        : {},      # Keep orders for accounting,
        }                              #   orders not completely filled yet.
        b = context.books

        # Environment   First/last dates and
        #   Arena: backtest or live.  Mode: daily or minute.
        env = get_environment('*')
        b['first_trading_date'] = str(env['start'].date())
        b['last_trading_date']  = str(env['end']  .date())
        b['last_trading_time']  = str(env['end']  .time())
        b['arena'] = env['arena']
        b['mode']  = env['data_frequency']

        if b['arena'] == 'live':
            b['arena'] = 'paper'
        elif b['arena'] != 'backtest': # ie like 'IB'
            b['arena'] = 'live'

        # Show environment at the beginning of the run
        prep_prnt = ' {}\n  {}  {} to {}  {}  {}\n'.format(
            b['arena'],
            b['mode'],
            b['first_trading_date'],
            b['last_trading_date'],
            '   $' + '%.0f' % context.portfolio.starting_cash,
            '  First bar stocks ({}) ...'.format(len(data)),
        )

        # Show current universe once
        for sec in data:
            if isinstance(sec, basestring):
                continue   # Skip any injected fetcher string keys.
            prep_prnt += (sec.symbol + ' ')
        log.info(prep_prnt)

    '''
        Prepare individual securities dictionaries
          with dynamic set_universe, fetcher, IPO's appearing etc.
    '''
    b = context.books   # For brevity.
    for sec in data:
        if isinstance(sec, basestring):
            continue   # Skip any injected fetcher string keys.
        sym = sec.symbol
        if sym in b:
            continue
        if sec not in b['sids_seen']:
            # Scenarios with price missing ...
            price = data[sec].price if 'price' in data[sec] else 0
            b['sids_seen'].append(sec)
            b[sym] = {
                'init_price'    : price,  # Save for summary.
                'price'         : price,  # Most recent price.
                'cash_low'      : 0,      # Lowest level of cash.
                'balance'       : 0,      # For individual 'x' return.
                'shares'        : 0,
                'count_buy'     : 0,      # Individual buy number of shares.
                'count_sell'    : 0,
                'cnt_buy_evnts' : 0,      # Individual buy events count.
                'cnt_sel_evnts' : 0,
                'return'        : 0,      # Return calculated.
                'analog'        : 0,      # Analog relative return ratio.
            }
    cash_now = context.portfolio.cash
    if cash_now < b['cash_low']:
        b['cash_low'] = cash_now
        # An alert for negative cash unless you like "leverage"
        if leverage_alert and cash_now < 0.:
            log.info('cash low ' + str(b['cash_low']))
    '''
        Custom chart of drawdown returns, profit/drawdown
    '''
    if drawdown_returns:
        cash_now  = context.portfolio.cash
        cash_strt = context.portfolio.starting_cash
        portfolio = context.portfolio.portfolio_value
        drawdown  = cash_strt - b['cash_low']
        profit    = portfolio - cash_strt
        dreturns  = 0 if not drawdown else profit / drawdown
        qreturns  = profit / cash_strt    # Can be useful for easy visual compare,
        record(QReturn = 100 * qreturns)  #   overlay, same as standard chart.
        record(MaxSpentReturn = 100 * dreturns)
    '''
        Accounting. Update the numbers, manage orders if any.
    '''
    accounting = {}  # Local, any orders ready to be counted.

    # Read open orders
    for security, oo_for_sid in get_open_orders().iteritems():
        for order_obj in oo_for_sid:
            # If an order not seen before, add for tracking
            if order_obj.id not in b['orders']:
                b['orders'][order_obj.id] = order_obj.filled

    for id in b['orders']:  # Take a look at current orders saved.
        o = get_order(id)   # Current order, might have been updated.

        # If filled is not zero, account for it
        if o.filled != 0:
            accounting[id] = o    # Set to account for filled.

            # On partial fills, a new order is automatically
            #   generated for the remainder.
            # Bugbug: The only way I could make sense of things so far ...
            # If filled is not amount (shares), that's a partial fill,
            #   cancelling remainder to simplify life. Unsure.
            if o.filled != o.amount and cancel_partials:
                cancel_order(id)

    for id in accounting:    # Do any accounting, into books{}.
        sec = accounting[id]['sid']
        sym = sec.symbol
        if sec in data and 'price' in data[sec]: # Update price if available.
            b[sym]['price'] = data[sec].price
        commission          = accounting[id]['commission']
        filled              = accounting[id]['filled']  # Number filled, sell neg.
        # ToDo: Don't know the official actual fill prices.
        transaction         = filled * b[sym]['price']  # Last known price.
        b[sym]['shares']   += filled      # The transaction on sell is negative
        b[sym]['balance']  -= transaction #   so this line adds to balance then.
        b[sym]['balance']  -= commission
        b['commissions']   += commission

        if filled > 0:                          # Buy
            b[sym]['cnt_buy_evnts'] += 1
            b[sym]['count_buy']     += filled
        elif filled < 0:                        # Sell
            b[sym]['cnt_sel_evnts'] += 1
            b[sym]['count_sell']    += abs(filled)

        del b['orders'][id]    # Remove from the list, accounting done.

        # Keep track of lowest cash per symbol
        if b[sym]['balance'] < b[sym]['cash_low']:
            b[sym]['cash_low'] = b[sym]['balance']
    '''
        Show summary if last bar
    '''
    last_bar_now = 0
    if not b['summary_print']:
        if context.books['arena'] in ['paper', 'live'] and daily_live:
            # When paper or live log summary every day end of day.
            # Assumes schedule is set to every_day().
            last_bar_now = 1
        elif context.books['arena'] == 'backtest':
            # Flag for summary output if last bar now
            bar = get_datetime()
            if b['last_trading_date'] == str(bar.date()):
                if b['mode'] == 'daily':
                    last_bar_now = 1
                elif b['mode'] == 'minute':
                    # Not ideal.
                    # How to print in minute mode only on last bar simply?
                    log.info('Algo time: ' + str(bar.time()))
                    last_bar_now = 1
    '''
        Summary output to the logging window
    '''
    if last_bar_now or b['summary_print']:
        # Independent copy of context.books using dict() in case summary print
        #   is set to happen more than once in a run, due to concats below (+=)
        b    = dict(context.books)
        done = {}   # Protect against any listed twice.

        # Some overall values by adding individual values
        for sec in b['sids_seen']:
            if sec in done:
                continue

            # There's a problem with a dynamic run where a security can have
            #   dropped out of the picture, all sold, not in current universe,
            #   and its price is no longer accessible. Need help from Q.
            if sec in data and 'price' in data[sec]:
                b[sec.symbol]['price'] = data[sec].price
            sym = sec.symbol
            b['count_buy']     += b[sym]['count_buy']
            b['count_sell']    += b[sym]['count_sell']
            b['cnt_buy_evnts'] += b[sym]['cnt_buy_evnts']
            b['cnt_sel_evnts'] += b[sym]['cnt_sel_evnts']
            b['shares']        += b[sym]['shares']
            done[sec] = 1

        portfolio    = context.portfolio.portfolio_value
        init_cash    = context.portfolio.starting_cash
        cash_now     = context.portfolio.cash
        cash_low     = b['cash_low']
        cash_profit  = cash_now - init_cash
        shares_value = portfolio - cash_now
        spent        = 0
        if init_cash > cash_low:
            spent    = init_cash - cash_low
        else:
            spent    = cash_now - init_cash  # ??  case of short-selling
        spent_prcnt  = ' ({}%)'.format(int(100 * (spent / init_cash)))
        tot_profit   = cash_profit + shares_value
        qntp_return  = (portfolio - init_cash) / init_cash
        draw_return  = 0.
        if b['count_buy'] or b['count_sell']:   # If there were trades
            draw_return = tot_profit / spent
        draw_float   = draw_return
        rel_word     = ' Prcnt' if percent_results else ' Ratio'
        if percent_results:
            if qntp_return < 10.:
                qntp_return = '{}%'.format(float('%.2f' % (100 * qntp_return)))
                draw_return = '{}%'.format(float('%.2f' % (100 * draw_return)))
                draw_float  = float('%.3f' % (100 * draw_float))
            elif qntp_return < 100.:
                qntp_return = '{}%'.format(float('%.1f' % (100 * qntp_return)))
                draw_return = '{}%'.format(float('%.1f' % (100 * draw_return)))
                draw_float  = float('%.1f' % (100 * draw_float))
            else:
                qntp_return = '{}%'.format(int(100 * qntp_return))
                draw_return = '{}%'.format(int(100 * draw_return))
                draw_float  = int(100 * draw_float)
        else:
            if qntp_return < 10.:
                qntp_return = 'x' + '%.2f' % qntp_return
                draw_return = 'x' + '%.2f' % draw_return
            elif qntp_return < 100.:
                qntp_return = 'x' + '%.1f' % qntp_return
                draw_return = 'x' + '%.1f' % draw_return
            else:
                qntp_return = 'x' + '%.0f' % qntp_return
                draw_return = 'x' + '%.0f' % draw_return
        if qntp_return == '0.00%' or qntp_return == 'x0.00':
            qntp_return = '0'
        if draw_return == '0.00%' or draw_return == 'x0.00':
            draw_return = '0'

        v1 = {  # values
            'pflo': '%.0f' % portfolio,
            'icsh': str(int(init_cash)),
            'untd': '0' if int(cash_low) <= 0 else str(int(cash_low)),
            'ncsh': '0' if int(cash_low) >= 0 else str(int(cash_low)),
            'down': str(int(spent)),
            'cshp': str(int(cash_profit)),
            'totp': '%.0f' % tot_profit,
            'qret': qntp_return,
            'dret': draw_return,
        }
        v2 = {
            'cbuy': str(b['count_buy']),
            'csel': str(b['count_sell']),
            'shnw': str(b['shares']),
            'shvl': '%.0f' % shares_value,
            'cmsn': '%.0f' % b['commissions'],
            'cshn': '%.0f' % cash_now,
        }
        # Widths of the longest for columns
        w1 = 0; w2 = 0
        for v in v1:
            len_v_str = len(str(v1[v]))
            if len_v_str > w1:
                w1 = len_v_str
        for v in v2:
            len_v_str = len(str(v2[v]))
            if len_v_str > w2:
                w2 = len_v_str
        for v in v1:  # Padding
            v1[v] = v1[v].rjust(w1)
        for v in v2:
            v2[v] = v2[v].rjust(w2)
        '''
            Portfolio: 342690                                                        `
         Initial Cash:   1000                          Buys: 217225 (147 trades)     `
          Unused Cash:      0                         Sells: 77559 (11 trades)       `
             Neg Cash:    -21                   Commissions: 1523                    `
             Drawdown:   1021 (102%)             Shares Now: 139666                  `
          Cash Profit:   -991                  Shares Value: 342681                  `
         Total Profit: 341690  w/ shares               Cash: 9                       `
              QReturn:   x342  Profit/InitCash                                       `
               Return:   x335  Profit/Drawdown                                       `
        2015-01-02 summary:616 INFO      200 average initial cash, 5 securities      `
               Relativ Buy| By|Sl By|Sl Price Draw Cash Shrs Shrs                    `
         Symbol Ratio Hold Count Evnts Strt|Now Down Now Now Value                   `
         RDNT x32.0  2.2 16849|16849  20|2     3|9     -39502   39768       0       0`
         NVAX x9.71  2.0 23042|23009  18|3     2|6     -34756   10439      33     190`
         EDAP x21.2  0.5 143855|4419  34|1     2|2    -205336 -205336  139436  341618`
         ACHN x61.8  0.6 22133|22133  38|2     8|13    -49470   96132       0       0`
         HGSH x1548 10.8 11346|11149  37|3     0|4      -1019   58273     197     872`
               
        '''
        pflo   = '{m1:>15} {m2}'.format(
            m1 = 'Portfolio:',     m2 = v1['pflo'] )
        icsh   = '{m1:>15} {m2:<34}{m3}{m4}'.format(
            m1 = 'Initial Cash:',  m2 = v1['icsh'],
            m3 = 'Buys: ' + v2['cbuy'],
            m4 = ' (' + str(b['cnt_buy_evnts']) + ' trades)' )
        ucsh   = '{m1:>15} {m2:<33}{m3}{m4}'.format(
            m1 = 'Unused Cash:',   m2 = v1['untd'],
            m3 = 'Sells: ' + v2['csel'],
            m4 = ' (' + str(b['cnt_sel_evnts']) + ' trades)' )
        ncsh   = '{m1:>15} {m2:<27}{m3}'.format(
            m1 = 'Neg Cash:',      m2 = v1['ncsh'],
            m3 = 'Commissions: ' + v2['cmsn'] )
        dcsh   = '{m1:>15} {m2:<28}{m3}'.format(
            m1 = 'Drawdown:',      m2 = v1['down'] + spent_prcnt,
            m3 = 'Shares Now: ' + v2['shnw'] )
        pcsh   = '{m1:>15} {m2:<26}{m3}'.format(
            m1 = 'Cash Profit:',   m2 = v1['cshp'],
            m3 = 'Shares Value: ' + v2['shvl'] )
        ttlp   = '{m1:>15} {m2:<34}{m3}'.format(
            m1 = 'Total Profit:',  m2 = v1['totp'] + '   w/ shares',
            m3 = 'Cash: ' + v2['cshn'] )
        qret   = '{m1:>15} {m2}'.format(
            m1 = 'QReturn:',
            m2 = v1['qret'] + '   Profit/InitCash' )
        dret   = '{m1:>15} {m2}'.format(            # drawdown return
            m1 = 'Return:',
            m2 = v1['dret'] + '   Profit/Drawdown')
        outs        = [pflo, icsh, ucsh, ncsh, dcsh, pcsh, ttlp, qret, dret]
        out_summary = '_\r\n'
        line_len    = 80      # Length
        for o in outs:
            out_summary += (o + ' ' * (line_len - len(o)) + '`\r\n')

        # -------------------------------
        # Individual securities detail
        # -------------------------------
        out_content_collections = []
        count_sids    = len(b['sids_seen'])
        avg_init_cash = init_cash / len(b['sids_seen'])
        sec_word      = ' security' if count_sids == 1 else ' securities'
        sec_strng     = '  ' + '%.0f' % int(avg_init_cash) \
                             + ' average initial cash, ' + str(count_sids) + sec_word
        out_content   = (sec_strng + '    `\r\n').rjust(line_len - 26)
        lines_out     = 11    # Log in clumps to stay under logging limits.
        count_lines   = 0
        if filter_zeros:
            count_lines += 1
            out_content += '.\r\n\tZero buy/sell filtered out `\r\n\r\n.'
        header1 = [
        '',     'Relativ','Buy|','By|Sl','By|Sl','Price',   'Draw','Cash','Shrs','Shrs ']
        header2 = [
        'Symbol',rel_word,'Hold','Count','Evnts','Strt|Now','Down',' Now',' Now','Value']
        contents_list = [header1, header2]    # To be lines per sym as a list of lists.

        # The list to process
        sids_to_process = []
        for sec in sorted(b['sids_seen']):
            sym = sec.symbol
            if filter_zeros and not b[sym]['count_buy'] and not b[sym]['count_sell']:
                continue
            sids_to_process.append(sec)

        # Individual return
        return_list = []
        for sec in sids_to_process:
            sym = sec.symbol
            # There's a problem with balance, it is tracked based on
            #   last known price, and when filled, no current way to obtain
            #   the actual fill price.
            # For that reason, there can be discrepancies, sometimes major.
            # To Q, request made to provide us with fill_price in the object id.
            cash_now   = b[sym]['balance']      # Balance started at zero
            cash_low   = b[sym]['cash_low']     # Maximum expended
            shares_val = b[sym]['shares'] * b[sym]['price']
            outputs    = shares_val + cash_now
            cash_pnl   = 0   # Cash profit and loss
            if avg_init_cash > cash_low:             # Typical trading
                cash_pnl = avg_init_cash - cash_low
            else:
                cash_pnl = cash_now - avg_init_cash  # ?? Case of short-selling, unsure
            if (b[sym]['count_buy'] or b[sym]['count_sell']) and avg_init_cash:
                b[sym]['return'] = outputs / cash_pnl
                return_list.append(b[sym]['return'])

        if not return_list:
            if not avg_init_cash:
                log.info('Odd, no avg_init_cash, aborting summary')
            else:
                schedule = '        ' + \
                'schedule_function( \n\t\tsummary, date_rules.every_day()'
                minute_note = ',\n    especially when in minute mode, '
                if b['mode'] == 'daily':
                    schedule   += '\n        )'
                    minute_note = '.\n    '
                else:
                    schedule += ', time_rules.market_close()\n        )'
                log.info(
                    '.\n  No buys and no sells. If unexpected, check placement' + \
                    ' of calls to summary,\n    after any orders and before any' + \
                    ' returns and/or the scheduling for it' + \
                    minute_note + 'like summary(context, data) or\n' + \
                    schedule    + '\n\tAborting summary()'
                )
            return

        # Multiplication factor
        mult_factor  = 0
        shift        = 0    # Up/dn to move value to line up with overall.
        avg_of_list  = 0    # Taking avg as an analog of draw_float.
        return_list  = sorted(return_list)
        lowest       = return_list[0]
        list_shifted = []
        if lowest < 0:
            # Shift upward to avoid zero-division,
            #   in case some are negative, then each back down.
            shift = 0 - lowest
            for r in return_list:
                list_shifted.append(r + shift)
            avg_of_list = sum(list_shifted) / len(list_shifted)
            mult_factor = draw_float / avg_of_list
        else:
            avg_of_list = sum(return_list) / len(return_list)
            mult_factor = draw_float / avg_of_list

        # Normalize x values proportionally compared to overall x value
        for sec in sids_to_process:
            sym    = sec.symbol
            analog = 0.
            value  = (b[sym]['return'] * mult_factor) - shift
            if value == 0:   # like 0.00
                analog = '0'
                continue
            if percent_results:
                if value < 10.:
                    analog = '{}%'.format(float('%.2f' % value))
                elif value < 100.:
                    analog = '{}%'.format(float('%.1f' % value))
                else:
                    analog = '{}%'.format(int(value))
            else:
                if value < 10.:
                    analog = 'x' + '%.2f' % value
                elif value < 100.:
                    analog = 'x' + '%.1f' % value
                else:
                    analog = 'x' + '%.0f' % value

            b[sym]['analog'] = analog

        # Set values
        for sec in sids_to_process:
            sym = sec.symbol
            init_price = b[sym]['init_price']
            if init_price:
                buy_hold = '%.1f' % ((b[sym]['price'] - init_price) / init_price)
                if buy_hold == '-0.0' or buy_hold == '0.0':
                    buy_hold = '0'
            content = [
                sym,
                ' ' + str(b[sym]['analog']),
                buy_hold,
                str(b[sym]['count_buy']) + '|' \
                    + str(b[sym]['count_sell']),
                str(b[sym]['cnt_buy_evnts']) + '|' \
                    + str(b[sym]['cnt_sel_evnts']),
                '%.0f' % init_price + '|' + '%.0f' % b[sym]['price'],
                int(b[sym]['cash_low']),
                int(b[sym]['balance']),
                b[sym]['shares'],
                int(b[sym]['shares'] * b[sym]['price'])
            ]
            # Collect lines per sym as a list of lists
            contents_list.append(content)

        # Set widths
        col_widths = {}
        for i in range(len(contents_list[0])):
            col_widths[i + 1] = 7       # Defaults
        col_widths[1] = 6               # Symbol
        col_widths[3] = 6               # Buy|Hold
        for line_list in contents_list:
            ec = 1  # element count
            for element in line_list:
                if len(str(element)) > col_widths[ec]:
                    col_widths[ec] = len(str(element)) # Set width to largest seen.
                ec += 1

        # Piece together the output lines formatted.
        line_c = 0
        for line in contents_list:
            out_line = ''
            line_c  += 1  # Line count
            cc       = 1  # Column count
            for column in line:
                if cc in [4, 5, 6] or line_c in [1, 2]:
                    out_line += str(column).center(col_widths[cc] + 1)
                else:
                    column = str(column) + ' ' if cc == 3 else column
                    out_line += str(column).rjust(col_widths[cc] + 1)
                cc += 1

            out_content += (out_line + ' ' * (line_len- len(out_line)) + '`\r\n')
            count_lines += 1

            # Backticks at the end of line are for replace-all in an editor
            #   later after copy/paste, since new lines are gone at least on Windows.
            #   Unfortunate to not be able to copy and paste results easily.

            # Decide when to tuck a group away for later and start a new group,
            #   due to logging limits, using modulus (remainder).
            if count_lines % lines_out == 0:
                out_content_collections.append(out_content)
                out_content = '_\r\n'       # Restart a group.

        if count_lines % lines_out != 0:    # A few remaining lines.
            out_content_collections.append(out_content)

        # Log output
        log.info(out_summary)   # The top, general overall output first

        # Log stored groups
        for occ in out_content_collections:
            log.info(occ)

        out_content = '(symbol ratios adjusted proportionally to overall)'.rjust(20)

        # Add any other content you want ---------------------------
        #out_content += '_\n' # Underscore to a new line for left alignment,
                              #   '\n' by itself would be ignored/dropped.
        # Some variables or whatever you might want to add ...
        out_content += ''

        log.info(out_content)

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
5
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
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=90, 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 = 10
    context.stocks_to_short = 10
    
    
    context.rebalance_date = None
    context.rebalance_days = 10

        
        
            
def handle_data(context, data):
    record_leverage(context, data)
    
    if context.rebalance_date != None:
        next_date = context.rebalance_date + timedelta(days=context.rebalance_days)
    
    if context.rebalance_date == None or get_datetime() == next_date:
        context.rebalance_date = get_datetime()
    else:
        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 = 0.5 / buy_length if buy_length != 0 else 0 
    short_weight = -0.5 / 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 record_leverage(context, data):
    P = context.portfolio    
    market_value = sum(data[i].price * abs(P.positions[i].amount) for i in data)    
    record(leverage=market_value / max(P.portfolio_value, 1))
          

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
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
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())
    
    context.rebalance_days = 10
    context.current_days_counted = 0
    
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

There was a runtime error.

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

Clone Algorithm
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
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())
    
    context.rebalance_days = 10
    context.current_days_counted = 0
    
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

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
5
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
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
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