Back to Community
Mean-Reversion Short - Money in bad markets

Simple mean reversion short only algorithm. In bear markets it beats the S&P 500 (first attached backtest) but in longer periods it performs worse. In sideways markets I was expecting the performance to be worse but not that much so there might be an error somewhere or I don't properly understand how shorting works in Quantopian.

Details of the algorithm:

Objective
- Trade short only on a large universe of stocks, taking advantage of overbought conditions by short selling the best stocks, and buying each back when it reverts to its mean.
- Execute trades every day.
- Beat the benchmarks, especially in bear markets

Trading universe
- All stocks from AMEX, NASDAQ, and NYSE.
- Do not trade ETFs, pink sheets or bulletin board stocks.

Filters
- Minimum Average Volume of the last 20 days is above 500.000 shares (ensure liquidity).
- Minimum price is 10 USD.

Position Sizing:
- Maximum 10 positions.
- Fixed fractional risk: 2 percent.
- Maximum position size: 10 percent.

Buys
1. Trade every day.
3. Seven day average directional index (ADX) is above 50 (short term trend strength).
4. Average true range percent of the last 10 days is above 5 percent (volatility).
5. The three day RSI is above 85 (overbought on a short term basis).
6. Rank orders by the highest three day RSI.

Sells:
1. Trade every day.
2. Sell when one of the following conditions is met:
2.1 Stop Loss: 1.5 times the ten-day ATR.
2.2 Profit Target: 4 percent or more.
2.3 Time Exit: 2 days have passed and none of the above conditions is met.

Notes:
- The rebalance is split in 2 functions with an hour gap. This is just to "ensure" sell orders are filled before we start buying.
- The only purpose of canceling open orders at the end of the trading day is to avoid the console warnings.

Other algorithms:
- Weekly Rotation S&P 500
- Mean-Reversion Long - For Bold Contrarians

Clone Algorithm
35
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 quantopian.algorithm as algo
import numpy as np
import talib
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS, Q1500US
from quantopian.pipeline.factors import SimpleMovingAverage, RSI, CustomFactor, Returns, Latest, AverageDollarVolume 
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline.data import Fundamentals
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.filters.morningstar import IsPrimaryShare
import copy
import math
import datetime as dt
from datetime import datetime, timedelta, date

class Signals:
    def __init__(self, d, a):
        self.days = d
        self.atr = a
        
class is_up_1_day_ago(CustomFactor):
    inputs = [USEquityPricing.close] 
    window_length = 10

    def compute(self, today, assets, out, close):
        out[:] = close[-1] - close[-2]
        
class is_up_2_day_ago(CustomFactor):
    inputs = [USEquityPricing.close] 
    window_length = 10

    def compute(self, today, assets, out, close):
        out[:] = close[-2] - close[-3]
            
class atr_10days_percent(CustomFactor):  
    inputs = [USEquityPricing.high, USEquityPricing.low, USEquityPricing.close]  
    window_length = 11  
    def compute(self, today, assets, out, high, low, close):  
        range_high = np.maximum(high[1:], close[:-1]) 
        range_low = np.minimum(low[1:], close[:-1])  
        out[:] = np.mean(((range_high - range_low)/close[:-1])*100, axis=0) 
        
class atr_10days(CustomFactor):
    inputs = [USEquityPricing.high, USEquityPricing.low, USEquityPricing.close]  
    window_length = 11  
    def compute(self, today, assets, out, high, low, close):  
        range_high = np.maximum(high[1:], close[:-1])
        range_low = np.minimum(low[1:], close[:-1])
        out[:] = np.mean(range_high - range_low, axis=0)  

def initialize(context):
    set_commission(commission.PerTrade(cost=0.00))
    set_slippage(slippage.FixedSlippage(spread=0.00))
 
    schedule_function(sell, date_rules.every_day(), time_rules.market_open(hours=0, minutes=2), half_days=False)
    schedule_function(buy, date_rules.every_day(), time_rules.market_open(hours=0, minutes=30), half_days=False)
    schedule_function(cancel_open_orders, date_rules.every_day(), time_rules.market_close(minutes=1), half_days=False)
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close(), half_days=False)
    algo.attach_pipeline(make_pipeline(), 'my_pipeline')
    
    context.tracker = dict()  
    context.tmp_tracker= dict()
    
def make_pipeline():
    exchange = Fundamentals.exchange_id.latest
    nyse_filter = exchange.eq('NYSE')
    amex_filter = exchange.eq('AMEX')
    nasdaq_filter = exchange.eq('NAS')
    filter_exchange = nasdaq_filter | amex_filter | nyse_filter
    
    have_market_cap = morningstar.valuation.market_cap.latest.notnull() 
    
    avg_volume = SimpleMovingAverage(inputs=[USEquityPricing.volume],window_length=20)
    filter_volume = avg_volume > 500000
    
    last_price = Latest(inputs=[USEquityPricing.close], window_length=1) 
    filter_price = last_price >= 10
    
    atr_10_percent = atr_10days_percent()
    filter_atr_10 = atr_10_percent > 5
    
    rsi = RSI(inputs=[USEquityPricing.close], window_length=3)
    filter_overbought = rsi > 85
    
    filter_up_1_day_ago = is_up_1_day_ago() > 0 
    filter_up_2_day_ago = is_up_2_day_ago() > 0
    
    atr_10 = atr_10days()
    
    stocks_to_trade = filter_exchange & have_market_cap & filter_volume & filter_price & filter_atr_10 & filter_overbought & filter_up_1_day_ago & filter_up_2_day_ago

    return Pipeline(
        columns = {
            'stocks': stocks_to_trade,
            'rsi': rsi,
            'atr': atr_10
        },
        screen = (stocks_to_trade)
    )

