Back to Community
Momentum Factor & Long-Short Equity Strategy during Coronavirus (COVID-19) outbreak

Going 10% long on top 3-day momentum, and 90% short on 3-day bottom momentum US equities during Coronavirus (COVID-19) outbreak.

Clone Algorithm
582
Loading...
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 order_optimal_portfolio
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import SimpleMovingAverage
from quantopian.pipeline.filters import QTradableStocksUS
import quantopian.optimize as opt

class Momentum(CustomFactor):
    # Default inputs
    inputs = [USEquityPricing.close]

    # Compute momentum
    def compute(self, today, assets, out, close):
        out[:] = close[-1] / close[0]

def initialize(context):
    # Schedule our rebalance function to run at the start of
    # each week, when the market opens.
    schedule_function(
        my_rebalance,
        date_rules.every_day(),
        time_rules.market_open()
    )

    # Create our pipeline and attach it to our algorithm.
    my_pipe = make_pipeline()
    attach_pipeline(my_pipe, 'my_pipeline')

def make_pipeline():
    """
    Create our pipeline.
    """

    # Base universe set to the QTradableStocksUS.
    base_universe = QTradableStocksUS()
    
    my_momentum = Momentum(window_length=3)

    # Filter to select securities to short.
    shorts = my_momentum.top(10)

    # Filter to select securities to long.
    longs = my_momentum.bottom(10)
    
    # Filter for all securities that we want to trade.
    securities_to_trade = (shorts | longs)

    return Pipeline(
        columns={
            'longs': longs,
            'shorts': shorts
        },
        screen=(base_universe)
    )

def compute_target_weights(context, data):
    """
    Compute ordering weights.
    """

    # Initialize empty target weights dictionary.
    # This will map securities to their target weight.
    weights = {}

    # If there are securities in our longs and shorts lists,
    # compute even target weights for each security.
    if context.longs and context.shorts:
        long_weight = 0.1 / len(context.longs)
        short_weight = -0.9 / len(context.shorts)
    else:
        return weights

    # Exit positions in our portfolio if they are not
    # in our longs or shorts lists.
    for security in context.portfolio.positions:
        if security not in context.longs and security not in context.shorts and data.can_trade(security):
            weights[security] = 0

    for security in context.longs:
        weights[security] = long_weight

    for security in context.shorts:
        weights[security] = short_weight

    return weights

def before_trading_start(context, data):
    """
    Get pipeline results.
    """

    # Gets our pipeline output every day.
    pipe_results = pipeline_output('my_pipeline')

    # Go long in securities for which the 'longs' value is True,
    # and check if they can be traded.
    context.longs = []
    for sec in pipe_results[pipe_results['longs']].index.tolist():
        if data.can_trade(sec):
            context.longs.append(sec)

    # Go short in securities for which the 'shorts' value is True,
    # and check if they can be traded.
    context.shorts = []
    for sec in pipe_results[pipe_results['shorts']].index.tolist():
        if data.can_trade(sec):
            context.shorts.append(sec)

def my_rebalance(context, data):
    """
    Rebalance weekly.
    """

    # Calculate target weights to rebalance
    target_weights = compute_target_weights(context, data)

    # If we have target weights, rebalance our portfolio
    if target_weights:
        order_optimal_portfolio(
            objective=opt.TargetWeights(target_weights),
            constraints=[],
        )
There was a runtime error.
32 responses

@Dzenan,

if you set universe to QTradableStocksUS, as you intended to,

my_momentum = Momentum(window_length = 3, mask = base_universe)  

the results will be not as bright.

@Vladimir
thanks for your comment!

screen=(base_universe) is set in return Pipeline function. All assets must be tradable(QTradableStocksUS) after all.

Removing base_universe from the pipeline yields in much better results.

Clone Algorithm
582
Loading...
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 order_optimal_portfolio
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import SimpleMovingAverage
from quantopian.pipeline.filters import QTradableStocksUS
import quantopian.optimize as opt

class Momentum(CustomFactor):
    # Default inputs
    inputs = [USEquityPricing.close]

    # Compute momentum
    def compute(self, today, assets, out, close):
        out[:] = close[-1] / close[0]

