Back to Community
What about common factors?

I'm new here in quantopian, I've been reading about trading for almost a year now and trying to make a first algorithm that just works. Im aiming for building a basic one, as a newby is not likely that I make some great discovery.
I'm currently reading about the common factors and especially interested in Momentum and Short term reversal, since my understanding of the markets is that stocks move in trends + waves.

My question is, its possible to build a nice working strategy based on the common factors? Maybe it won't be a great one for a competition neither a unique one, but really useful for someone just getting started for making a few bucks and gaining faith. If so, can anyone point me in some direction for continue learning.
Or is it that the common factors are too risky and you will never be able to make something good enough?

Here are two post that were useful to me for anyone else that is in the same road:
https://www.quantopian.com/posts/enhancing-short-term-mean-reversion-strategies-1
https://www.quantopian.com/posts/equity-momentum

Im really struggling with my learning curve so I would appreciate any kind of help or advice

6 responses

I think there's no money to be made in basic momentum (12-month returns minus 1-month returns) or short-term reversal (15-day RSI) factors. Try it for yourself -- you'll see the returns won't be terribly strong or consistent. However I believe it is possible to find strategies that incidentally expose you to these factors or specific situations where momentum or short-term reversal work.

I'm skeptical about the two links you posted. As you pointed out, the mean reversion one fails spectacularly out-of-sample. Clenow's strategy may give you an edge over a really long time horizon. But I'm a little skeptical -- such low-frequency strategies are easy enough to overfit to profitable noise.

Thanks, yeah the mean reversion one fails terribly out of sample, but he exposed some nice ideas. Sorry for my ignorance, the common factors are calculated with the (15-day RSI) and (12-month returns minus 1-month returns) ? that would be the COMMON factors? like you could create a momentum factor other than the common one?

I was thinking of developing a strategy based on getting some factors from a linear regression (maybe slope and error) and combine them with some mean reversion factor like top gainers/losers. That factors wouldn't be considered common ones? I can create a momentum strategy that differs from the momentum common factor?

Also, what do you (or anyone reading) think about the idea (linear regression plus top gainers/losers)? I assume is has already been tried out, so maybe I could save some time and work with something else. It has sense? Or is it too basic?

Thanks for replying

Yeah, mean reversion and momentum definitely happen on other time frames as well. But the "common" factors that Quantopian wants us to avoid are these ones I mentioned above, since those are the ones documented in the academic literature.

I think you should try your idea. Just make sure to keep at least a couple years hold-out period. That way you avoid overfitting.

Somebody correct me if I'm implementing this wrong, but here's a very vanilla Short-Term Reversal strategy, using 15-day RSI to predict the stock's next 15-day move.

Clone Algorithm
2
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
"""
Vanilla Size
by Viridian Hawk
"""
import quantopian.algorithm as algo
import quantopian.optimize as opt
from quantopian.pipeline import Pipeline
#from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS
#from quantopian.pipeline.experimental import risk_loading_pipeline  
from quantopian.pipeline.factors import Returns, SimpleBeta, RSI, SimpleMovingAverage
from quantopian.pipeline.data import Fundamentals


def initialize(context):
    algo.schedule_function( 
        rebalance,
        algo.date_rules.every_day(),
        algo.time_rules.market_open(hours=1)
    )

    algo.schedule_function(
        record_vars,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(),
    )

    algo.attach_pipeline(make_pipeline(), 'pipeline')

    #algo.attach_pipeline(risk_loading_pipeline(), 'risk_loading_pipeline')


def make_pipeline():
    base_universe = QTradableStocksUS()
    m = base_universe

    #beta = SimpleBeta(target=symbol('SPY'), regression_length=160) # $VT
    #m &= beta.notnull()
    
    #momentum = Returns(window_length=220, mask=m) - Returns(window_length=22, mask=m)
    #m &= momentum.notnull()
    
    streversal = 100 - RSI(window_length = 15)
    m &= streversal.notnull()
    
    #size = Fundamentals.market_cap.latest
    #m &= size.notnull()
    
    return Pipeline(
        columns={
            #'momentum' : SimpleMovingAverage(inputs=[momentum.zscore().demean()], window_length=220),
            'streversal' : SimpleMovingAverage(inputs=[streversal.zscore().demean()], window_length=15),
            #'size_risk'     : size.zscore().demean(),
            #'beta'     : beta,
        },
        screen=m
    )