def before_trading_start(context, data):
    context.my_output = pipeline_output('my_pipeline')
    prepare_candidates(context, data)
    
def sell(context, data):
    clean_tracker(context, data)
    add_to_tracker(context, data)
    increment_day(context, data)
    for security in context.portfolio.positions:
        if data.can_trade(security): 
            age = context.tracker[security].days
            if is_expired(age):
                order_target_percent(security, 0)
            else:
                price_share = context.portfolio.positions[security].cost_basis
                atr = context.tracker[security].atr
                stop_loss_price = get_stop_price(price_share, atr)
                order_target_percent(security, 0, style=StopOrder(stop_loss_price)) 

                profit_price = price_share * 0.96
                last_price = context.portfolio.positions[security].last_sale_price
                if last_price <= profit_price:
                    order_target_percent(security, 0)
    context.tmp_tracker = dict()

def buy(context, data):
    for security in context.candidates:
        if security not in context.portfolio.positions and data.can_trade(security): 
            if len(context.portfolio.positions) < 10:
                price_share = data.current(security,'price')
                cost = get_cost(security, context, data, price_share)
                order_value(security, -cost)
                context.tmp_tracker[security] = context.my_output.get_value(security, 'atr')
                    
def cancel_open_orders(context, data):
    for s in get_open_orders():  
        for o in get_open_orders(s):  
            cancel_order(o)       
        
def my_record_vars(context, data):
    record(leverage=context.account.leverage)

def compute_adx(stock, data):
    period = 7  
    h = data.history(stock,'high', 2*period,'1d').dropna()  
    l = data.history(stock,'low', 2*period,'1d').dropna()  
    c = data.history(stock,'close', 2*period,'1d').dropna() 
    ta_ADX = talib.ADX(h, l, c, period)  
    adx = ta_ADX[-1]
    return adx

def prepare_candidates(context, data): 
    my_out = context.my_output.copy(deep=True)
    to_remove = []
    for index, row in my_out.iterrows():        
        adx = compute_adx(index, data)
        if(adx > 50.0):
            to_remove.append(index)        
    my_out.drop(to_remove, inplace=True)   
    context.candidates = my_out.sort_values('rsi', ascending=False).head(10).index.tolist()

def get_cost(security, context, data, price_share):
    atr = context.my_output.get_value(security, 'atr')       
    stop_loss = get_stop_price(price_share, atr)
    dollar_risk_share = math.fabs(price_share - stop_loss)
    total_value = math.fabs(context.portfolio.portfolio_value) 
    cash_to_risk = total_value * 0.02
    num_shares = math.floor(cash_to_risk / dollar_risk_share)
    cost = num_shares * price_share
    max_cost = total_value * 0.1
    if cost > max_cost:
        num_shares = math.floor((total_value * 0.1) / price_share)     
    
    cost = num_shares * price_share
    return cost
    
def is_expired(days):
    return True if days > 1 else False

def clean_tracker(context, data):
    to_remove = copy.deepcopy(context.tracker)
    for sec in context.tracker:
        if not (sec in context.portfolio.positions):
           to_remove.pop(sec)          
    context.tracker = to_remove

def add_to_tracker(context, data):
    for security in context.portfolio.positions:
        if not (security in context.tracker):
            context.tracker[security] = Signals(0, context.tmp_tracker[security])
    
def increment_day(context, data):
    for sec in context.tracker:
        context.tracker[sec].days = context.tracker[sec].days + 1
    
def get_stop_price(price, atr):
    p = price + 1.5 * atr
    return 0 if p < 0 else p
There was a runtime error.
9 responses

The backtest of a longer period looks much worse.

Clone Algorithm
35
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 quantopian.algorithm as algo
import numpy as np
import talib
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS, Q1500US
from quantopian.pipeline.factors import SimpleMovingAverage, RSI, CustomFactor, Returns, Latest, AverageDollarVolume 
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline.data import Fundamentals
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.filters.morningstar import IsPrimaryShare
import copy
import math
import datetime as dt
from datetime import datetime, timedelta, date

class Signals:
    def __init__(self, d, a):
        self.days = d
        self.atr = a
        
class is_up_1_day_ago(CustomFactor):
    inputs = [USEquityPricing.close] 
    window_length = 10

    def compute(self, today, assets, out, close):
        out[:] = close[-1] - close[-2]
        
class is_up_2_day_ago(CustomFactor):
    inputs = [USEquityPricing.close] 
    window_length = 10

    def compute(self, today, assets, out, close):
        out[:] = close[-2] - close[-3]
            
class atr_10days_percent(CustomFactor):  
    inputs = [USEquityPricing.high, USEquityPricing.low, USEquityPricing.close]  
    window_length = 11  
    def compute(self, today, assets, out, high, low, close):  
        range_high = np.maximum(high[1:], close[:-1]) 
        range_low = np.minimum(low[1:], close[:-1])  
        out[:] = np.mean(((range_high - range_low)/close[:-1])*100, axis=0) 
        
class atr_10days(CustomFactor):
    inputs = [USEquityPricing.high, USEquityPricing.low, USEquityPricing.close]  
    window_length = 11  
    def compute(self, today, assets, out, high, low, close):  
        range_high = np.maximum(high[1:], close[:-1])
        range_low = np.minimum(low[1:], close[:-1])
        out[:] = np.mean(range_high - range_low, axis=0)  

def initialize(context):
    set_commission(commission.PerTrade(cost=0.00))
    set_slippage(slippage.FixedSlippage(spread=0.00))
 
    schedule_function(sell, date_rules.every_day(), time_rules.market_open(hours=0, minutes=2), half_days=False)
    schedule_function(buy, date_rules.every_day(), time_rules.market_open(hours=0, minutes=30), half_days=False)
    schedule_function(cancel_open_orders, date_rules.every_day(), time_rules.market_close(minutes=1), half_days=False)
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close(), half_days=False)
    algo.attach_pipeline(make_pipeline(), 'my_pipeline')
    
    context.tracker = dict()  
    context.tmp_tracker= dict()
    