def initialize(context):
    # Schedule our rebalance function to run at the start of
    # each week, when the market opens.
    schedule_function(
        my_rebalance,
        date_rules.every_day(),
        time_rules.market_open()
    )

    # Create our pipeline and attach it to our algorithm.
    my_pipe = make_pipeline()
    attach_pipeline(my_pipe, 'my_pipeline')

def make_pipeline():
    """
    Create our pipeline.
    """

    # Base universe set to the QTradableStocksUS.
    base_universe = QTradableStocksUS()
    
    my_momentum = Momentum(window_length=3)

    # Filter to select securities to short.
    shorts = my_momentum.top(10)

    # Filter to select securities to long.
    longs = my_momentum.bottom(10)
    
    # Filter for all securities that we want to trade.
    securities_to_trade = (shorts | longs)

    return Pipeline(
        columns={
            'longs': longs,
            'shorts': shorts
        },
        #screen=(base_universe)
    )

def compute_target_weights(context, data):
    """
    Compute ordering weights.
    """

    # Initialize empty target weights dictionary.
    # This will map securities to their target weight.
    weights = {}

    # If there are securities in our longs and shorts lists,
    # compute even target weights for each security.
    if context.longs and context.shorts:
        long_weight = 0.1 / len(context.longs)
        short_weight = -0.9 / len(context.shorts)
    else:
        return weights

    # Exit positions in our portfolio if they are not
    # in our longs or shorts lists.
    for security in context.portfolio.positions:
        if security not in context.longs and security not in context.shorts and data.can_trade(security):
            weights[security] = 0

    for security in context.longs:
        weights[security] = long_weight

    for security in context.shorts:
        weights[security] = short_weight

    return weights

def before_trading_start(context, data):
    """
    Get pipeline results.
    """

    # Gets our pipeline output every day.
    pipe_results = pipeline_output('my_pipeline')

    # Go long in securities for which the 'longs' value is True,
    # and check if they can be traded.
    context.longs = []
    for sec in pipe_results[pipe_results['longs']].index.tolist():
        if data.can_trade(sec):
            context.longs.append(sec)

    # Go short in securities for which the 'shorts' value is True,
    # and check if they can be traded.
    context.shorts = []
    for sec in pipe_results[pipe_results['shorts']].index.tolist():
        if data.can_trade(sec):
            context.shorts.append(sec)

def my_rebalance(context, data):
    """
    Rebalance weekly.
    """

    # Calculate target weights to rebalance
    target_weights = compute_target_weights(context, data)

    # If we have target weights, rebalance our portfolio
    if target_weights:
        order_optimal_portfolio(
            objective=opt.TargetWeights(target_weights),
            constraints=[],
        )
There was a runtime error.

Hi @Dzenan Hamzic

Very interesting strategy!

However, this strategy might only apply to a very very volatile bear market. I post a backtest based on your algo during the financial crisis last time (2007-2009) on the bottom.
As soon as the market turns its head to a bull (or up-trend) market, the strategy suffers huge losses and very large volatility. Then a very sharp stop during the market real bottom is very much necessary.

Just my two cents.

Clone Algorithm
38
Loading...
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 order_optimal_portfolio
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import SimpleMovingAverage
from quantopian.pipeline.filters import QTradableStocksUS
import quantopian.optimize as opt

class Momentum(CustomFactor):
    # Default inputs
    inputs = [USEquityPricing.close]

    # Compute momentum
    def compute(self, today, assets, out, close):
        out[:] = close[-1] / close[0]

def initialize(context):
    # Schedule our rebalance function to run at the start of
    # each week, when the market opens.
    schedule_function(
        my_rebalance,
        date_rules.every_day(),
        time_rules.market_open()
    )

    # Create our pipeline and attach it to our algorithm.
    my_pipe = make_pipeline()
    attach_pipeline(my_pipe, 'my_pipeline')

def make_pipeline():
    """
    Create our pipeline.
    """

    # Base universe set to the QTradableStocksUS.
    base_universe = QTradableStocksUS()
    
    my_momentum = Momentum(window_length=3)

    # Filter to select securities to short.
    shorts = my_momentum.top(10)

    # Filter to select securities to long.
    longs = my_momentum.bottom(10)
    
    # Filter for all securities that we want to trade.
    securities_to_trade = (shorts | longs)

    return Pipeline(
        columns={
            'longs': longs,
            'shorts': shorts
        },
        screen=(base_universe)
    )

