Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Attempt at making modular algorithm gone awry

Hey there everyone! Pretty much total Quantopian noob here. I've been trying to make a modular algorithm that can take multiple strategies/alpha generators and implement them into a single portfolio. After much bug fixing and frankencode, I've hit a roadblock.

The backtest, when run, takes an INSANELY long time to do anything, and at first I thought it was due to a problem with websockets, but checking the google chrome console, it showed the backtest as initialized. So after leaving it to run for about 30 minutes, I came back and it had gotten through 0.2% of the backtest. I looked at the logs, and it was attempting to sell an insane amount of stocks per day, which leads me to assume it's a problem with the pipeline implementation. At this point, I'm thinking about giving up on the modular approach in favor of simpler tactics. Can anyone help me figure out where the bug is? Thanks a ton!

Code below (Sorry it's so long, but I don't know where the error is)


from quantopian.algorithm import attach_pipeline, pipeline_output  
from quantopian.pipeline import Pipeline  
from quantopian.pipeline import CustomFactor  
from quantopian.pipeline.factors.morningstar import MarketCap  
from quantopian.pipeline.data.builtin import USEquityPricing  
from quantopian.pipeline.factors import AverageDollarVolume, SimpleMovingAverage  
from quantopian.pipeline.filters.morningstar import Q1500US  
from quantopian.pipeline.classifiers.morningstar import Sector  
from quantopian.pipeline.data import morningstar  
from quantopian.pipeline.filters.morningstar import IsPrimaryShare  
import math  
import pandas as pd  
import numpy as np

##############################################################  
#Abstract Classes  
##############################################################

"""
Portfolio manager manages the portfolio on a frequency assigned by the owner. Controls alpha generators and allocates a  
portion of the assets in the portfolio to each. Assisted by a risk manager and a transaction cost model.  
"""
class portfolioManager:  
    def __init__(self):  
        #target portfolio and assigns to an empty dictionary  
        self.target_portfolio = dict()  
        #list of strategies  
        self.list_alpha = []  
    #computes target portfolio  
    def compute_target(self, context, data):  
        raise NotImplementedError()  

"""
Execution handler executes allocation to different strategies once an allocation is decided upon by  
the portfolio manager.  
"""
class executionHandler:  
    #decides which orders to make  
    def compute_orders(self, context, data, target_portfolio):  
        raise NotImplementedError()  

"""
Alpha generator finds edge on market, asks portfolio manager for allocation, and keeps track of  
positions held by it.  
"""
class alphaGenerator:  
    def __init__(self):  
        #requests allocation and assigns to an empty dictionary  
        self.alloc = dict()  
        #records positions associated with strategy and assigns to an empty dictionary  
        self.pos = dict()  
    def compute_allocation(self, context, data):  
        pass  
        #raise NotImplementedError()  

"""Risk manager helps portfolio manager measure risks held by the portfolio. It can hold models of volatility, correlation, or market models, for example.
"""
class riskManager:  
    pass

###########################################################  
#Definite Classes  
###########################################################

class equiWeightPortfolioManager(portfolioManager):  
    def __init__(self):  
        portfolioManager.__init__(self)  
    def compute_target(self, context, data):  
        #get number of alpha generators  
        num_alpha = len(self.list_alpha)  
        #resets target allocation to 0  
        for stock in context.output.index:  
            self.target_portfolio[stock] = 0  
        #for each alpha, assign target allocation - in this case, portfolio allocation is even across  
        #strategies  
        for alpha in self.list_alpha:  
            for stock in alpha.alloc:  
                alloc_alpha = alpha.alloc[stock] / num_alpha  
                self.target_portfolio[stock] = self.target_portfolio[stock] + alloc_alpha  
                #update number of shares strategy is responsible for  
                portfolio_value = context.portfolio.portfolio_value  
                price = data.current(stock, 'price')  
                alpha.pos[stock] = math.floor(alloc_alpha * portfolio_value / price)  


class marketExecutionHandler(executionHandler):  
    def compute_orders(self, context, data, target_portfolio):  
        for stock in context.securities:  
            order_target_percent(stock, target_portfolio[stock])  