def make_pipeline():
    exchange = Fundamentals.exchange_id.latest
    nyse_filter = exchange.eq('NYSE')
    amex_filter = exchange.eq('AMEX')
    nasdaq_filter = exchange.eq('NAS')
    filter_exchange = nasdaq_filter | amex_filter | nyse_filter
    
    have_market_cap = morningstar.valuation.market_cap.latest.notnull() 
    
    avg_volume = SimpleMovingAverage(inputs=[USEquityPricing.volume],window_length=20)
    filter_volume = avg_volume > 500000
    
    last_price = Latest(inputs=[USEquityPricing.close], window_length=1) 
    filter_price = last_price >= 10
    
    atr_10_percent = atr_10days_percent()
    filter_atr_10 = atr_10_percent > 5
    
    rsi = RSI(inputs=[USEquityPricing.close], window_length=3)
    filter_overbought = rsi > 85
    
    filter_up_1_day_ago = is_up_1_day_ago() > 0 
    filter_up_2_day_ago = is_up_2_day_ago() > 0
    
    atr_10 = atr_10days()
    
    stocks_to_trade = filter_exchange & have_market_cap & filter_volume & filter_price & filter_atr_10 & filter_overbought & filter_up_1_day_ago & filter_up_2_day_ago

    return Pipeline(
        columns = {
            'stocks': stocks_to_trade,
            'rsi': rsi,
            'atr': atr_10
        },
        screen = (stocks_to_trade)
    )

def before_trading_start(context, data):
    context.my_output = pipeline_output('my_pipeline')
    prepare_candidates(context, data)
    
def sell(context, data):
    clean_tracker(context, data)
    add_to_tracker(context, data)
    increment_day(context, data)
    for security in context.portfolio.positions:
        if data.can_trade(security): 
            age = context.tracker[security].days
            if is_expired(age):
                order_target_percent(security, 0)
            else:
                price_share = context.portfolio.positions[security].cost_basis
                atr = context.tracker[security].atr
                stop_loss_price = get_stop_price(price_share, atr)
                order_target_percent(security, 0, style=StopOrder(stop_loss_price)) 

                profit_price = price_share * 0.96
                last_price = context.portfolio.positions[security].last_sale_price
                if last_price <= profit_price:
                    order_target_percent(security, 0)
    context.tmp_tracker = dict()

def buy(context, data):
    for security in context.candidates:
        if security not in context.portfolio.positions and data.can_trade(security): 
            if len(context.portfolio.positions) < 10:
                price_share = data.current(security,'price')
                cost = get_cost(security, context, data, price_share)
                order_value(security, -cost)
                context.tmp_tracker[security] = context.my_output.get_value(security, 'atr')
                    
def cancel_open_orders(context, data):
    for s in get_open_orders():  
        for o in get_open_orders(s):  
            cancel_order(o)       
        
def my_record_vars(context, data):
    record(leverage=context.account.leverage)

def compute_adx(stock, data):
    period = 7  
    h = data.history(stock,'high', 2*period,'1d').dropna()  
    l = data.history(stock,'low', 2*period,'1d').dropna()  
    c = data.history(stock,'close', 2*period,'1d').dropna() 
    ta_ADX = talib.ADX(h, l, c, period)  
    adx = ta_ADX[-1]
    return adx

def prepare_candidates(context, data): 
    my_out = context.my_output.copy(deep=True)
    to_remove = []
    for index, row in my_out.iterrows():        
        adx = compute_adx(index, data)
        if(adx > 50.0):
            to_remove.append(index)        
    my_out.drop(to_remove, inplace=True)   
    context.candidates = my_out.sort_values('rsi', ascending=False).head(10).index.tolist()

def get_cost(security, context, data, price_share):
    atr = context.my_output.get_value(security, 'atr')       
    stop_loss = get_stop_price(price_share, atr)
    dollar_risk_share = math.fabs(price_share - stop_loss)
    total_value = math.fabs(context.portfolio.portfolio_value) 
    cash_to_risk = total_value * 0.02
    num_shares = math.floor(cash_to_risk / dollar_risk_share)
    cost = num_shares * price_share
    max_cost = total_value * 0.1
    if cost > max_cost:
        num_shares = math.floor((total_value * 0.1) / price_share)     
    
    cost = num_shares * price_share
    return cost
    
def is_expired(days):
    return True if days > 1 else False

def clean_tracker(context, data):
    to_remove = copy.deepcopy(context.tracker)
    for sec in context.tracker:
        if not (sec in context.portfolio.positions):
           to_remove.pop(sec)          
    context.tracker = to_remove

def add_to_tracker(context, data):
    for security in context.portfolio.positions:
        if not (security in context.tracker):
            context.tracker[security] = Signals(0, context.tmp_tracker[security])
    
def increment_day(context, data):
    for sec in context.tracker:
        context.tracker[sec].days = context.tracker[sec].days + 1
    
def get_stop_price(price, atr):
    p = price + 1.5 * atr
    return 0 if p < 0 else p
There was a runtime error.

Good idea. I find that the leverage is sometimes higher than one? What if we add a condition that limits trade when the market is going down?

Hi,

Regarding leverage, you are right. I don't understand how to keep it under control in an "only short" algorithm.

If you want to trade just in downtrend markets, you could probably use the daily SMA band of the SPY. Just trade when the last close of the SPY is below the 200 daily SMA band (with a -2% buffer). Otherwise you exit all your positions. I used something similar to just trade uptrend markets here

