Back to Community
Trading Strategy: Moving Average Mean Reversion

Hi All,

I'd like to share with you a simple mean reversion technique that relies on moving averages. In short, the idea is that the mean-reversion signals can be approximated by intersections of different-length moving averages. This is best made clear by the following illustration:

http://i.imgur.com/5oi3yao.png

In which the data is from a 3,000-day dataset of a stock's closing price. In this model, we generally have one "long" moving average and one "short" moving average (in this case, 90 and 30 days, respectively). We trade when these lines intersect, then choosing to buy or sell based on the direction of the trend (whether the short MA is rising or falling). I note that we don't trade exactly when the lines intersect, but rather when they are sufficiently close (by some user-defined metric). While this is not as statistically strong as mean reversion could be, it's a reasonable approximation with plenty of nice properties because of the lag between the two MAs: a reasonable buy/sell strategy with clear signals that translates into having an effective stop-loss from any peak, except for cases of sudden and severe price crashes.

I've attached a backtest that I ran on some tech stocks between 2008 and 2010, with MA periods of 30 and 10 days.

I'm somewhat new to Quantopian and I didn't have much time to write my algorithm, so I apologize for some of the crude techniques I used in my code, which I intend to fix in future versions. I plan to rewrite the part that decides how much to invest (it's currently mostly hand-tuned with guesstimates), to add a more sophisticated stop-loss, and and to improve the heuristic for determining whether a security is trending negatively or positively. I also need to make my algorithm start shorting stocks. There's generally a great deal of calibration to be done to this algorithm. It would also probably be useful to develop some analyses that determine the best MA periods to use.

Feel free to play around with this algorithm and see if you find anything interesting!

Clone Algorithm
590
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
import numpy as np

def initialize(context):
    # list of tradeable securities. Below: an example
    context.tradeables_list = [sid(3951),sid(1637),sid(2673),sid(351),sid(1221),
                               sid(24),sid(5061),sid(3766),sid(1900)]
    # 30% max drawdown
    context.max_drawdown = 0.3
    context.day_counter = 0
    # Historical price database
    context.database = {}
    for security in context.tradeables_list:
        # Initiate an empty list of prices for each security
        context.database[security] = []

def handle_data(context, data):
    context.day_counter += 1
    for security in context.tradeables_list:
        context.database[security].append(data[security]['price'])
    # We need to collect 30 days of data before we start trading.
    if context.day_counter >= 30: 
        for security in context.tradeables_list:
            # past 10 and past 30 prices, respectively
            past10 = (context.database[security][-10:])[::-1]
            past30 = (context.database[security][-30:])[::-1]
            mean10 = np.mean(past10)
            mean30 = np.mean(past30)
            # has the asset been increasing in value for the last three days?
            trend10p = past10[0]>=past10[1]>=past10[2]>=past10[3]
            # or falling?
            trend10n = past10[0]<=past10[1]<=past10[2]<=past10[3]
            
            # If we hold a security, we want to test if it is time to sell.
            # (Securities even with amount 0 may still be listed in portfolio, so it's
            #  necessary to double-check)
            if security in context.portfolio.positions.keys():
                #and context.portfolio.positions[security].amount > 0:
                # Checking that the 10-day moving average is below the 30-day moving average,
                # and that the price of the security has been falling for four days
                if mean10 < mean30*0.95 and trend10p:
                    order(security, -context.portfolio.positions[security].amount)
                # 8% stop-loss
                elif context.portfolio.positions[security].cost_basis*0.92 >=data[security]['price']:
                    order(security, -context.portfolio.positions[security].amount)
                       
            # If we don't hold a security: check if we should buy it.
            # Check if the 10-day moving average is above the 30-day moving average,
            # and that the price of the security has been rising for four days
            elif mean10>mean30*1.05 and trend10n:
                # But if the security appears to be falling in price long-term,
                # then going for mean-reversion might not be a good idea, so we pass.
                if past30[0]<=past30[5]<=past30[10]<=past30[15]:
                    pass
                elif security not in context.portfolio.positions.keys():
                    # The heuristic below is to prevent investing too much in one security.
                    p = data[security]['price']
                    toInvest = (context.portfolio.cash) * (context.max_drawdown**0.75)
                    numShares = max(0,np.round(toInvest/p))
                    order(security, numShares)
There was a runtime error.
9 responses

Pardon the bump, but this is a notebook I made to help visualize like your photo. The variables are accessible as well.

Loading notebook preview...
Notebook previews are currently unavailable.

Hi John,

I spent some time looking over this over, and I have to give it to you: this is a very well calculated and intuitive algorithm.
I was able to make some pretty good improvements to the algorithm's readability and performance.

As far as lines of code goes, I sought out to make some readability improvements by streamlining some of the more rigorous methods using Quantopian builtins. I removed the need for a price database and a 30-day waiting period using the history function. I also used the records as Darrell suggested to track the positions and leverage. In order to ensure the orders went in correctly, I changed those to orders by percentages. After that, I noticed that you had placed a "pass" statement where the algorithm called for a "continue" statement. Without your comments, I definitely would have missed it.

In terms of algorithm logic, I reversed the buy/sell initiations to activate when the price over the last 4 days increases/decreases in order to ride the mean reversion momentum up as opposed to guessing whether it will or not. As an aside, this makes the algorithm more of a hybrid than mean reverting, but I was able to gain some get some good improvements to the sharpe ratio. I also removed a security in your sample that was delisted in 2010 to view the algorithm's performance to date. It performs very well, great job.

Best,
Lotanna Ezenwa

Clone Algorithm
371
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
import numpy as np

def initialize(context):
    # list of tradeable securities. Below: an example
    context.tradeables_list = [sid(3951),sid(1637),sid(2673),sid(351),
                               sid(24),sid(5061),sid(3766),sid(1900)]
    # 50% max drawdown
    context.max_drawdown = .5
    context.day_counter = 0


def handle_data(context, data):
    for security in context.tradeables_list:
            # past 10 and past 30 prices, respectively
            past10 = history(10,'1d','close_price')[security][::-1]
            past30 = history(30,'1d','close_price')[security][::-1]
            mean10 = np.mean(past10)
            mean30 = np.mean(past30)
            # has the asset been increasing in value for the last three days?
            trend10p = past10[0]>=past10[1]>=past10[2]>=past10[3]
            # or falling?
            trend10n = past10[0]<=past10[1]<=past10[2]<=past10[3]
            
            # If we hold a security, we want to test if it is time to sell.
            # (Securities even with amount 0 may still be listed in portfolio, so it's
            #  necessary to double-check)
            if security in context.portfolio.positions.keys():
                #and context.portfolio.positions[security].amount > 0:
                # Checking that the 10-day moving average is below the 30-day moving average,
                # and that the price of the security has been falling for four days
                if mean10 < mean30*0.95 and trend10p:
                    order_target_percent(security, 0)
                # 8% stop-loss
                elif context.portfolio.positions[security].cost_basis*0.92 >=data[security]['price']:
                    order_target_percent(security, 0)
                       
            # If we don't hold a security: check if we should buy it.
            # Check if the 10-day moving average is above the 30-day moving average,
            # and that the price of the security has been rising for four days
            elif mean10>mean30*1.05 and trend10n:
                # But if the security appears to be falling in price long-term,
                # then going for mean-reversion might not be a good idea, so we continue.
                if past30[0]<=past30[5]<=past30[10]<=past30[15]:
                    continue
                elif security not in context.portfolio.positions.keys():
                    # The heuristic below is to prevent investing too much in one security.
                    p = data[security]['price']
                    toInvest = (context.portfolio.cash) * (context.max_drawdown**0.75)
                    numShares = max(0,np.round(toInvest/p))
                    order(security, numShares)
                    
                    
    record(leverage=context.account.leverage)
    record(num_positions=len(context.portfolio.positions.keys()))
           
           
           
           
           
           
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.

It would be helpful to see if this is still profitable without the hand-picked stocks, but rather using some sort of dynamic universe.

Yeah, I'm currently working on editing the logic to encompass some other stocks using pipeline. I tried putting a few in there at random and the performance was lacking.

My hypothesis at the moment is that new securities selected based on fundamentals will do just as well. I'll post the update when I finish.

Same algorithm on a universe of 600 stocks. It might be worth double checking I didn't introduce any bug.

Clone Algorithm
81
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 quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.factors import CustomFactor, SimpleMovingAverage, Latest, EWMA, EWMSTD, Returns, ExponentialWeightedMovingAverage, AverageDollarVolume
from quantopian.pipeline.filters.morningstar import IsPrimaryShare
import pandas as pd
import numpy as np

def high_volume_universe(min_price = 0., min_volume = 0.):  
    """
    Computes a security universe based on nine different filters:

    1. The security is common stock
    2 & 3. It is not limited partnership - name and database check
    4. The database has fundamental data on this stock
    5. Not over the counter
    6. Not when issued
    7. Not depository receipts
    8. Is Primary share
    9. Has high dollar volume
    
    Returns
    -------
    high_volume_tradable - zipline.pipeline.factor.Rank
        A ranked AverageDollarVolume factor that's filtered on the nine criteria
    """
    common_stock = morningstar.share_class_reference.security_type.latest.eq('ST00000001')
    not_lp_name = ~morningstar.company_reference.standard_name.latest.matches('.* L[\\. ]?P\.?$')
    not_lp_balance_sheet = morningstar.balance_sheet.limited_partnership.latest.isnull()
    have_data = morningstar.valuation.market_cap.latest.notnull()
    not_otc = ~morningstar.share_class_reference.exchange_id.latest.startswith('OTC')
    not_wi = ~morningstar.share_class_reference.symbol.latest.endswith('.WI')
    not_depository = ~morningstar.share_class_reference.is_depositary_receipt.latest
    primary_share = IsPrimaryShare()
    
    # Combine the above filters.
    tradable_filter = (common_stock & not_lp_name & not_lp_balance_sheet &
                       have_data & not_otc & not_wi & not_depository & primary_share)

    price = SimpleMovingAverage(inputs=[USEquityPricing.close],
                                window_length=21, mask=tradable_filter)
    volume = SimpleMovingAverage(inputs=[USEquityPricing.volume],
                                 window_length=21, mask=tradable_filter)
    
    full_filter = tradable_filter & (price >= min_price) & (volume >= min_volume)

    high_volume_tradable = AverageDollarVolume(
            window_length=21,
            mask=full_filter
        ).rank(ascending=False)
    return high_volume_tradable
              
def make_pipeline(context):
       
    dollar_volume = high_volume_universe(min_price=5.0)
    full_filter = (dollar_volume <= context.universe_size)
   
    pipe = Pipeline()
    pipe.set_screen(full_filter) 
    pipe.add(dollar_volume, "liquid_stocks")
        
    return pipe


def initialize(context):
    
    context.universe_size = 600 # how many securities to deal with every day
    context.max_cash_per_sec = 10000 # limit the amount of money on a single security
    
    context.dont_buys = security_lists.leveraged_etf_list
    set_do_not_order_list(context.dont_buys)    
       
    attach_pipeline(make_pipeline(context), 'factors')   
    
    schedule_function(rebalance, date_rules.every_day(), time_rules.market_open())
    schedule_function(log_stats, date_rules.every_day(), time_rules.market_close())


def before_trading_start(context, data):
    # get and clean pipeline results
    results = pipeline_output('factors')
    results = results.replace([np.inf, -np.inf], np.nan)
    results = results.dropna()
    
    # remove stocks we don't want to trade
    results = results.drop(context.dont_buys, axis=0, errors='ignore')    
    print 'Basket of stocks %d' % (len(results))
    
    # save stock universe
    context.tradeables_secs = results['liquid_stocks'].index
    
    
def rebalance(context, data):

    context.num_events = 0 # just for logging

    #
    # Let's decide what position to close and which new one to enter
    #
    new_secs = set()
    positions_to_close = {}
    all_sec = list( set(context.tradeables_secs) | set(context.portfolio.positions.keys()) )
    
    current_price = data.current(all_sec, 'price')
    _past10 = data.history(all_sec, fields='close', bar_count=10, frequency='1d')[::-1]
    _past30 = data.history(all_sec, fields='close', bar_count=30, frequency='1d')[::-1]
    
    for security in all_sec:
        
            # past 10 and past 30 prices, respectively
            past10 = _past10[security]
            past30 = _past30[security]
            mean10 = np.mean(past10)
            mean30 = np.mean(past30)
            # has the asset been increasing in value for the last three days?
            trend10p = past10[0]>=past10[1]>=past10[2]>=past10[3]
            # or falling?
            trend10n = past10[0]<=past10[1]<=past10[2]<=past10[3]
            
            # If we hold a security, we want to test if it is time to sell.
            # (Securities even with amount 0 may still be listed in portfolio, so it's
            #  necessary to double-check)
            if security in context.portfolio.positions.keys():
                #and context.portfolio.positions[security].amount > 0:
                # Checking that the 10-day moving average is below the 30-day moving average,
                # and that the price of the security has been falling for four days
                if mean10 < mean30*0.95 and trend10p:
                    positions_to_close[security] = context.portfolio.positions[security].amount
                # 8% stop-loss
                elif context.portfolio.positions[security].cost_basis*0.92 >= current_price[security]:
                    positions_to_close[security] = context.portfolio.positions[security].amount
                       
            # If we don't hold a security: check if we should buy it.
            # Check if the 10-day moving average is above the 30-day moving average,
            # and that the price of the security has been rising for four days
            elif mean10>mean30*1.05 and trend10n:
                # But if the security appears to be falling in price long-term,
                # then going for mean-reversion might not be a good idea, so we continue.
                if past30[0]<=past30[5]<=past30[10]<=past30[15]:
                    continue
                elif security not in context.portfolio.positions.keys():
                    new_secs.add(security)

    context.num_events = len(new_secs) # for logging
    
    # Helper function
    diff_positions = lambda d1, d2: { k:(d1.get(k,0)-d2.get(k,0)) for k in set(d1) | set(d2) if (d1.get(k,0)-d2.get(k,0)) != 0 }
    
    #
    # Calcualte new positions in our portfolio
    #        
    current_positions = { sec:position.amount for sec, position in context.portfolio.positions.iteritems() }
    positions_to_keep = diff_positions(current_positions, positions_to_close)
    final_secs = new_secs | set(positions_to_keep.keys())
    
    final_positions = {}
    available_cash_per_sec = context.portfolio.portfolio_value / len(final_secs)
    available_cash_per_sec = min(available_cash_per_sec, context.max_cash_per_sec)
    for sec in final_secs:
         if data.can_trade(sec):
             amount = available_cash_per_sec / current_price[sec]
             final_positions[sec] = round(amount)
         else:
             log.warn('Security %s missing in data, cannot buy it' % str(sec))
                
    actual_orders = diff_positions(final_positions, current_positions)
    for sec, amount in actual_orders.iteritems():
        log.debug( 'order %s amount %d' % (str(sec), amount) )
        order(sec, amount)                         
                                         
def log_stats(context, data):

    record(lever=context.account.leverage,
           num_pos=len(context.portfolio.positions),
           num_events=context.num_events)
                
There was a runtime error.

and on a universe of 50 stocks

Clone Algorithm
81
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 quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.factors import CustomFactor, SimpleMovingAverage, Latest, EWMA, EWMSTD, Returns, ExponentialWeightedMovingAverage, AverageDollarVolume
from quantopian.pipeline.filters.morningstar import IsPrimaryShare
import pandas as pd
import numpy as np

def high_volume_universe(min_price = 0., min_volume = 0.):  
    """
    Computes a security universe based on nine different filters:

    1. The security is common stock
    2 & 3. It is not limited partnership - name and database check
    4. The database has fundamental data on this stock
    5. Not over the counter
    6. Not when issued
    7. Not depository receipts
    8. Is Primary share
    9. Has high dollar volume
    
    Returns
    -------
    high_volume_tradable - zipline.pipeline.factor.Rank
        A ranked AverageDollarVolume factor that's filtered on the nine criteria
    """
    common_stock = morningstar.share_class_reference.security_type.latest.eq('ST00000001')
    not_lp_name = ~morningstar.company_reference.standard_name.latest.matches('.* L[\\. ]?P\.?$')
    not_lp_balance_sheet = morningstar.balance_sheet.limited_partnership.latest.isnull()
    have_data = morningstar.valuation.market_cap.latest.notnull()
    not_otc = ~morningstar.share_class_reference.exchange_id.latest.startswith('OTC')
    not_wi = ~morningstar.share_class_reference.symbol.latest.endswith('.WI')
    not_depository = ~morningstar.share_class_reference.is_depositary_receipt.latest
    primary_share = IsPrimaryShare()
    
    # Combine the above filters.
    tradable_filter = (common_stock & not_lp_name & not_lp_balance_sheet &
                       have_data & not_otc & not_wi & not_depository & primary_share)

    price = SimpleMovingAverage(inputs=[USEquityPricing.close],
                                window_length=21, mask=tradable_filter)
    volume = SimpleMovingAverage(inputs=[USEquityPricing.volume],
                                 window_length=21, mask=tradable_filter)
    
    full_filter = tradable_filter & (price >= min_price) & (volume >= min_volume)

    high_volume_tradable = AverageDollarVolume(
            window_length=21,
            mask=full_filter
        ).rank(ascending=False)
    return high_volume_tradable
              
def make_pipeline(context):
       
    dollar_volume = high_volume_universe(min_price=5.0)
    full_filter = (dollar_volume <= context.universe_size)
   
    pipe = Pipeline()
    pipe.set_screen(full_filter) 
    pipe.add(dollar_volume, "liquid_stocks")
        
    return pipe


def initialize(context):
    
    context.universe_size = 50 # how many securities to deal with every day
    context.max_cash_per_sec = 10000 # limit the amount of money on a single security
    
    context.dont_buys = security_lists.leveraged_etf_list
    set_do_not_order_list(context.dont_buys)    
       
    attach_pipeline(make_pipeline(context), 'factors')   
    
    schedule_function(rebalance, date_rules.every_day(), time_rules.market_open())
    schedule_function(log_stats, date_rules.every_day(), time_rules.market_close())


def before_trading_start(context, data):
    # get and clean pipeline results
    results = pipeline_output('factors')
    results = results.replace([np.inf, -np.inf], np.nan)
    results = results.dropna()
    
    # remove stocks we don't want to trade
    results = results.drop(context.dont_buys, axis=0, errors='ignore')    
    print 'Basket of stocks %d' % (len(results))
    
    # save stock universe
    context.tradeables_secs = results['liquid_stocks'].index
    
    
def rebalance(context, data):

    context.num_events = 0 # just for logging

    #
    # Let's decide what position to close and which new one to enter
    #
    new_secs = set()
    positions_to_close = {}
    all_sec = list( set(context.tradeables_secs) | set(context.portfolio.positions.keys()) )
    
    current_price = data.current(all_sec, 'price')
    _past10 = data.history(all_sec, fields='close', bar_count=10, frequency='1d')[::-1]
    _past30 = data.history(all_sec, fields='close', bar_count=30, frequency='1d')[::-1]
    
    for security in all_sec:
        
            # past 10 and past 30 prices, respectively
            past10 = _past10[security]
            past30 = _past30[security]
            mean10 = np.mean(past10)
            mean30 = np.mean(past30)
            # has the asset been increasing in value for the last three days?
            trend10p = past10[0]>=past10[1]>=past10[2]>=past10[3]
            # or falling?
            trend10n = past10[0]<=past10[1]<=past10[2]<=past10[3]
            
            # If we hold a security, we want to test if it is time to sell.
            # (Securities even with amount 0 may still be listed in portfolio, so it's
            #  necessary to double-check)
            if security in context.portfolio.positions.keys():
                #and context.portfolio.positions[security].amount > 0:
                # Checking that the 10-day moving average is below the 30-day moving average,
                # and that the price of the security has been falling for four days
                if mean10 < mean30*0.95 and trend10p:
                    positions_to_close[security] = context.portfolio.positions[security].amount
                # 8% stop-loss
                elif context.portfolio.positions[security].cost_basis*0.92 >= current_price[security]:
                    positions_to_close[security] = context.portfolio.positions[security].amount
                       
            # If we don't hold a security: check if we should buy it.
            # Check if the 10-day moving average is above the 30-day moving average,
            # and that the price of the security has been rising for four days
            elif mean10>mean30*1.05 and trend10n:
                # But if the security appears to be falling in price long-term,
                # then going for mean-reversion might not be a good idea, so we continue.
                if past30[0]<=past30[5]<=past30[10]<=past30[15]:
                    continue
                elif security not in context.portfolio.positions.keys():
                    new_secs.add(security)

    context.num_events = len(new_secs) # for logging
    
    # Helper function
    diff_positions = lambda d1, d2: { k:(d1.get(k,0)-d2.get(k,0)) for k in set(d1) | set(d2) if (d1.get(k,0)-d2.get(k,0)) != 0 }
    
    #
    # Calcualte new positions in our portfolio
    #        
    current_positions = { sec:position.amount for sec, position in context.portfolio.positions.iteritems() }
    positions_to_keep = diff_positions(current_positions, positions_to_close)
    final_secs = new_secs | set(positions_to_keep.keys())
    
    final_positions = {}
    available_cash_per_sec = context.portfolio.portfolio_value / len(final_secs)
    available_cash_per_sec = min(available_cash_per_sec, context.max_cash_per_sec)
    for sec in final_secs:
         if data.can_trade(sec):
             amount = available_cash_per_sec / current_price[sec]
             final_positions[sec] = round(amount)
         else:
             log.warn('Security %s missing in data, cannot buy it' % str(sec))
                
    actual_orders = diff_positions(final_positions, current_positions)
    for sec, amount in actual_orders.iteritems():
        log.debug( 'order %s amount %d' % (str(sec), amount) )
        order(sec, amount)                         
                                         
def log_stats(context, data):

    record(lever=context.account.leverage,
           num_pos=len(context.portfolio.positions),
           num_events=context.num_events)
                
There was a runtime error.

How about testing it with a 3X ETF

if 10day.mean > 30day.mean, we long (buy). If 10day.mean < 30day.mean, shall we short-sell ?

@Tory, It's a little bit more involved. It looks for whether the 10day mean is greater than 95% of the 30day mean. What the algo is trying to compensate for is that moving averages are a lagging indicator, so it's fudging the signals so that they trip earlier. It also checks for a 3-day continuous uptrend as a confirmation signal. And visa versa for selling. Also has a 8% stop loss. It all sounds good in theory, but as Luca's posts pointed out, it's not the algorithm that produces those impressive results in the initial backtests -- it was the bias introduced by the hand-selected stocks.

The difficulty with crossovers is that 1. due to the smoothing they're a lagging indicator, and 2. the frequency and amplitude of the price waveform is too variable for fixed-period crossovers to discern between signal and noise -- they don't do a good job of detecting whether a recent price move is establishing new momentum as opposed to a temporary move that will quickly revert. So just as often as not by the time the signal gets triggered the price is already moving in the other direction.