######################################################################################  
class psebitdaLongAlphaGenerator(alphaGenerator):  
    def compute_allocation(self, context, data):  
        for stock in context.long_list_psebitda.index:  
            self.alloc[stock] = 1  
        self.long_weight = context.long_leverage / float(len(context.long_list_psebitda))  
        for long_stock in context.long_list_psebitda.index:  
            if data.can_trade(long_stock) == True:  
                context.securities.append(long_stock)  


class psebitdaShortAlphaGenerator(alphaGenerator):  
    def compute_allocation(self, context, data):  
        for stock in context.short_list_psebitda.index:  
            self.alloc[stock] = -1  
        self.short_weight = context.short_leverage / float(len(context.short_list_psebitda))  
        for short_stock in context.short_list_psebitda.index:  
            if data.can_trade(short_stock) == True:  
                context.securities.append(short_stock)

# Classes used in psebitdaAlphaGenerator

class priceToSales(CustomFactor):  
    # Pre-declare inputs and window_length  
    inputs = [morningstar.valuation_ratios.ps_ratio]  
    window_length = 1  
    # Compute factor1 value  
    def compute(self, today, assets, out, ps_ratio):  
        out[:] = ps_ratio[-1]  
class ebitda(CustomFactor):  
    inputs = [morningstar.income_statement.ebitda]  
    window_length = 1  
    def compute(self, today, assets, out, ebitda):  
        out[:] = ebitda[-1]  
######################################################################################  


def initialize(context):  
    """  
    Called once at the start of the algorithm.  
    """  
    #Transaction cost model  
    set_commission(commission.PerShare(cost=0.0075, min_trade_cost=1.00))  
    set_slippage(slippage.VolumeShareSlippage(volume_limit=0.025, price_impact=0.1))  
    # Rebalance every day, 1 hour after market open.  
    schedule_function(my_rebalance, date_rules.every_day(), time_rules.market_open(hours=1))  
    # Record tracking variables at the end of each day.  
    schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close())  
    # Create our dynamic stock selector.  
    pipe = make_pipeline()  
    attach_pipeline(pipe, 'my_pipeline')  
    #leverage assignment  
    context.long_leverage = 1.00  
    context.short_leverage = -1.00  
    context.securities = []  
    try:  
        context.first_time  
    except:  
        context.first_time = False  
        #creates portfolio manager  
        context.p_manager = equiWeightPortfolioManager()  
        #creates execution handler  
        context.exec_handler = marketExecutionHandler()  
        #creates alpha generator  
        psebitda_long_alpha_gen = psebitdaLongAlphaGenerator()  
        psebitda_short_alpha_gen = psebitdaShortAlphaGenerator()  
        #alpha generator added to portfolio manager  
        context.p_manager.list_alpha.append(psebitda_long_alpha_gen)  
        context.p_manager.list_alpha.append(psebitda_short_alpha_gen)  


def make_pipeline():  
    """  
    A function to create our dynamic stock selector (pipeline). Documentation on  
    pipeline can be found here: https://www.quantopian.com/help#pipeline-title  
    """  
    # Base universe set to the Q1500US  
    base_universe = Q1500US()

    # Factor of yesterday's close price.  
    yesterday_close = USEquityPricing.close.latest  
    # Setting up general trading screens  
    ###################################################################################  
    # All the below commented out is covered by Q1500US Universe pre-screening  
    ###################################################################################  
    """  
    9 filters  
    1: Common stock  
    2: Not limited partnership, checks name  
    3: Database has fundamental data  
    4: Not over-the-counter  
    5: Not when issued  
    6: Not depository receipts  
    7: Primary share  
    8: Not a penny stock (price > 1)  
    9: High dollar volume  
    common_stock = morningstar.share_class_reference.security_type.latest.eq("ST00000001")  
    not_lp_name = ~morningstar.company_reference.standard_name.latest.matches('.* L[\\. ]?P\.?$')  
    have_data = morningstar.valuation.market_cap.latest.notnull()  
    not_otc = ~morningstar.share_class_reference.exchange_id.latest.startswith('OTC')  
    not_wi = ~morningstar.share_class_reference.symbol.latest.endswith('.WI')  
    not_depository = ~morningstar.share_class_reference.is_depositary_receipt.latest  
    primary_share = IsPrimaryShare()  
    not_penny = ~morningstar.share  
    tradable_filter = (common_stock & not_lp_name & not_lp_balance_sheet & have_data & not_otc & not_wi & not_depository & primary_share)  
    """  
    # Vars for psebitdaAlphaGenerator  
    price_to_sales = priceToSales()  
    EBITDA = ebitda()  
    ps_ebitda_quotient = (price_to_sales/EBITDA)  
    ps_ebitda_quotient_rank = ps_ebitda_quotient.rank()  

    pipe = Pipeline(  
        screen = base_universe,  
        columns = {  
            'close': yesterday_close,  
        }  
    )  
    pipe.add(ps_ebitda_quotient_rank, "ps_ebitda_q_rank")  
    return pipe  