@Mark I add the downtrend in the algorithm. As a result, the volatility declines, while the return decreases.

In reality, the case that the rsi is greater than 85 seems not so common for most stocks. Therefore, it is not easy to filter more than five stocks during real trading. But if I set the rsi lower, the return also declines.

To go short could be like this:
for equity in short_list:
if data.can_trade(equity):
order_target_percent(equity, -min(0.3 / len(short_list), context.MAX_IN_ONE))
for equity in long_list:
if data.can_trade(equity):
order_target_percent(equity, min(0.7 / len(long_list), context.MAX_IN_ONE))

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
#https://www.quantopian.com/algorithms/5c57209c910705004cb3ba98

import quantopian.algorithm as algo
import numpy as np
import talib
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS, Q1500US
from quantopian.pipeline.factors import SimpleMovingAverage, RSI, CustomFactor, Returns, Latest, AverageDollarVolume 
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline.data import Fundamentals
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.filters.morningstar import IsPrimaryShare
import copy
import math
import datetime as dt
from datetime import datetime, timedelta, date

class Signals:
    def __init__(self, d, a):
        self.days = d
        self.atr = a
        
class is_up_1_day_ago(CustomFactor):
    inputs = [USEquityPricing.close] 
    window_length = 10

    def compute(self, today, assets, out, close):
        out[:] = close[-1] - close[-2]
        
class is_up_2_day_ago(CustomFactor):
    inputs = [USEquityPricing.close] 
    window_length = 10

    def compute(self, today, assets, out, close):
        out[:] = close[-2] - close[-3]
            
class atr_10days_percent(CustomFactor):  
    inputs = [USEquityPricing.high, USEquityPricing.low, USEquityPricing.close]  
    window_length = 11  
    def compute(self, today, assets, out, high, low, close):  
        range_high = np.maximum(high[1:], close[:-1]) 
        range_low = np.minimum(low[1:], close[:-1])  
        out[:] = np.mean(((range_high - range_low)/close[:-1])*100, axis=0) 
        
class atr_10days(CustomFactor):
    inputs = [USEquityPricing.high, USEquityPricing.low, USEquityPricing.close]  
    window_length = 11  
    def compute(self, today, assets, out, high, low, close):  
        range_high = np.maximum(high[1:], close[:-1])
        range_low = np.minimum(low[1:], close[:-1])
        out[:] = np.mean(range_high - range_low, axis=0)  

def initialize(context):
    set_commission(commission.PerTrade(cost=0.00))
    set_slippage(slippage.FixedSlippage(spread=0.00))
 
    schedule_function(sell, date_rules.every_day(), time_rules.market_open(hours=0, minutes=2), half_days=False)
    schedule_function(buy, date_rules.every_day(), time_rules.market_open(hours=0, minutes=30), half_days=False)
    schedule_function(cancel_open_orders, date_rules.every_day(), time_rules.market_close(minutes=1), half_days=False)
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close(), half_days=False)
    algo.attach_pipeline(make_pipeline(), 'my_pipeline')
    
    context.tracker = dict()  
    context.tmp_tracker= dict()
    
def make_pipeline():
    exchange = Fundamentals.exchange_id.latest
    nyse_filter = exchange.eq('NYSE')
    amex_filter = exchange.eq('AMEX')
    nasdaq_filter = exchange.eq('NAS')
    filter_exchange = nasdaq_filter | amex_filter | nyse_filter
    
    have_market_cap = morningstar.valuation.market_cap.latest.notnull() 
    
    avg_volume = SimpleMovingAverage(inputs=[USEquityPricing.volume],window_length=20)
    filter_volume = avg_volume > 500000
    
    last_price = Latest(inputs=[USEquityPricing.close], window_length=1) 
    filter_price = last_price >= 1
    
    atr_10_percent = atr_10days_percent()
    filter_atr_10 = atr_10_percent > 5
    
    rsi = RSI(inputs=[USEquityPricing.close], window_length=3)
    filter_overbought = rsi > 70
    
    atr_10 = atr_10days()
    
    stocks_to_trade = filter_exchange & have_market_cap & filter_volume & filter_price & filter_atr_10 & filter_overbought

    return Pipeline(
        columns = {
            'stocks': stocks_to_trade,
            'rsi': rsi,
            'atr': atr_10
        },
        screen = (stocks_to_trade)
    )

def before_trading_start(context, data):
    context.my_output = pipeline_output('my_pipeline')
    prepare_candidates(context, data)
    trend(context, data)

def trend(context, data):
    index_history = data.history(sid(8554), "close", 200, "1d")  # Gets index history
    index_sma = index_history.mean()  # Average of index history
    current_index = index_history[-1]  # get last element
    # declare bull if index is over average
    bear_market = current_index <= index_sma
    return bear_market
    
def sell(context, data):
    clean_tracker(context, data)
    add_to_tracker(context, data)
    increment_day(context, data)
    for security in context.portfolio.positions:
        if data.can_trade(security): 
            age = context.tracker[security].days
            if is_expired(age):
                order_target_percent(security, 0)
            else:
                price_share = context.portfolio.positions[security].cost_basis
                atr = context.tracker[security].atr
                stop_loss_price = get_stop_price(price_share, atr)
                order_target_percent(security, 0, style=StopOrder(stop_loss_price)) 

                profit_price = price_share * 0.96
                last_price = context.portfolio.positions[security].last_sale_price
                if last_price <= profit_price:
                    order_target_percent(security, 0)
    context.tmp_tracker = dict()

