Back to Community
5 Day EMA with RSI and EPS std_dev filters

By switching Long/Short Leverage based on yearly Market returns we can minimize any large losses causes by large market draw backs.

Targets
- Stocks with the largest moves from their 5 day EMA that are either Over bought or over sold.
- 20 longs, 10 shorts with 0.7 and -0.3 leverage

Trading
- Collect needed data 10 minutes before market opens
- Enter trades 15 minutes after market opens
- Check for Profit/Loss thresholds every 60 seconds
- If 252 day SPY return is less than -0.02 switch Long/Short leverage percentages

Filters
- RSI Greater than 70 for Shorts, less than 30 for longs
- Above average EPS consensus standard deviation (Need to do more research, hi stddev should mean more volatility after big moves)

Please let me know if you see any issues or improvements.
There seems to be stagnate returns towards the end of the back test. Is mean reversion trading still viable in 2019?

Clone Algorithm
10
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
"""

*Five dave EWMA Reversion Strat*

    - Enter poisition of the highest and lowest percent deviations form the 5 day EWMA
    - Number and Leverage of long/short positions is determined by the 50 day SPY slope (Positive: .7/-.3, Negative: .3/-.7)
    - Take profits/losses every 60 minutes

"""

import numpy as np
import pandas as pd
from quantopian.pipeline import Pipeline
from quantopian.pipeline import CustomFactor
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data.factset.estimates import PeriodicConsensus
from quantopian.pipeline.factors import SimpleMovingAverage, AverageDollarVolume, ExponentialWeightedMovingAverage, RSI
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.filters import Q1500US