def before_trading_start(context, data):
    context.output = algo.pipeline_output('pipeline')
    #context.risk_loading_pipeline = algo.pipeline_output('risk_loading_pipeline')


def rebalance(context, data):
    algo.order_optimal_portfolio(
        objective=opt.TargetWeights( context.output.streversal / context.output.streversal.abs().sum() ),
        constraints=[
            opt.MaxGrossExposure(1.00),
            opt.DollarNeutral(),
            #opt.PositionConcentration.with_equal_bounds(-0.04, 0.04),
            #opt.experimental.RiskModelExposure(  
            #    risk_model_loadings=context.risk_loading_pipeline,  
            #    version=opt.Newest),
            #opt.FactorExposure(
            #    context.output[['beta']],
            #    min_exposures={'beta': -0.2},
            #    max_exposures={'beta': 0.2}),
            #opt.MaxTurnover( 0.65)
        ],
    )

def record_vars(context, data):
    longs = shorts = 0
    for stock in context.portfolio.positions:
        if context.portfolio.positions[stock].amount > 0:
            longs += 1
        elif  context.portfolio.positions[stock].amount < 0:
            shorts += 1
    record(longs = longs)
    record(shorts = shorts)
    record(l = context.account.leverage)
There was a runtime error.

Here's a really simple implementation of momentum. I welcome any corrections. As you can see, these factors don't look too great.

Clone Algorithm
2
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
"""
Vanilla Momentum
by Viridian Hawk
"""
import quantopian.algorithm as algo
import quantopian.optimize as opt
from quantopian.pipeline import Pipeline
#from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS
#from quantopian.pipeline.experimental import risk_loading_pipeline  
from quantopian.pipeline.factors import Returns, SimpleBeta


def initialize(context):
    algo.schedule_function( 
        rebalance,
        algo.date_rules.every_day(),
        algo.time_rules.market_open(hours=1)
    )

    algo.schedule_function(
        record_vars,
        algo.date_rules.every_day(),
        algo.time_rules.market_close(),
    )

    algo.attach_pipeline(make_pipeline(), 'pipeline')

    #algo.attach_pipeline(risk_loading_pipeline(), 'risk_loading_pipeline')


def make_pipeline():
    base_universe = QTradableStocksUS()
    m = base_universe

    #beta = SimpleBeta(target=symbol('SPY'), regression_length=160) # $VT
    #m &= beta.notnull()
    
    momentum = Returns(window_length=220, mask=m) - Returns(window_length=22, mask=m)
    m &= momentum.notnull()
    
    return Pipeline(
        columns={
            'momentum' : momentum.zscore().demean(),
            #'beta'     : beta,
        },
        screen=m
    )

def before_trading_start(context, data):
    context.output = algo.pipeline_output('pipeline')
    #context.risk_loading_pipeline = algo.pipeline_output('risk_loading_pipeline')


def rebalance(context, data):
    algo.order_optimal_portfolio(
        objective=opt.TargetWeights( context.output.momentum / context.output.momentum.abs().sum() ),
        constraints=[
            opt.MaxGrossExposure(1.00),
            opt.DollarNeutral(),
            #opt.PositionConcentration.with_equal_bounds(-0.04, 0.04),
            #opt.experimental.RiskModelExposure(  
            #    risk_model_loadings=context.risk_loading_pipeline,  
            #    version=opt.Newest),
            #opt.FactorExposure(
            #    context.output[['beta']],
            #    min_exposures={'beta': -0.2},
            #    max_exposures={'beta': 0.2}),
            #opt.MaxTurnover( 0.65)
        ],
    )

def record_vars(context, data):
    longs = shorts = 0
    for stock in context.portfolio.positions:
        if context.portfolio.positions[stock].amount > 0:
            longs += 1
        elif  context.portfolio.positions[stock].amount < 0:
            shorts += 1
    record(longs = longs)
    record(shorts = shorts)
    record(l = context.account.leverage)
There was a runtime error.