def buy(context, data):
    if trend(context, data):        
        for security in context.candidates:
            if security not in context.portfolio.positions and data.can_trade(security): 
                if len(context.portfolio.positions) < 10:
                    price_share = data.current(security,'price')
                    cost = get_cost(security, context, data, price_share)
                    order_value(security, -cost)
                    context.tmp_tracker[security] = context.my_output.get_value(security, 'atr')
                    
def cancel_open_orders(context, data):
    for s in get_open_orders():  
        for o in get_open_orders(s):  
            cancel_order(o)       
        
def my_record_vars(context, data):
    record(leverage=context.account.leverage)

def compute_adx(stock, data):
    period = 7  
    h = data.history(stock,'high', 2*period,'1d').dropna()  
    l = data.history(stock,'low', 2*period,'1d').dropna()  
    c = data.history(stock,'close', 2*period,'1d').dropna() 
    ta_ADX = talib.ADX(h, l, c, period)  
    adx = ta_ADX[-1]
    return adx

def prepare_candidates(context, data): 
    my_out = context.my_output.copy(deep=True)
    to_remove = []
    for index, row in my_out.iterrows():        
        adx = compute_adx(index, data)
        if(adx > 50.0):
            to_remove.append(index)        
    my_out.drop(to_remove, inplace=True)   
    context.candidates = my_out.sort_values('rsi', ascending=False).head(10).index.tolist()

def get_cost(security, context, data, price_share):
    atr = context.my_output.get_value(security, 'atr')       
    stop_loss = get_stop_price(price_share, atr)
    dollar_risk_share = math.fabs(price_share - stop_loss)
    total_value = math.fabs(context.portfolio.portfolio_value) 
    cash_to_risk = total_value * 0.02
    num_shares = math.floor(cash_to_risk / dollar_risk_share)
    cost = num_shares * price_share
    max_cost = total_value * 0.1
    if cost > max_cost:
        num_shares = math.floor((total_value * 0.1) / price_share)     
    
    cost = num_shares * price_share
    return cost
    
def is_expired(days):
    return True if days > 1 else False

def clean_tracker(context, data):
    to_remove = copy.deepcopy(context.tracker)
    for sec in context.tracker:
        if not (sec in context.portfolio.positions):
           to_remove.pop(sec)          
    context.tracker = to_remove

def add_to_tracker(context, data):
    for security in context.portfolio.positions:
        if not (security in context.tracker):
            context.tracker[security] = Signals(0, context.tmp_tracker[security])
    
def increment_day(context, data):
    for sec in context.tracker:
        context.tracker[sec].days = context.tracker[sec].days + 1
    
def get_stop_price(price, atr):
    p = price + 1.5 * atr
    return 0 if p < 0 else p
There was a runtime error.

It's very difficult to control the leverage of a short-only algo. You can under-lever so when it moves against you it's still a reasonable leverage. That or improve the predictive power of your algo so that it never moves too hard against you. Easier said than done. Holding more than 10 positions may help reduce volatility, drawdowns, and therefore leverage spikes by lowering single-name risk. Another thing you can do is diversify across sectors.

Unfortunately on Quantopian we only have one crash available in the data. However, even if there were more it's not a good idea to tailor algorithms to one-off events. The market is always doing new things -- setting new records, exhibiting new behaviors, etc. The 2019/2020 crash will likely be nothing like 2008.

Note: technically opening a short positions is "selling" and closing a short position is "buying." I use "open" and "close" when talking about shorts so there's no ambiguity.

Hi all,
I have inserted a further filter based on the vix and 50 moving average, looking for more stability in gains.. try to take a look .. thanks

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
#https://www.quantopian.com/algorithms/5c57209c910705004cb3ba98

import quantopian.algorithm as algo
import numpy as np
import talib
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS, Q1500US
from quantopian.pipeline.factors import SimpleMovingAverage, RSI, CustomFactor, Returns, Latest, AverageDollarVolume 
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline.data import Fundamentals
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.filters.morningstar import IsPrimaryShare
import copy
import math
import datetime as dt
from datetime import datetime, timedelta, date


def look_ahead(df):  
    # Introduce lookahead in order to access VIX Open on trading days  
    df = df.rename(columns={'VIX Open': 'open'})  
    df = df.rename(columns={'VIX Close': 'close'})  
    df = df.rename(columns={'VIX High': 'high'})  
    df = df.rename(columns={'VIX Low': 'low'})  
    return df  


class Signals:
    def __init__(self, d, a):
        self.days = d
        self.atr = a
        
class is_up_1_day_ago(CustomFactor):
    inputs = [USEquityPricing.close] 
    window_length = 10

    def compute(self, today, assets, out, close):
        out[:] = close[-1] - close[-2]
        
class is_up_2_day_ago(CustomFactor):
    inputs = [USEquityPricing.close] 
    window_length = 10

    def compute(self, today, assets, out, close):
        out[:] = close[-2] - close[-3]
            
class atr_10days_percent(CustomFactor):  
    inputs = [USEquityPricing.high, USEquityPricing.low, USEquityPricing.close]  
    window_length = 11  
    def compute(self, today, assets, out, high, low, close):  
        range_high = np.maximum(high[1:], close[:-1]) 
        range_low = np.minimum(low[1:], close[:-1])  
        out[:] = np.mean(((range_high - range_low)/close[:-1])*100, axis=0) 
        
class atr_10days(CustomFactor):
    inputs = [USEquityPricing.high, USEquityPricing.low, USEquityPricing.close]  
    window_length = 11  
    def compute(self, today, assets, out, high, low, close):  
        range_high = np.maximum(high[1:], close[:-1])
        range_low = np.minimum(low[1:], close[:-1])
        out[:] = np.mean(range_high - range_low, axis=0)  