def compute_target_weights(context, data):
    """
    Compute ordering weights.
    """

    # Initialize empty target weights dictionary.
    # This will map securities to their target weight.
    weights = {}

    # If there are securities in our longs and shorts lists,
    # compute even target weights for each security.
    if context.longs and context.shorts:
        long_weight = 0.1 / len(context.longs)
        short_weight = -0.9 / len(context.shorts)
    else:
        return weights

    # Exit positions in our portfolio if they are not
    # in our longs or shorts lists.
    for security in context.portfolio.positions:
        if security not in context.longs and security not in context.shorts and data.can_trade(security):
            weights[security] = 0

    for security in context.longs:
        weights[security] = long_weight

    for security in context.shorts:
        weights[security] = short_weight

    return weights

def before_trading_start(context, data):
    """
    Get pipeline results.
    """

    # Gets our pipeline output every day.
    pipe_results = pipeline_output('my_pipeline')

    # Go long in securities for which the 'longs' value is True,
    # and check if they can be traded.
    context.longs = []
    for sec in pipe_results[pipe_results['longs']].index.tolist():
        if data.can_trade(sec):
            context.longs.append(sec)

    # Go short in securities for which the 'shorts' value is True,
    # and check if they can be traded.
    context.shorts = []
    for sec in pipe_results[pipe_results['shorts']].index.tolist():
        if data.can_trade(sec):
            context.shorts.append(sec)

def my_rebalance(context, data):
    """
    Rebalance weekly.
    """

    # Calculate target weights to rebalance
    target_weights = compute_target_weights(context, data)

    # If we have target weights, rebalance our portfolio
    if target_weights:
        order_optimal_portfolio(
            objective=opt.TargetWeights(target_weights),
            constraints=[],
        )
There was a runtime error.

May i ask if this part of the algo is correct for a momentum factor:

    # Filter to select securities to short.  
    shorts = my_momentum.top(10)  
    # Filter to select securities to long.  
    longs = my_momentum.bottom(10)  

I am reading it as... if the stock has risen a lot, short it; if the stock price has fallen badly, long it. The code seems like mean reversion rather than momentum. For momentum factor, shouldn't it be long the higher return stock, short the lowest return stock?

Going long into stocks that are falling is definitely not momentum trading.

Check out out[:] = close[-1] / close[0] being the momentum calculator. When prices are rising, will the ratio be above or below 1? There lies your answer.

@ Alexander Katyk

Thanks for your reply. So this thread on momentum factor is wrong? In fact, the algo is actually testing mean reversion strategy rather than momentum.

Isn’t close[-1] price before close[0]?

I'm new to Python and Quantopian, and probably this algo has been vetted by way more experienced users, but i think i am correct in reading this (please correct me if im wrong though).

close[-1] is the most recent price in the lookback period denoted by window_length parameter.
close[0] is the oldest price in the same lookback period.

Referring to this other algo: https://www.quantopian.com/posts/long-short-pipeline-multi-factor
Their momentum factor is computed as " close[-20] / close[0] " for a 252 window_length, which means a one-year return, but excluding the most recent 1 month (20 trading days); therefore, technically a 232 days period. Thereafter, they long the tail via "context.longs = ranks.tail(200)" after ranking the momentum factor (along with other factors) in default ascending order . This supports my understanding of the direction of 'close[-1]'.

So back to the algo in this thread, if i got the direction of close[-1] and close[0] correctly, the next question will be the direction of the built-in function .top() and .bottom(). According to the documentation, the definition for .top() is "Construct a Filter matching the top N asset values of self each day." I am understanding it as, finding the top N asset with the HIGHEST factor value, rather than functioning like .head().
https://www.quantopian.com/docs/api-reference/pipeline-api-reference#zipline.pipeline.Factor.top