def before_trading_start(context, data):  
    """  
    Called every day before market open.  
    """  
    context.output = pipeline_output('my_pipeline')  
    # These are the securities that we are interested in trading each day, organized by alpha generator  
    context.long_list_psebitda = context.output.sort_values(by='ps_ebitda_q_rank', ascending=False).iloc[:-20]  
    context.short_list_psebitda = context.output.sort_values(by='ps_ebitda_q_rank', ascending=False).iloc[20:]  


def my_rebalance(context,data):  
    """  
    Execute orders according to our schedule_function() timing.  
    """  
    #computes new allocations for each strategy  
    for alpha in context.p_manager.list_alpha:  
        alpha.compute_allocation(context, data)  
    #computes new target portfolio  
    context.p_manager.compute_target(context, data)  
    #computes order strategy  
    target = context.p_manager.target_portfolio  
    context.exec_handler.compute_orders(context, data, target)  
    context.securities = []  

def my_record_vars(context, data):  
    """  
    Plot variables at the end of each day.  
    """  
    longs = shorts = 0  
    for position in context.portfolio.positions.itervalues():  
        if position.amount > 0:  
            longs += 1  
        elif position.amount < 0:  
            shorts += 1  
    record(leverage=context.account.leverage, long_count=longs, short_count=shorts)  
    log.info( 'Long List')  
    log.info("\n" + str(context.long_list_psebitda))  
    log.info( 'Short List')  
    log.info("\n" + str(context.short_list_psebitda))  
3 responses

Here's your problem:

class psebitdaLongAlphaGenerator(alphaGenerator):  
    def compute_allocation(self, context, data):  
        for stock in context.long_list_psebitda.index:  
            self.alloc[stock] = 1  
        self.long_weight = context.long_leverage / float(len(context.long_list_psebitda))  
        for long_stock in context.long_list_psebitda.index:  
            if data.can_trade(long_stock) == True:  
                context.securities.append(long_stock)  

Which basically tells the algorithm to order 100% of every stock in your long list, then does some other stuff that never gets used. You also have an issue where you add new securities to your list and you only order those stocks. That means you will never sell stocks because as soon as it comes off your long list it is removed from your algorithms ordering.

You need to do something like this:

class psebitdaLongAlphaGenerator(alphaGenerator):  
    def compute_allocation(self, context, data):  
        self.alloc = dict()  
        for stock in context.long_list_psebitda.index:  
            if  data.can_trade(long_stock):  
                 long_weight = context.long_leverage / float(len(context.long_list_psebitda))  
                 self.alloc[stock] = 1.0/long_weight  
                 context.securities.append(long_stock)              

class marketExecutionHandler(executionHandler):  
    def compute_orders(self, context, data, target_portfolio):  
        for stock in context.portfolio.positions:  
            if not stock in context.securities:  
              order_target_percent(stock, 0.0)  
        for stock in context.securities:  
            order_target_percent(stock, target_portfolio[stock])  

@Luke
Thanks a ton for your response! Your changes sped up the algorithm a decent amount, and fixed my stock overload problem. I do still have a bit of a leverage problem, though. At one point, my leverage spiked up to ~340K, which seems large enough that it may be a bug, but in general it sits above 20 and fluctuates quite a bit. How can I limit my leverage to 1 or so for the time being? Thanks again!

That is a complex problem to fix, I suggest using the backtest debugger and step through your algorithm one line at a time to figure out how exactly it is calculating the number of shares to buy. Remeber that order_target_percent() should be a decimal value and that context.securities should be a "unique" list aka no duplicates. Another thing you might want to look at is what is the function of lines 169-171?

Edit: Also, you will always have some leverage because order_target_percent() does not take into account other orders that aren't filled yet when placing another order. So if you sell $100 of stocks and buy $100 of stocks in the same minute of execution you will have $200 worth of leverage until the sell orders get filled.