def initialize(context):
    set_commission(commission.PerShare())
    set_slippage(slippage.FixedSlippage(spread=0.00))
 
    schedule_function(sell, date_rules.every_day(), time_rules.market_open(hours=0, minutes=2), half_days=False)
    schedule_function(buy, date_rules.every_day(), time_rules.market_open(hours=0, minutes=30), half_days=False)
    schedule_function(cancel_open_orders, date_rules.every_day(), time_rules.market_close(minutes=1), half_days=False)
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close(), half_days=False)
    algo.attach_pipeline(make_pipeline(), 'my_pipeline')
    
    context.tracker = dict()  
    context.tmp_tracker= dict()
    
    fetch_csv('http://www.cboe.com/publish/scheduledtask/mktdata/datahouse/vixcurrent.csv',  
              symbol='vixcurrent',  
              date_column = 'Date',  
              date_format = '%m/%d/%y',  
              post_func=look_ahead,  
              header = 1)  
    
def make_pipeline():
    exchange = Fundamentals.exchange_id.latest
    nyse_filter = exchange.eq('NYSE')
    amex_filter = exchange.eq('AMEX')
    nasdaq_filter = exchange.eq('NAS')
    filter_exchange = nyse_filter and amex_filter and nasdaq_filter
    
    have_market_cap = morningstar.valuation.market_cap.latest.notnull() 
    
    avg_volume = SimpleMovingAverage(inputs=[USEquityPricing.volume],window_length=20)
    filter_volume = avg_volume > 500000
    
    last_price = Latest(inputs=[USEquityPricing.close], window_length=1) 
    filter_price = last_price >= 1
    
    atr_10_percent = atr_10days_percent()
    filter_atr_10 = atr_10_percent > 5
    
    rsi = RSI(inputs=[USEquityPricing.close], window_length=3)
    filter_overbought = rsi > 70
    
    atr_10 = atr_10days()
    
    stocks_to_trade = filter_exchange & have_market_cap & filter_volume & filter_price & filter_atr_10 & filter_overbought

    return Pipeline(
        columns = {
            'stocks': stocks_to_trade,
            'rsi': rsi,
            'atr': atr_10
        },
        screen = (stocks_to_trade)
    )

def before_trading_start(context, data):
    context.my_output = pipeline_output('my_pipeline')
    prepare_candidates(context, data)
    trend(context, data)

def trend(context, data):
    index_history = data.history(sid(8554), "close", 50, "1d") 
    vix=data.current('vixcurrent','close')
    # Gets index history
    index_sma = index_history.mean()  # Average of index history
    current_index = index_history[-1] 
    condition1 = current_index < index_sma# get last element
    # declare bull if index is over average
    bear_market = vix>17 and condition1
    return bear_market
    
def sell(context, data):
    clean_tracker(context, data)
    add_to_tracker(context, data)
    increment_day(context, data)
    for security in context.portfolio.positions:
        if data.can_trade(security): 
            age = context.tracker[security].days
            if is_expired(age):
                order_target_percent(security, 0)
            else:
                price_share = context.portfolio.positions[security].cost_basis
                atr = context.tracker[security].atr
                stop_loss_price = get_stop_price(price_share, atr)
                order_target_percent(security, 0, style=StopOrder(stop_loss_price)) 

                profit_price = price_share * 0.96
                last_price = context.portfolio.positions[security].last_sale_price
                if last_price <= profit_price:
                    order_target_percent(security, 0)
    context.tmp_tracker = dict()

def buy(context, data):
    if trend(context, data):        
        for security in context.candidates:
            if security not in context.portfolio.positions and data.can_trade(security): 
                if len(context.portfolio.positions) < 10:
                    price_share = data.current(security,'price')
                    cost = get_cost(security, context, data, price_share)
                    order_value(security, -cost)
                    context.tmp_tracker[security] = context.my_output.get_value(security, 'atr')
                    
def cancel_open_orders(context, data):
    for s in get_open_orders():  
        for o in get_open_orders(s):  
            cancel_order(o)       
        
def my_record_vars(context, data):
    record(leverage=context.account.leverage)

def compute_adx(stock, data):
    period = 7  
    h = data.history(stock,'high', 2*period,'1d').dropna()  
    l = data.history(stock,'low', 2*period,'1d').dropna()  
    c = data.history(stock,'close', 2*period,'1d').dropna() 
    ta_ADX = talib.ADX(h, l, c, period)  
    adx = ta_ADX[-1]
    return adx

def prepare_candidates(context, data): 
    my_out = context.my_output.copy(deep=True)
    to_remove = []
    for index, row in my_out.iterrows():        
        adx = compute_adx(index, data)
        if(adx > 50.0):
            to_remove.append(index)        
    my_out.drop(to_remove, inplace=True)   
    context.candidates = my_out.sort_values('rsi', ascending=False).head(10).index.tolist()

def get_cost(security, context, data, price_share):
    atr = context.my_output.get_value(security, 'atr')       
    stop_loss = get_stop_price(price_share, atr)
    dollar_risk_share = math.fabs(price_share - stop_loss)
    total_value = math.fabs(context.portfolio.portfolio_value) 
    cash_to_risk = total_value * 0.02
    num_shares = math.floor(cash_to_risk / dollar_risk_share)
    cost = num_shares * price_share
    max_cost = total_value * 0.1
    if cost > max_cost:
        num_shares = math.floor((total_value * 0.1) / price_share)     
    
    cost = num_shares * price_share
    return cost
    
def is_expired(days):
    return True if days > 1 else False

def clean_tracker(context, data):
    to_remove = copy.deepcopy(context.tracker)
    for sec in context.tracker:
        if not (sec in context.portfolio.positions):
           to_remove.pop(sec)          
    context.tracker = to_remove