Referring to Q's lecture series:
https://www.quantopian.com/lectures/example-long-short-equity-algorithm
".top()" is used to filter out list of asset to LONG via "longs = combined_factor.top(TOTAL_POSITIONS//2, mask=universe)", which would support my interpretation of the direction of the .top(), that is, to obtain the N assets with the highest factor value.

If my understanding of the direction of "close[-1]" and ".top()" function is correct, then there is a mistake with the long short rules in the algo from this thread, it should be swapped.

Again, please correct me if i am wrong about this fundamentally important concept. I have no confidence in myself, given that people with way more experience than me have vetted this code.

close[-20] grabs the 20th price from the end of the series. So the question becomes is close[0], being the first price in the series, the oldest or newest price? I thought it goes from newest to oldest but could be wrong.

In notebook, it is hard to replicate as I couldn't get close[0] but check out what [-1] in here

Loading notebook preview...

Also a good post on price discussion: https://www.quantopian.com/posts/current-price-vs-last-price

Yup, so according to your notebook, pipeline churns out the oldest data at the top and the newer data at the bottom. Hence, with python slicing, close[-1] gets you the most recent price out of your series and it would mean that close[0] gets you the oldest price. Then the question will be about .top() and .bottom(), which if i am correct (from my previous reply), then there is mistake in the long short rule for this algo and that the backtest results are wrong for a momentum algo.

Did you take the Udemy Python for Financial Analysis and Algorithmic trading Course?

A while back. Am i wrong with the close[-1]?

Can somebody explain to me how does [1] computes the momentum? Specially when called by [2]!!
I am a computer scientist, very use to "standard" programming, however, I am still having a very hard time understading how the variables a linked in this code.

Some of my confusing thoughts:
- there is no .top() neigther .bottom() function within the class Momentum that I can see.
- "window_length=3" is not a parameter of the class as well (?), instead, "(CustomFactor)" is.
- what "out[:]" is doing? by the name, I can imagine this is the class output somehow, but what is the [:}

Please, somebody help me to understand the mechanics of this code!! It is very frustrating having to guess where "hidden" variables are so I can assume they here and there.

Cheers!

[1]

class Momentum(CustomFactor):  
    # Default inputs  
    inputs = [USEquityPricing.close]

    # Compute momentum  
    def compute(self, today, assets, out, close):  
        out[:] = close[-1] / close[0]  

