Back to Community
What am I doing wrong?

As you can see from the partial backtest I included with this post, my algorithm does horribly. But, the strange thing (to me at least) is that when I invert the algorithm and go short the stocks with the highest overnight gap, the algorithm does horribly again.

This confuses me. Can anyone explain:

1) Why are the results bad for both the long and short version of the algorithm (note there are no transaction costs or slippage)?
2) Is there something wrong with the code itself?
3) Is there a good way for me to debug this kind of thing?

Any help would be appreciated.

Rudy

Clone Algorithm
19
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
# Backtest ID: 54c886737222da0a1851b0a9
There was a runtime error.
5 responses

Hi Rudiger,

I don't think there's anything wrong with your code. In fact, your code is structured very well and I love your use of schedule_function. At this point, I think you'll need to spend some time tinkering with your parameters (e.g. not overnight return, or biggest losers from yesterday's close to today's open).

As for your debugging question, one way that I would go about it is to log the returns from when you first bought the stock to when you sold it so you can get an indepth look at how your picks are doing. Here's your code with the debugging method added:

def initialize(context):  
    set_universe(universe.DollarVolumeUniverse(floor_percentile=98.0,              ceiling_percentile=100.0))  
    context.position_count = 5  
    schedule_function(  
      func=check_gap,  
      date_rule=date_rules.every_day(),  
      time_rule=time_rules.market_open(minutes=1),  
      half_days=True  
    )  
    schedule_function(  
      func=close_positions,  
      date_rule=date_rules.every_day(),  
      time_rule=time_rules.market_close(minutes=2),  
      half_days=True  
    )  
    context.stocks = {}

def handle_data(context, data):  
    pass

def check_gap(context, data):  
    close_prices = history(2, '1d', 'close_price')  
    open_prices = history(1, '1m', 'open_price')  
    pairs = []  
    for stock in data:  
        last_day_close = close_prices[stock][-2]  
        this_minute_open = open_prices[stock][-1]  
        overnight_return = this_minute_open / last_day_close  
        pair = (stock, overnight_return)  
        pairs.append(pair)  
    sorted_pairs = sorted(pairs, key=lambda (stock, ret): ret)  
    for pair in sorted_pairs[context.position_count:]:  
        stock = pair[0]  
        fraction = 1.0 / context.position_count  
        order_target_percent(stock, fraction)  
        context.stocks[stock] = data[stock].price  
def close_positions(context, data):  
    for stock in context.portfolio.positions:  
        order_target_percent(stock, 0.0)  
        buy_price = context.stocks[stock]  
        end_price = data[stock].price  
        print "Returns for %s are %s with buy price: %s and ending price: %s" % (stock.symbol, (end_price - buy_price)/buy_price, buy_price, end_price)  
    context.stocks = {}  

Seong

Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

Thanks Seong! I'll try that out

I believe that the issue is if you don't specify a slippage or commission module, the default ones are used.

@Rudiger, thanks for sharing your code and idea. Here's a variation.
Uses the intraday move as an indicator to hold overnight.

Also, per Mr Aken's comment, if you plot cost basis for a security, you may see the number of trades that have to be made to fully enter any on position. Lowering your capital, or increasing your number of positions, may allow you to enter exactly at the time of your submission signal. Volume slippage can be a shock when you watch how many actual fills you get on a big order.

minRankToQualify = 1.05

def initialize(context):  
    set_universe(universe.DollarVolumeUniverse(floor_percentile=98.0, ceiling_percentile=99.0))  
    context.maxPositionCount = 5  
    schedule_function(func = ExecuteEODEntry,  
      date_rule = date_rules.every_day(),  
      time_rule = time_rules.market_close(minutes=1),  
      half_days = True  
    )  
    schedule_function(func = ExitPositions,  
      date_rule = date_rules.every_day(),  
      time_rule = time_rules.market_open(minutes=1),  
      half_days = True  
    )

def handle_data(context, data):  
    pass

def ExecuteEODEntry(context, data):  
    dailyOpens     = history(1, '1d', 'open_price')  
    minutelyCloses = history(1, '1m', 'close_price')

    pairs = []  
    for stock in data:  
        todaysOpen  = dailyOpens[stock][-1]  
        todaysClose = minutelyCloses[stock][-1]  
        intradayReturn = todaysOpen / todaysClose  
        pair = (stock, intradayReturn)  
        pairs.append(pair)

    rankedByIntradayReturn = sorted(pairs, key=lambda (stock, ret): ret, reverse=True)  
    rankedByIntradayReturn = filter(lambda (stock, ret): ret >= minRankToQualify , rankedByIntradayReturn)

    eligibleCount = float(len(rankedByIntradayReturn))  
    for stock, rank in rankedByIntradayReturn:  
        order_target_percent(stock, 1.0 / eligibleCount)  
        print("   Entry {0:<5} @ {1:>7.2f} rank: {2:>7.2f} >>".format(stock.symbol, data[stock].close_price, rank))

def ExitPositions(context, data):  
    for stock in context.portfolio.positions:  
        order_target_percent(stock, 0.0)  
        print("<< Exit  {0:<5} @ {1:>7.2f}".format(stock.symbol, data[stock].close_price))  

David, you're totally right! My algorithm does incredibly well when I set the trading costs to 0 explicitly (see attached backtest).

Market Tech, thanks for the idea! I'll give that a try :)

Clone Algorithm
19
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
# Backtest ID: 54ca954bfa5b324652020c35
There was a runtime error.