def add_to_tracker(context, data):
    for security in context.portfolio.positions:
        if not (security in context.tracker):
            context.tracker[security] = Signals(0, context.tmp_tracker[security])
    
def increment_day(context, data):
    for sec in context.tracker:
        context.tracker[sec].days = context.tracker[sec].days + 1
    
def get_stop_price(price, atr):
    p = price + 1.5 * atr
    return 0 if p < 0 else p
There was a runtime error.

@daniele carabini
Well done. But I think there is bias if you backtest only through 2008-2009.

Forever's filter exchange screens out OTCPK and OTCBB leaving NYSE, NAS, AMEX:
filter_exchange = nasdaq_filter | amex_filter | nyse_filter

The modification in the most recent--and this is surely counter-intuitive--causes them to all be NYSE, keeping just the last one:
filter_exchange = nasdaq_filter and amex_filter and nyse_filter

Another way to go about it, where the tilde (~) means not:
filter_exchange = ~Fundamentals.exchange_id.latest.startswith('OTC') # not_otc

Meanwhile on a larger scale if I may, before collections starting with Q500US and eventually QTradableStocksUS, there was a time when Q's filters/mask would look not too much unlike this:

    f = Fundamentals  
    m &= (  # mask  
           f.is_primary_share                                       # primary_share  
        & ~f.is_depositary_receipt.latest                           # not_depositary  
        & ~f.exchange_id          .latest.startswith('OTC')         # not_otc  
        & ~f.symbol               .latest.endswith('.WI')           # not_wi  
        & ~f.standard_name        .latest.matches('.* L[. ]?P.?$')  # not_limited partnership  
        &  f.security_type        .latest.eq('ST00000001')          # common_stock  
        &  f.market_cap           .latest.notnull()                 # has market_cap  
    )  

To use those where the mask is progressively applied to each operation, a route I prefer, can sometimes be not just an increase in speed with fewer and fewer stocks being processed in each step but also very important, as without it can result in unintended results. It's hard to explain, I just recall a long time ago the hours and hours I spent experimenting around and finding that weights or included stocks would be way off at times until I began adhering to this pattern. So I've become a cheerleader for it, adding to m (mask) each time and always using/applying it whenever possible. You would find, for example, that the RSI( ... mask=m) step would be chewing on fewer stocks. A custom factor doing calculations on how all of the stock's values relate to each other and/or filtering them could otherwise be operating on stocks that may later be tossed out, so maybe that makes the point well enough.
I haven't tested this pipe for returns but the pipeline is indeed different, 33 in number vs 42 on first day.

def make_pipeline():  
    avg_volume = SimpleMovingAverage(inputs=[USEquityPricing.volume],window_length=20)

    # mask is 'm', initialized  
    m = avg_volume > 500000

    f = Fundamentals  
    m &= (  # mask  
           f.is_primary_share                                       # primary_share  
        & ~f.is_depositary_receipt.latest                           # not_depositary  
        & ~f.exchange_id          .latest.startswith('OTC')         # not_otc  
        & ~f.symbol               .latest.endswith('.WI')           # not_wi  
        & ~f.standard_name        .latest.matches('.* L[. ]?P.?$')  # not_limited partnership  
        &  f.security_type        .latest.eq('ST00000001')          # common_stock  
        &  f.market_cap           .latest.notnull()                 # has market_cap  
    )

    last_price = Latest(inputs=[USEquityPricing.close], window_length=1, mask=m) # using the mask  
    m &= last_price >= 1  # adding to mask

    atr_10_percent = atr_10days_percent(mask=m)  
    m &= atr_10_percent > 5

    rsi = RSI(inputs=[USEquityPricing.close], window_length=3, mask=m)  
    m &= rsi > 70

    atr_10 = atr_10days(mask=m)

    return Pipeline(  
        columns = {  
            'rsi': rsi,  
            'atr': atr_10,  
        },  
        screen = m  
    )  

I also found that sending out orders 1 hour before the market opens and 1 hour after the market opens can help to avoid some unnecessary volatility.

*Note: I also decreased the initial capital to reflect a strategy that I might actually trade with.

Clone Algorithm
9
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 quantopian.algorithm as algo
import numpy as np
import talib
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS, Q1500US
from quantopian.pipeline.factors import SimpleMovingAverage, RSI, CustomFactor, Returns, Latest, AverageDollarVolume 
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline.data import Fundamentals
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.filters.morningstar import IsPrimaryShare
import copy
import math
import datetime as dt
from datetime import datetime, timedelta, date

class Signals:
    def __init__(self, d, a):
        self.days = d
        self.atr = a
        
class is_up_1_day_ago(CustomFactor):
    inputs = [USEquityPricing.close] 
    window_length = 10

    def compute(self, today, assets, out, close):
        out[:] = close[-1] - close[-2]
        
class is_up_2_day_ago(CustomFactor):
    inputs = [USEquityPricing.close] 
    window_length = 10

    def compute(self, today, assets, out, close):
        out[:] = close[-2] - close[-3]
            
class atr_10days_percent(CustomFactor):  
    inputs = [USEquityPricing.high, USEquityPricing.low, USEquityPricing.close]  
    window_length = 11  
    def compute(self, today, assets, out, high, low, close):  
        range_high = np.maximum(high[1:], close[:-1]) 
        range_low = np.minimum(low[1:], close[:-1])  
        out[:] = np.mean(((range_high - range_low)/close[:-1])*100, axis=0) 
        
class atr_10days(CustomFactor):
    inputs = [USEquityPricing.high, USEquityPricing.low, USEquityPricing.close]  
    window_length = 11  
    def compute(self, today, assets, out, high, low, close):  
        range_high = np.maximum(high[1:], close[:-1])
        range_low = np.minimum(low[1:], close[:-1])
        out[:] = np.mean(range_high - range_low, axis=0)  