[2]
``` my_momentum = Momentum(window_length=3)

# Filter to select securities to short.  
shorts = my_momentum.top(10)

# Filter to select securities to long.  
longs = my_momentum.bottom(10)  

@Roger
Yes, there's a lot of hidden magic. In order to find it you can follow the classes, for instance:

The Momentum above inherits from the class CustomFactor, which in turn inherits (amongst others) from Factor and the latter has functions called "top", "bottom" and many others. The class Factor inherits from ComputableTerm and there is your declaration of window_length. One way to inspect the underlying code is to check out zipline on github
I haven't found the definition of "out" yet...

And a quick demonstration what the [:] is good for. Basically, if you say

something  =  something_else  

you redefine something with something_else, but if you say

something[:] = something_else  

you populate something with something_else

Loading notebook preview...

I modified the strategy to take volume into account, and I changed the ratio of longing and shorting. This backtest is for 2020 so far.

Clone Algorithm
43
Loading...
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 order_optimal_portfolio
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import SimpleMovingAverage
from quantopian.pipeline.filters import QTradableStocksUS
import quantopian.optimize as opt

class Momentum(CustomFactor):
    # Default inputs
    inputs = [USEquityPricing.close]

    # Compute momentum
    def compute(self, today, assets, out, close):
        out[:] = close[-1] / close[0]

def initialize(context):
    # Schedule our rebalance function to run at the start of
    # each week, when the market opens.
    schedule_function(
        my_rebalance,
        date_rules.every_day(),
        time_rules.market_open()
    )

    # Create our pipeline and attach it to our algorithm.
    my_pipe = make_pipeline()
    attach_pipeline(my_pipe, 'my_pipeline')

def make_pipeline():
    """
    Create our pipeline.
    """
    
    # Calculate momentum
    my_momentum = Momentum(window_length=3)
    
    # Volume
    volume = USEquityPricing.volume.latest

    # Filter to select securities to short.
    shorts = my_momentum.top(10)

    # Filter to select securities to long.
    longs = my_momentum.bottom(5)
    
    # Filter for all securities that we want to trade.
    securities_to_trade = (shorts | longs)
    
    # Volume filer
    volume_filter = (volume > 1000000)

    return Pipeline(
        columns={
            'longs': longs,
            'shorts': shorts
        },
        screen=(securities_to_trade & volume_filter)
    )

def compute_target_weights(context, data):
    """
    Compute ordering weights.
    """

    # Initialize empty target weights dictionary.
    # This will map securities to their target weight.
    weights = {}

    # If there are securities in our longs and shorts lists,
    # compute even target weights for each security.
    if context.longs and context.shorts:
        long_weight = 0.2 / len(context.longs)
        short_weight = -0.8 / len(context.shorts)
    else:
        return weights

    # Exit positions in our portfolio if they are not
    # in our longs or shorts lists.
    for security in context.portfolio.positions:
        if security not in context.longs and security not in context.shorts and data.can_trade(security):
            weights[security] = 0

    for security in context.longs:
        weights[security] = long_weight

    for security in context.shorts:
        weights[security] = short_weight

    return weights

def before_trading_start(context, data):
    """
    Get pipeline results.
    """

    # Gets our pipeline output every day.
    pipe_results = pipeline_output('my_pipeline')

    # Go long in securities for which the 'longs' value is True,
    # and check if they can be traded.
    context.longs = []
    for sec in pipe_results[pipe_results['longs']].index.tolist():
        if data.can_trade(sec):
            context.longs.append(sec)

    # Go short in securities for which the 'shorts' value is True,
    # and check if they can be traded.
    context.shorts = []
    for sec in pipe_results[pipe_results['shorts']].index.tolist():
        if data.can_trade(sec):
            context.shorts.append(sec)

def my_rebalance(context, data):
    """
    Rebalance weekly.
    """

    # Calculate target weights to rebalance
    target_weights = compute_target_weights(context, data)

    # If we have target weights, rebalance our portfolio
    if target_weights:
        order_optimal_portfolio(
            objective=opt.TargetWeights(target_weights),
            constraints=[],
        )
There was a runtime error.

I'm a quant newbie here, trying to run this using pylivetrader. what's the alternative to "quantopian.optimize" since it's not supported

I'm getting an error in line 127 when playing it from 2017 (Nibraas Khan version)

@Anthony
You can just iterate over the weights and order each of the symbols using order_target_percent. I don't know how ordering works there and would need more details, like which order methods are available (percentage of portfolio, target size, or only number of shares) and if you can place an order when you don't have enough cash ready at that time

Clone Algorithm
23
Loading...
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 order_optimal_portfolio
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import SimpleMovingAverage
from quantopian.pipeline.filters import QTradableStocksUS
import quantopian.optimize as opt


class Momentum(CustomFactor):
    # Default inputs
    inputs = [USEquityPricing.close]

    # Compute momentum
    def compute(self, today, assets, out, close):
        out[:] = close[-1] / close[0]


def initialize(context):
    # Schedule our rebalance function to run at the start of
    # each week, when the market opens.
    schedule_function(
        my_rebalance,
        date_rules.every_day(),
        time_rules.market_open()
    )

    # Create our pipeline and attach it to our algorithm.
    my_pipe = make_pipeline()
    attach_pipeline(my_pipe, 'my_pipeline')


def make_pipeline():
    """
    Create our pipeline.
    """

    # Calculate momentum
    my_momentum = Momentum(window_length=3)

    # Volume
    volume = USEquityPricing.volume.latest

    # Filter to select securities to short.
    shorts = my_momentum.top(10)

    # Filter to select securities to long.
    longs = my_momentum.bottom(5)

    # Filter for all securities that we want to trade.
    securities_to_trade = (shorts | longs)

    # Volume filer
    volume_filter = (volume > 1000000)

    return Pipeline(
        columns={
            'longs': longs,
            'shorts': shorts
        },
        screen=(securities_to_trade & volume_filter)
    )


def compute_target_weights(context, data):
    """
    Compute ordering weights.
    """

    # Initialize empty target weights dictionary.
    # This will map securities to their target weight.
    weights = {}

    # If there are securities in our longs and shorts lists,
    # compute even target weights for each security.
    if context.longs and context.shorts:
        long_weight = 0.2 / len(context.longs)
        short_weight = -0.8 / len(context.shorts)
    else:
        return weights

    # Exit positions in our portfolio if they are not
    # in our longs or shorts lists.
    for security in context.portfolio.positions:
        if security not in context.longs and security not in context.shorts and data.can_trade(security):
            weights[security] = 0

    for security in context.longs:
        weights[security] = long_weight

    for security in context.shorts:
        weights[security] = short_weight

    return weights


def before_trading_start(context, data):
    """
    Get pipeline results.
    """

    # Gets our pipeline output every day.
    pipe_results = pipeline_output('my_pipeline')

    # Go long in securities for which the 'longs' value is True,
    # and check if they can be traded.
    context.longs = []
    for sec in pipe_results[pipe_results['longs']].index.tolist():
        if data.can_trade(sec):
            context.longs.append(sec)

    # Go short in securities for which the 'shorts' value is True,
    # and check if they can be traded.
    context.shorts = []
    for sec in pipe_results[pipe_results['shorts']].index.tolist():
        if data.can_trade(sec):
            context.shorts.append(sec)


def my_rebalance(context, data):
    """
    Rebalance weekly.
    """

    # Calculate target weights to rebalance
    target_weights = compute_target_weights(context, data)

    # If we have target weights, rebalance our portfolio
    if target_weights:
        # First, drop all symbols that are not in the current weights (opt does that automatically)
        for sym in context.portfolio.positions.keys():
            if sym not in target_weights.keys():
                order_target_percent(sym, 0)
        # Then order the new weights by iterating over them
        for sym, wt in target_weights.items():
            order_target_percent(sym, wt)
There was a runtime error.

@andres
I also got an error message in another timespan. It has probably something to do with the optimizer having problems with the universe - aka mostly penny stocks. You can avoid this using try and except:

    try:  
        order_optimal_portfolio(  
            objective=opt.TargetWeights(wts),  
            constraints=[],  
        )  
    except Exception as e:  
        print(str(e))

@All others who think these are valid results
I ran a test where I invested $ 1,000 in 2005 and got a return of 190,578,981 % = ONE HUNDRED AND NINETY MILLION PERCENT. That means, if I now invest 1000 bucks in this strategy I will be a Billionaire in 15 Years!!!
Sounds too good to be true? Well, it probably is. This post might be enlightening...

Clone Algorithm
23
Loading...
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 order_optimal_portfolio
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import SimpleMovingAverage
from quantopian.pipeline.filters import QTradableStocksUS
import quantopian.optimize as opt
import numpy as np
import pandas as pd


class Momentum(CustomFactor):
    inputs = [USEquityPricing.close]
    def compute(self, today, assets, out, close):
        out[:] = close[-1] / close[0]


def initialize(context):
    schedule_function(
        my_rebalance,
        date_rules.every_day(),
        time_rules.market_open()
    )
    my_pipe = make_pipeline()
    attach_pipeline(my_pipe, 'my_pipeline')


def make_pipeline():
    my_momentum = Momentum(window_length=3)
    volume = USEquityPricing.volume.latest
    shorts = my_momentum.top(10)
    longs = my_momentum.bottom(5)
    securities_to_trade = (shorts | longs)
    volume_filter = (volume > 1000000)

    return Pipeline(
        columns={
            'longs': longs,
            'shorts': shorts
        },
        screen=(securities_to_trade & volume_filter)
    )


def my_rebalance(context, data):
    record(lever=context.account.leverage)
    df = pipeline_output('my_pipeline')
    df['sym'] = df.index
    cantrade = df.sym.apply(lambda x: data.can_trade(x))
    df = df[cantrade]

    if 'longs' not in df.columns or 'shorts' not in df.columns:
        return
    longs = df.longs
    shorts = df.shorts

    if min(sum(longs), sum(shorts)) == 0:
        return

    wts = pd.Series(np.full(len(df), 0, dtype=float), index=df.index)
    wts[longs] = .2 / sum(longs)
    wts[shorts] = -0.8 / sum(shorts)
    wts = wts[np.isfinite(wts)]
    
    try:
        order_optimal_portfolio(
            objective=opt.TargetWeights(wts),
            constraints=[],
        )
    except Exception as e:
        print(str(e))


There was a runtime error.

so this is like a small cap strategy? Since the factor would almost surely capture those small caps with large volatility.

@Tentor

first of thank you for your responses. It really does help us understand this algo more.

I wonder how this algo would run with nonpenny stocks and high volume maybe 20M+

My point is, it's not a strategy at all, more like a hack of the backtester. Try using QTradableStocksUS as a filter and the returns go away. If you let the algo look for the biggest price movements unfiltered, it will naturally find many penny stocks. its not uncommon that a penny stock moves several hundred percent in one day. That often means the price rises from $ 0.001 to $ 0.008 - 700 % and still a fraction of a penny. Its just very difficult to simulate realistic trading with prices like that.

Does it suffer from survivorship bias? All those small cap traded are those survived. I see a lot of warnings because many trades cannot be made in the backtest.

@Anthony
Dollar volume is more meaningful (price*volume) because if someone orders 20M of a stock worth $ 0.001 that's just 20,000 dollar being traded. Qtu has a minimum of $2.5 M dollar volume. Here's a test with qtu as universe. A working strategy should work at least a bit in a different universe...

Clone Algorithm
0
Loading...
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 order_optimal_portfolio
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import SimpleMovingAverage
from quantopian.pipeline.filters import QTradableStocksUS
import quantopian.optimize as opt
import numpy as np
import pandas as pd


class Momentum(CustomFactor):
    inputs = [USEquityPricing.close]

    def compute(self, today, assets, out, close):
        out[:] = close[-1] / close[0]


def initialize(context):
    schedule_function(
        my_rebalance,
        date_rules.every_day(),
        time_rules.market_open()
    )
    my_pipe = make_pipeline()
    attach_pipeline(my_pipe, 'my_pipeline')


def make_pipeline():
    my_momentum = Momentum(window_length=3)
    volume = USEquityPricing.volume.latest
    shorts = my_momentum.top(10)
    longs = my_momentum.bottom(5)
    securities_to_trade = (shorts | longs)
    volume_filter = (volume > 1000000)

    return Pipeline(
        columns={
            'longs': longs,
            'shorts': shorts
        },
        screen=(securities_to_trade & volume_filter & QTradableStocksUS())
    )


def my_rebalance(context, data):
    record(lever=context.account.leverage)
    df = pipeline_output('my_pipeline')
    df['sym'] = df.index
    cantrade = df.sym.apply(lambda x: data.can_trade(x))
    df = df[cantrade]

    if 'longs' not in df.columns or 'shorts' not in df.columns:
        return
    longs = df.longs
    shorts = df.shorts

    if min(sum(longs), sum(shorts)) == 0:
        return

    wts = pd.Series(np.full(len(df), 0, dtype=float), index=df.index)
    wts[longs] = .2 / sum(longs)
    wts[shorts] = -0.8 / sum(shorts)
    wts = wts[np.isfinite(wts)]

    try:
        order_optimal_portfolio(
            objective=opt.TargetWeights(wts),
            constraints=[],
        )
    except Exception as e:
        print(str(e))
There was a runtime error.

Of course I could be wrong and we have a real goldmine here - a trading strategy that only works with penny stocks and makes you very rich. The problem is, how will you know? I tried a few different versions and some of them got a negative return of several thousand percent. That not only means your investment is gone but you also owe your broker such a huge mountain of debt, you will perhaps never be able to pay...

@tentor,
This makes a lot of sense.
I wonder if we can add another screen and just retrieve the penny stocks that have a substantial jump in volume within past x time to choose from, rather than all tradeable stocks.

The pipe line code, would it be better to change to this? I dont see why the original code use securities_to_trade filter.

return Pipeline(  
    columns={  
        'longs': longs,  
        'shorts': shorts  
    },  
    screen=securities_to_trade  

I suppose this algo has a huge beta, won t work out of the sample