def initialize(context):
    
    # Set benchmark to short-term Treasury note ETF (SHY) since strategy is dollar neutral
    set_benchmark(sid(8554))
    
    # Schedule our rebalance function to run at the end of each day.
    schedule_function(my_rebalance, date_rules.every_day(), time_rules.market_open(minutes=15))

    # Record variables at the end of each day.
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())
    
    # Get intraday prices today before the close if you are not skipping the most recent data
    schedule_function(get_prices,date_rules.every_day(), time_rules.market_open(minutes=10))
    
    # Set commissions and slippage to 0 to determine pure alpha
    set_commission(commission.PerShare(cost=0, min_trade_cost=0))
    set_slippage(slippage.FixedSlippage(spread=0))
    
    # Number of quantiles for sorting returns for mean reversion
    context.nq=9
    
    # Number of quantiles for sorting volatility over five-day mean reversion period
    context.nq_vol=3
    
    # Take profit setup
    context.waits = {}
    context.waits_max = 3    # trading days
    
    context.profit_threshold = .075
    context.loss_threshold = -.03
    context.profit_logging = True
    
    # market based long/short percentages
    context.long_leverage = 0.7
    context.long_cnt = 20
    context.short_leverage = -0.3
    context.short_cnt = 10
    
    for i in range(25, 395, 60):    # (low, high, every i minutes)
        #continue    # uncomment to deactivate
        schedule_function(take_profits, date_rules.every_day(), time_rules.market_open(minutes=i))
    

    # 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.
    """
    
    pricing=USEquityPricing.close.latest

    # FactSet PeriodicConsensus Standard Deviation Data
    fq1_eps_cons = PeriodicConsensus.slice('EPS', 'qf', 1)
    normalized_std_dev = fq1_eps_cons.std_dev.latest / fq1_eps_cons.mean.latest
    eps_filter = normalized_std_dev.percentile_between(65,99)
   
    ewma5 = ExponentialWeightedMovingAverage.from_span(
        inputs=[USEquityPricing.close],
        window_length=5,
        span=2.5
    )
    rsi = RSI(
        inputs=[USEquityPricing.close], 
        window_length=3
    )
    
    
    universe = (
        Q1500US()
        & (pricing > 5)
        & eps_filter
    )


    return Pipeline(
        columns = {
            "ewma5": ewma5,
            "normalized_std_dev": normalized_std_dev,
            "rsi": rsi
        },
        screen=universe
    )


def before_trading_start(context, data):
    # Gets our pipeline output every day.
    context.output = pipeline_output('my_pipeline')
       

def get_prices(context, data):
    # Get the last 6 days of prices for every stock in our universe
    spy_price = data.history([sid(8554)],'price',252,'1d')
    spy_slope = slope(spy_price[sid(8554)])
    log.info(spy_slope)
    if spy_slope >= -0.02:
        context.long_leverage = 0.7
        context.long_cnt = 20
        context.short_leverage = -0.3
        context.short_cnt = 10
    else:
        context.long_leverage = 0.3
        context.long_cnt = 10
        context.short_leverage = -0.7
        context.short_cnt = 20
    Universe500=context.output.index.tolist()
    prices = data.history(Universe500,'price',6,'1d')
    intraday_price = data.history(Universe500, 'close', 10, '1m').bfill().ffill()
    intraday_ret = (intraday_price.iloc[-1] - intraday_price.iloc[0]) / intraday_price.iloc[0]
    
    
    # daily_rets=np.log(prices/prices.shift(1))
    today_price = prices.iloc[-1]

    rets=(prices.iloc[-2] - prices.iloc[0]) / prices.iloc[0]
    
    rets_df=pd.DataFrame(rets,columns=['five_day_ret'])
    today_returns_df=pd.DataFrame(today_price)
    today_returns_df.columns = ["cur_price"]
    
    
    # Create VELOCITY Metric
    pri = data.history(Universe500, "price",200, "1d")
    velocity = (pri.iloc[-1] - pri.iloc[-2:].mean())
    velocity_df=pd.DataFrame(velocity)
    velocity_df.columns = ["velocity"]
    
    # Create Today's price Metric
    intraday_ret_df=pd.DataFrame(intraday_ret)
    intraday_ret_df.columns = ["intraday_return"]
    
    # Joins
    context.output=context.output.join(rets_df,how='outer')
    context.output=context.output.join(today_returns_df,how='outer')
    context.output = context.output.join(intraday_ret_df, how='outer')
    context.output = context.output.join(velocity_df, how='outer')
    
    context.output["per_off_ewma"] = (context.output["ewma5"] - context.output["cur_price"]) / context.output["ewma5"]
    context.output['ret_quantile']=pd.qcut(context.output['per_off_ewma'],context.nq,labels=False)+1
    
    context.shorts = context.output.query("rsi > 70 and velocity < 0").nlargest(context.short_cnt, "per_off_ewma").index.tolist()
    context.longs = context.output.query("rsi < 30 and velocity > 0").nsmallest(context.long_cnt, "per_off_ewma").index.tolist()
    
def my_rebalance(context, data):
    """
    Rebalance daily.
    """
    Universe500=context.output.index.tolist()


    existing_longs=0
    existing_shorts=0
    for security in context.portfolio.positions:
        # Unwind stocks that have moved out of Universe
        if security not in Universe500 and data.can_trade(security): 
            order_target_percent(security, 0)
        else:
            if data.can_trade(security):
                current_quantile=context.output['ret_quantile'].loc[security]
                if context.portfolio.positions[security].amount<0:
                    if (current_quantile==1) and (security not in context.shorts):
                        existing_shorts += 1
                    elif security not in context.longs:
                        order_target_percent(security, 0)
                elif context.portfolio.positions[security].amount>0:
                    if (current_quantile==context.nq) and (security not in context.longs):
                        existing_longs += 1
                    elif security not in context.shorts:
                        order_target_percent(security, 0)

    for security in context.longs:
        if data.can_trade(security):
            order_target_percent(security, context.long_leverage/(len(context.longs)+existing_longs))

    for security in context.shorts:
        if data.can_trade(security):
            order_target_percent(security, context.short_leverage/(len(context.shorts)+existing_shorts))


def take_profits(context, data):
    positions = context.portfolio.positions
    history = data.history(positions.keys(), 'close', 10, '1m').bfill().ffill()
    total_profit = 0
    for s in positions:
        if not data.can_trade(s): continue
        price = data.current(s, 'price')
        amount = positions[s].amount
        percent_profit = (amount / abs(amount)) * ((price / positions[s].cost_basis) - 1)
        if positions[s].amount > 0:
            if slope(history[s]) > 0: continue
            if slope(history[s][-5:]) > 0: continue
            if history[s][-1] > history[s][-2]: continue
            if percent_profit > context.profit_threshold or percent_profit < context.loss_threshold:
                order_target(s, 0)
                wait(context, s, 1)    # start wait
                if not context.profit_logging: continue
                pnl = (amount * (price - positions[s].cost_basis))
                total_profit += pnl
        elif positions[s].amount < 0:
            if slope(history[s]) < 0: continue
            if slope(history[s][-5:]) < 0: continue
            if history[s][-1] < history[s][-2]: continue
            if percent_profit > context.profit_threshold or percent_profit < context.loss_threshold:
                order_target(s, 0)
                wait(context, s, 1)    # start wait
                if not context.profit_logging: continue
                pnl = (abs(amount) * (positions[s].cost_basis - price))
                total_profit += pnl
    log.info("Total Profit {}".format(total_profit))

            
import statsmodels.api as sm
def slope(in_list):     # Return slope of regression line. [Make sure this list contains no nans]
    return sm.OLS(in_list, sm.add_constant(range(-len(in_list) + 1, 1))).fit().params[-1]  # slope


def wait(c, sec=None, action=None):
    if sec and action:
        if action == 1:
            c.waits[sec] = 1    # start wait
        elif action == 0:
            del c.waits[sec]    # end wait
    else:
        for sec in c.waits.copy():
            if c.waits[sec] > c.waits_max:
                del c.waits[sec]
            else:
                c.waits[sec] += 1   # increment


def my_record_vars(context, data):
    """
    Record variables at the end of each day.
    """
    longs = shorts = 0
    for position in context.portfolio.positions.itervalues():
        if position.amount > 0:
            longs += 1
        elif position.amount < 0:
            shorts += 1
    # Record our variables.
    record(leverage=context.account.leverage, long_count=longs, short_count=shorts)
    
    log.info("Today's shorts: "  +", ".join([short_.symbol for short_ in context.shorts]))
    log.info("Today's longs: "  +", ".join([long_.symbol for long_ in context.longs]))
There was a runtime error.