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))