Hello Quantopian community,

I have implemented the classical 5-10-20 EMA crossover trend following strategy with one twist. Instead of buying the SPY, I am shorting SDS (ultra inverse ETF). The strategy buys when 5 day EMA > 10 day EMA and 10 day EMA > 20 day EMA. The strategy has checks in place to ensure that prior orders are executed before placing new and to make sure we do not exceed cash balance.

I am interested to know how this can be improved further including any drawbacks or pitfalls that I might be missing. Any comments about reducing volatility or increasing alpha are certainly welcome.

Thanks,

-Dev

Clone Algorithm

381

Loading...

There was an error loading this backtest.

Backtest from
to
with
initial capital

Cumulative performance:

Algorithm
Benchmark

Custom data:

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 talib import math import pandas as pd import numpy as np from pytz import timezone # Put any initialization logic here. The context object will be passed to # the other methods in your algorithm. def initialize(context): context.secs = [ sid(8554), # SPY sid(32268), # SH sid(32270), # SSO sid(32382) # SDS ] # context.spy = sid(8554) context.sso = sid(32270) context.sds = sid(32382) context.sh = sid(32268) context.max_notional = 10000.0 context.min_notional = -10000.0 context.invested = False context.shorted = False context.lastbuydate = '01011990' context.longshort = 'none' context.shortenabled = False set_commission(commission.PerTrade(cost=0.0)) set_slippage(slippage.FixedSlippage(spread=0.00)) def lastboughttoday(context): loc_dt = get_datetime().astimezone(timezone('US/Eastern')) todaysdate = str(loc_dt.month)+str(loc_dt.day)+str(loc_dt.year) if(context.lastbuydate == todaysdate ): return True else: return False def updatelastbought(context): loc_dt = get_datetime().astimezone(timezone('US/Eastern')) context.lastbuydate = str(loc_dt.month)+str(loc_dt.day)+str(loc_dt.year) def intradingwindow_check(context): # Converts all time-zones into US EST to avoid confusion loc_dt = get_datetime().astimezone(timezone('US/Eastern')) if loc_dt.hour >= 10 and (loc_dt.minute == 45 ): #log.info("First Timestamp: Year {0}, Month {1}, Day {2} ".format(loc_dt.year, loc_dt.month, loc_dt.day)) return True else: return False # Will be called on every trade event for the securities you specify. def handle_data(context, data): loc_dt = get_datetime().astimezone(timezone('US/Eastern')) if not intradingwindow_check(context): return if has_orders(context): return if lastboughttoday(context): return # data[sid(X)] holds the trade event data for that security. close_price =history(bar_count=25, frequency='1d',field='close_price') open_price = history(bar_count=25, frequency='1d',field='open_price') high_price = history(bar_count=25, frequency='1d',field='high') low_price = history(bar_count=25, frequency='1d',field='low') concat_price = pd.concat((open_price, close_price,high_price, low_price)) mean_price = concat_price.groupby(concat_price.index).mean() spy_series = mean_price[context.spy] spy_ema5 = talib.EMA(spy_series, timeperiod=5) spy_ema10 = talib.EMA(spy_series, timeperiod=10) spy_ema20 = talib.EMA(spy_series, timeperiod=20) # [0:-1] above remove the last entry #if loc_dt.hour == 11 and loc_dt.minute == 45: # print('EMA5' + '\n' + '%s' % spy_ema5 ) record(ema5 = spy_ema5[-1], ema10 = spy_ema10[-1], ema20 = spy_ema20[-1] ) #capital = capital_invested(context, data) #record(capital_invested = capital) #cash = context.portfolio.cash #record(cash = cash) #plot positions value #positions_value = context.portfolio.positions_value #record(positions_value = positions_value) #log.info("ALL EMAS, EMA5= {0}, EMA10= {1}, EMA20= {2} EMA5= {3}, EMA10= {4}, EMA20= {5}, Invested = {6}".format( spy_ema5[-1], spy_ema10[-1], spy_ema20[-1], spy_ema5[-2], spy_ema10[-2], spy_ema20[-2],context.invested )) if spy_ema5[-1] > spy_ema20[-1] and spy_ema10[-1] > spy_ema20[-1] and (spy_ema10[-2]<=spy_ema20[-1] or spy_ema5[-2]<=spy_ema20[-1] ) : if context.longshort == 'short' and context.shortenabled: order_target_percent(context.sh,0) context.longshort = 'none' return if context.longshort == 'none': order_target_percent(context.sds,-0.99) log.info("Bot SPY EMA5 = {0}, EMA10 = {1}, EMA20 = {2}".format( spy_ema5[-1], spy_ema10[-1], spy_ema20[-1])) context.longshort = 'long' updatelastbought(context) elif spy_ema5[-1]<spy_ema20[-1] and spy_ema10[-1]<spy_ema20[-1] and (spy_ema10[-2]>=spy_ema20[-1] or spy_ema5[-2]>=spy_ema20[-1] ) : if context.longshort == 'long': order_target_percent(context.sds,0) context.longshort = 'none' return if context.longshort == 'none' and context.shortenabled: order_target_percent(context.sh,0.99) log.info("Bot SPY EMA5 = {0}, EMA10 = {1}, EMA20 = {2}".format( spy_ema5[-1], spy_ema10[-1], spy_ema20[-1])) context.longshort = 'short' updatelastbought(context) else: return def capital_invested(context, data): # get a sum total of capital spent or borrowed for all current positions capital = 0.0 # initialize to zero # check every stock in current positions (also works with set_universe) for stock in context.portfolio.positions: # get amount of shares in current position for this stock amount = context.portfolio.positions[stock].amount # get the cost basis of the shares (how much we spent on average per share) cost_basis = context.portfolio.positions[stock].cost_basis # check if position is a short trade (negative amount) amount = max(amount, -amount) # change amount to a positive number # add dollar amount to the 'spent' total capital += amount * cost_basis # return amount of capital tied up in positions return capital #################################################################################################################################### def has_orders(context): #Return true if there are pending orders. #has_orders = False #orders = get_open_orders() #if orders: # has_orders = True has_orders = False for sec in context.secs: orders = get_open_orders(sec) if orders: log.info("has order") for oo in orders: message = 'Open order for {amount} shares in {stock}' message = message.format(amount=oo.amount, stock=sec) log.info(message) has_orders = True return has_orders

This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.