Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
zipline: order_target not selling assets

I'm trying to develop a monthly rotational trading strategy with Zipline (leaning on some ideas from Andreas Clenow's great new book) and data from the Quandl bundle. The strategy is supposed to hold a number ("topn") of assets with the highest momentum score and hold them until they dropped below a certain momentum rank ("keepn").

When I run the following code through zipline, it works for a couple of months, then suddenly starts holding more and more positions, selling the same positions repeatedly without actually removing the positions from the portfolio. This happens with Quandl data as well as with a custom bundle.

I'm guessing, there's a fundamental flaw in my strategy, but going through debugging, I really can't find it.

Any help is appreciated! Thank you.

Dirk

def initialize(context):  
    # List of all assets to chose from  
    context.tickers = ["AAPL", "YELP", "YHOO", "MMM",  
                       "ABT", "AMD", "AMZN", "GOOG",  
                       "AXP", "AMGN", "BBY", "BLK",  
                       "CAT"]

    context.universe = [symbol(ticker) for ticker in context.tickers]  
    context.momentum_lookback = 256

    # Hold (topn) 3 assets, as long as they are in the (keepn) top 5 momentum_rank  
    context.topn = 3  
    context.keepn = 5

    # Schedule the trading routine for once per month  
    schedule_function(handle_data, date_rules.month_start(), time_rules.market_close())

    # Allow no leverage  
   set_max_leverage = 1.0


def momentum_score(ts):  
    # Simplified momentum score: Last price / price 256 days ago  
    return ts[-1] / ts[0]


def handle_data(context, data):  
    # String with today's date for logging purposes  
    today = get_datetime().date().strftime('%d/%m/%Y')

    # Create 256 days (context.momentum_lookup) history for all equities  
    hist = data.history(context.universe,  
                              "close",  
                              context.momentum_lookback,  
                              "1d")

    # How much to hold of each equity  
    target_percent = 100 / context.topn

    # Rank ETFs by momentum score  
    ranking_table = hist.apply(momentum_score).sort_values(ascending=False)  
    top_assets = ranking_table[:context.topn]  
    grace_assets = ranking_table[:context.keepn]

    # List of equities being held in current portfolio  
    kept_positions = list(context.portfolio.positions.keys())

    # Sell logic  
    # ==========  
    # Sell current holdings no longer in top N  
    for holding in context.portfolio.positions:  
        if holding not in grace_assets:  
            if data.can_trade(holding):  
                print(today + " [Sell] "+holding.symbol)  
                order_target_percent(holding, 0.0)  
                kept_positions.remove(holding)

    # Buy Logic  
    # =========  
    # Determine how many new assets to buy  
    replacements = context.topn - len(kept_positions) 

    # Remove currently held positions from the top list  
    buy_list = ranking_table.loc[~ranking_table.index.isin(kept_positions)][:replacements]

    # Buy new entities and rebalance "kept_positions" to the desired weights  
    new_portfolio = list(buy_list.index) + kept_positions

    # Buy/rebalance assets  
    for asset in new_portfolio:  
        if data.can_trade(asset):  
            print(today+"[BUY] "+asset.symbol)  
            order_target_percent(asset, target_percent)

# Set start- and end-date of the backtest  
start_date = datetime(2008, 1, 1, tzinfo=pytz.UTC)  
end_date = datetime(2017, 12, 31, tzinfo=pytz.UTC)

# Initial portfolio value  
capital_base = 10000

# Run the backtest  
results = run_algorithm(  
    start = start_date,  
    end = end_date,  
    initialize = initialize,  
    analyze = pyfolio_analyze,  
    capital_base = capital_base,  
    data_frequency = "daily",  
    bundle = "Quandl",  
)
1 response

Ok, so I figured out what the problem is. Basic math failure on my end.
This is the troublesome code:

# How much to hold of each equity  
    target_percent = 100 / context.topn  

It should have been target_percent context.topn / 100 instead. facepalm
I'm assuming this leads to situations in which orders aren't filled properly, leading to the described behavior.

Lesson learned:
- Check for open orders and cancel them, if needed
- Keep an eye on leverage and position sizes and check against restrictions during the algo run