Generating the Bayesian Forecast Cone Graph in Research (plus a bonus!)

If you've used pyfolio tear sheets then you've already seen the cumulative return graph with a forecast cone. Unfortunately, the forecast cone generated is not very good. That's the whole reason behind the Bayesian tear sheet, after all! But, the Bayesian tear sheet's forecast cone is, well... terrible. You can't see anything!

I searched for a solution but couldn't find one, so I looked through the source on github and made it work in research. I also found a bootstrap multistrike cone that isn't used in the tear sheets as a bonus.

Hopefully someone will pick up what I have revealed here and show how to customize the charts. There are additional charts and options as well.

12
1 response

backtest used to generate graph above

3
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
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline import CustomFactor
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data import morningstar
import numpy as np
from collections import defaultdict

class momentum_factor_1(CustomFactor):
inputs = [USEquityPricing.close]
window_length = 20

def compute(self, today, assets, out, close):
out[:] = close[-1]/close[0]

class momentum_factor_2(CustomFactor):
inputs = [USEquityPricing.close]
window_length = 60

def compute(self, today, assets, out, close):
out[:] = close[-1]/close[0]

class momentum_factor_3(CustomFactor):
inputs = [USEquityPricing.close]
window_length = 125

def compute(self, today, assets, out, close):
out[:] = close[-1]/close[0]

class momentum_factor_4(CustomFactor):
inputs = [USEquityPricing.close]
window_length = 252

def compute(self, today, assets, out, close):
out[:] = close[-1]/close[0]

class market_cap(CustomFactor):
inputs = [USEquityPricing.close, morningstar.valuation.shares_outstanding]
window_length = 1

def compute(self, today, assets, out, close, shares):
out[:] = close[-1] * shares[-1]

class efficiency_ratio(CustomFactor):
inputs = [USEquityPricing.close, USEquityPricing.high, USEquityPricing.low]
window_length = 252

def compute(self, today, assets, out, close, high, low):
lb = self.window_length
e_r = np.zeros(len(assets), dtype=np.float64)
a=np.array(([high[1:(lb):1]-low[1:(lb):1],abs(high[1:(lb):1]-close[0:(lb-1):1]),abs(low[1:(lb):1]-close[0:(lb-1):1])]))
b=a.T.max(axis=1)
c=b.sum(axis=1)
e_r=abs(close[-1]-close[0]) /c
out[:] = e_r

def initialize(context):
schedule_function(func=monthly_rebalance, date_rule=date_rules.month_start(days_offset=5), time_rule=time_rules.market_open(), half_days=True)
schedule_function(func=daily_rebalance, date_rule=date_rules.every_day(), time_rule=time_rules.market_close(hours=1))

set_asset_restrictions(security_lists.restrict_leveraged_etfs)
context.acc_leverage = 1.00
context.holdings =10
context.profit_taking_factor = 0.01
context.profit_target={}
context.profit_taken={}
context.entry_date={}
context.stop_pct = 0.75
context.stop_price = defaultdict(lambda:0)

pipe = Pipeline()
attach_pipeline(pipe, 'ranked_stocks')

factor1 = momentum_factor_1()
factor2 = momentum_factor_2()
factor3 = momentum_factor_3()
factor4 = momentum_factor_4()
factor5=efficiency_ratio()

mkt_screen = market_cap()
stocks = mkt_screen.top(3000)
factor_5_filter = factor5 > 0.031
total_filter = (stocks& factor_5_filter)
pipe.set_screen(total_filter)

combo_raw = (factor1_rank+factor2_rank+factor3_rank+factor4_rank)/4

context.output = pipeline_output('ranked_stocks')

# Only consider stocks with a positive efficiency rating
ranked_stocks = context.output[context.output.factor_5 > 0]

# We are interested in the top 10 stocks ranked by combo_rank
context.stock_factors = ranked_stocks.sort(['combo_rank'], ascending=True).iloc[:context.holdings]

context.stock_list = context.stock_factors.index

def daily_rebalance(context, data):

for stock in context.portfolio.positions:
# Set/update stop price
price = data.current(stock, 'price')
context.stop_price[stock] = max(context.stop_price[stock], context.stop_pct * price)

# Check stop price, sell if price is below it
if price < context.stop_price[stock]:
order_target(stock, 0)
context.stop_price[stock] = 0

# Increase our position in stocks that are performing better than their target and reset the target
takes = 0
for stock in context.portfolio.positions:
if data.can_trade(stock) and data.current(stock, 'close') > context.profit_target[stock]:
context.profit_target[stock] = data.current(stock, 'close')*1.25
profit_taking_amount = context.portfolio.positions[stock].amount * context.profit_taking_factor
takes += 1
order_target(stock, profit_taking_amount)

# Log the 10 stocks we are interested in
print "Long List"

# Record leverage and number of positions held
record(leverage=context.account.leverage, positions=len(context.portfolio.positions), t=takes)

def monthly_rebalance(context,data):

# used to calculate order weights
positions = set()

for stock in context.stock_list:
for stock in context.portfolio.positions:

weight = context.acc_leverage / len(context.stock_list)

for stock in context.stock_list:
if stock in security_lists.leveraged_etf_list.current_securities(get_datetime()):
continue
if context.stock_factors.factor_1[stock] > 1:
order_target_percent(stock, weight)
context.profit_target[stock] = data.current(stock, 'close')*1.25

for stock in context.portfolio.positions:
if data.can_trade(stock) not in context.stock_list or context.stock_factors.factor_1[stock]<=1:
order_target(stock, 0)

There was a runtime error.