def initialize(context):
    set_commission(commission.PerTrade(cost=0.00))
    set_slippage(slippage.FixedSlippage(spread=0.00))
 
    schedule_function(sell, date_rules.every_day(), time_rules.market_open(hours=1, minutes=2), half_days=False)
    schedule_function(buy, date_rules.every_day(), time_rules.market_open(hours=1, minutes=30), half_days=False)
    schedule_function(cancel_open_orders, date_rules.every_day(), time_rules.market_close(minutes=1), half_days=False)
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close(), half_days=False)
    algo.attach_pipeline(make_pipeline(), 'my_pipeline')
    
    context.tracker = dict()  
    context.tmp_tracker= dict()
    
def make_pipeline():
    exchange = Fundamentals.exchange_id.latest
    nyse_filter = exchange.eq('NYSE')
    amex_filter = exchange.eq('AMEX')
    nasdaq_filter = exchange.eq('NAS')
    filter_exchange = nasdaq_filter | amex_filter | nyse_filter
    
    have_market_cap = morningstar.valuation.market_cap.latest.notnull() 
    
    avg_volume = SimpleMovingAverage(inputs=[USEquityPricing.volume],window_length=20)
    filter_volume = avg_volume > 500000
    
    last_price = Latest(inputs=[USEquityPricing.close], window_length=1) 
    filter_price = last_price >= 10
    
    atr_10_percent = atr_10days_percent()
    filter_atr_10 = atr_10_percent > 5
    
    rsi = RSI(inputs=[USEquityPricing.close], window_length=3)
    filter_overbought = rsi > 85
    
    filter_up_1_day_ago = is_up_1_day_ago() > 0 
    filter_up_2_day_ago = is_up_2_day_ago() > 0
    
    atr_10 = atr_10days()
    
    stocks_to_trade = filter_exchange & have_market_cap & filter_volume & filter_price & filter_atr_10 & filter_overbought & filter_up_1_day_ago & filter_up_2_day_ago

    return Pipeline(
        columns = {
            'stocks': stocks_to_trade,
            'rsi': rsi,
            'atr': atr_10
        },
        screen = (stocks_to_trade)
    )

def before_trading_start(context, data):
    context.my_output = pipeline_output('my_pipeline')
    prepare_candidates(context, data)
    
def sell(context, data):
    clean_tracker(context, data)
    add_to_tracker(context, data)
    increment_day(context, data)
    for security in context.portfolio.positions:
        if data.can_trade(security): 
            age = context.tracker[security].days
            if is_expired(age):
                order_target_percent(security, 0)
            else:
                price_share = context.portfolio.positions[security].cost_basis
                atr = context.tracker[security].atr
                stop_loss_price = get_stop_price(price_share, atr)
                order_target_percent(security, 0, style=StopOrder(stop_loss_price)) 

                profit_price = price_share * 0.96
                last_price = context.portfolio.positions[security].last_sale_price
                if last_price <= profit_price:
                    order_target_percent(security, 0)
    context.tmp_tracker = dict()

def buy(context, data):
    for security in context.candidates:
        if security not in context.portfolio.positions and data.can_trade(security): 
            if len(context.portfolio.positions) < 10:
                price_share = data.current(security,'price')
                cost = get_cost(security, context, data, price_share)
                order_value(security, -cost)
                context.tmp_tracker[security] = context.my_output.get_value(security, 'atr')
                    
def cancel_open_orders(context, data):
    for s in get_open_orders():  
        for o in get_open_orders(s):  
            cancel_order(o)       
        
def my_record_vars(context, data):
    record(leverage=context.account.leverage)

def compute_adx(stock, data):
    period = 7  
    h = data.history(stock,'high', 2*period,'1d').dropna()  
    l = data.history(stock,'low', 2*period,'1d').dropna()  
    c = data.history(stock,'close', 2*period,'1d').dropna() 
    ta_ADX = talib.ADX(h, l, c, period)  
    adx = ta_ADX[-1]
    return adx

def prepare_candidates(context, data): 
    my_out = context.my_output.copy(deep=True)
    to_remove = []
    for index, row in my_out.iterrows():        
        adx = compute_adx(index, data)
        if(adx > 50.0):
            to_remove.append(index)        
    my_out.drop(to_remove, inplace=True)   
    context.candidates = my_out.sort_values('rsi', ascending=False).head(10).index.tolist()

def get_cost(security, context, data, price_share):
    atr = context.my_output.get_value(security, 'atr')       
    stop_loss = get_stop_price(price_share, atr)
    dollar_risk_share = math.fabs(price_share - stop_loss)
    total_value = math.fabs(context.portfolio.portfolio_value) 
    cash_to_risk = total_value * 0.02
    num_shares = math.floor(cash_to_risk / dollar_risk_share)
    cost = num_shares * price_share
    max_cost = total_value * 0.1
    if cost > max_cost:
        num_shares = math.floor((total_value * 0.1) / price_share)     
    
    cost = num_shares * price_share
    return cost
    
def is_expired(days):
    return True if days > 1 else False

def clean_tracker(context, data):
    to_remove = copy.deepcopy(context.tracker)
    for sec in context.tracker:
        if not (sec in context.portfolio.positions):
           to_remove.pop(sec)          
    context.tracker = to_remove

def add_to_tracker(context, data):
    for security in context.portfolio.positions:
        if not (security in context.tracker):
            context.tracker[security] = Signals(0, context.tmp_tracker[security])
    
def increment_day(context, data):
    for sec in context.tracker:
        context.tracker[sec].days = context.tracker[sec].days + 1
    
def get_stop_price(price, atr):
    p = price + 1.5 * atr
    return 0 if p < 0 else p
There was a runtime error.