Back to Community
Holding Spy Overnight

I read this: https://www.quantopian.com/posts/holding-spy-overnight-equals-most-returns-with-half-the-drawdown
these days in which apparently the overnight returns on the spy are better than intraday returns, therefore I tried it out myself with this simple backtest. However, for me, the overnight SPY didn't even get close to the regular SPY. Any idea on what I might be doing wrong?
Thanks,
Mattia

Clone Algorithm
11
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
# ---------------------------------------------------
index = sid(8554)
# ---------------------------------------------------
 
def initialize(context):
        schedule_function(close_position, date_rules.every_day(), time_rules.market_open(minutes=1))
        schedule_function(open_position, date_rules.every_day(), time_rules.market_close(minutes=1))

 
def open_position(context, data):
    order_target_percent(index, 1.0)
    log.info('opening position')
    
def close_position(context, data):
    order_target(index, 0.0)
    log.info('closing position')
There was a runtime error.
3 responses

In addition, from this paper: http://www.sciencedirect.com/science/article/pii/S1059056016301563
It is shown evidence of higher overnight returns int ETFs, but with lower volatility. Moreover, it appears to be possible to forecast the direction of the first 30 minutes of trading (with a negative relation) and the last 30 minutes of trading (with a positive correlation). However when I modify the previous algorithm (to add the last 30 minutes of trading to the overnight holding for example), the result is even worse. Any suggestion on how to improve my strategy?
Thanks

Clone Algorithm
6
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
# ---------------------------------------------------
index = sid(8554)
# ---------------------------------------------------
 
def initialize(context):
        schedule_function(close_position, date_rules.every_day(), time_rules.market_open(minutes=1))
        schedule_function(open_position, date_rules.every_day(), time_rules.market_close(minutes=30))
 
def open_position(context, data):
    order_target_percent(index, 1.0)
    log.info('opening position')
    
def close_position(context, data):
    order_target(index, 0.0)
    log.info('closing position')
There was a runtime error.

Few changes

Clone Algorithm
14
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
# ---------------------------------------------------
index = sid(8554)
# ---------------------------------------------------
 
def initialize(context):
    #
    # This allows to fill orders immediatly, no check on volume
    #
    set_slippage(slippage.FixedSlippage(spread=0.00))
    
    schedule_function(close_position, date_rules.every_day(), time_rules.market_open(minutes=60))
    schedule_function(open_position, date_rules.every_day(), time_rules.market_close(minutes=5))
 
def handle_data(context, data):
    
    for security in context.portfolio.positions:
        if data.can_trade(security) and not get_open_orders(security):
            cost_basis = context.portfolio.positions[security].cost_basis  
            price = data.current(security, 'price')
            if price >= cost_basis * 1.001:
                order_target(security, 0.0)
                log.info('closing position')

    
def open_position(context, data):
    order_target_percent(index, 1.0)
    log.info('opening position')
    
def close_position(context, data):
    order_target(index, 0.0)
    log.info('closing position')
There was a runtime error.

If you know of an OCD competition, here's my application obsessing over orders, but for those ready, kidding aside, there's some actual useful stuff.
I would wager that everyone can benefit from trying cls_opn_crs() in their own algo to do one thing when an order is opening a position (partially filled) and something different if it is a closing order. Handles both long and short. Took a lot of effort to figure out that complex puzzle. In the bigger picture, probably everyone already knows this is classic overfitting. Incidentally, most of my code is off here using the break, I wanted to beat Luca's result heh.

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
# https://www.quantopian.com/posts/holding-spy-overnight
spy = sid(8554)
 
def initialize(context):
    # Fill orders with no volume check? Are we 100% sure?
    #set_slippage(slippage.FixedSlippage(spread=0.0))
    # Fill orders with no volume limit, set outrageously high for testing only. 
    # With starting capital turned down, nothing for this to chew on
    #set_slippage(slippage.VolumeShareSlippage(volume_limit=   1e6   ))
    
    minut = 120
    schedule_function(close_pos, date_rules.every_day(), time_rules.market_open (minutes=minut))
    schedule_function(open_pos,  date_rules.every_day(), time_rules.market_close(minutes=minut))
    
    # end is non-inclusive, at 390 will not run on 390 the last minute of the day.
    #   Set to 391 to run 390 and see incomplete orders. How is that possible?
    for i in range(1, 390, 1):  # start, end, every i
        
      break    # comment out or remove to activate
    
      schedule_function(profit_or_stop, date_rules.every_day(), time_rules.market_open(minutes=i))

def profit_or_stop(context, data):
    for s in context.portfolio.positions:
        cb  = context.portfolio.positions[s].cost_basis  
        prc = data.current(s, 'price')
        if   prc > cb * 1.002: 
            if orders_just_so(context, s):
                continue
            order_target(s, 0)
            #log.info('  profit prc {}  cb {}  {}'.format(prc, cb, '%.3f' % (prc / cb)))
        elif prc < cb *  .95: 
            if orders_just_so(context, s):
                continue
            order_target(s, 0)
            log.info('stoploss prc {}  cb {}  {}'.format(prc, cb, '%.3f' % (prc / cb)))
    
def open_pos(context, data):    # pos stands for position
    oo  = get_open_orders(spy)
    for o in oo:
        cancel_order(o.id)
    order_target_percent(spy, 1.0)
    #log.info('open')
    
def close_pos(context, data):
    oo  = get_open_orders(spy)
    for o in oo:
        cancel_order(o.id)
    order_target(spy, 0)
    #log.info('close')
    
def orders_just_so(context, s):
    oo  = get_open_orders(s)
    for o in oo:                # returns are assuming only one order
        if cls_opn_crs(context, s, o) in [1, 3]:    # Opening
            cancel_order(o.id)          # Cancel opening order
            return 0                    # To allow the close
        else:
            return 1    # Already closing, signal to skip adding another close order
    
def cls_opn_crs(c, s, o):
    # https://www.quantopian.com/posts/order-state-on-partial-fills-close-open-or-crossover
    if o.stop or o.limit: return 0    # ... assuming you're using stop, limit only to close
    if c.portfolio.positions[s].amount * o.amount < 0:   # close or x
        if abs(c.portfolio.positions[s].amount) < abs(o.amount - o.filled):
            if abs(c.portfolio.positions[s].amount) - abs(o.filled) < 0:
                  return 3  # crossed now opening
            else: return 2  # cross closing
        else:     return 0  # close
    else:         return 1  # open
    
There was a runtime error.