Optimal Portfolio as outlined by Merton. Uses the tools of Malliavin Calculus to in an economy with the short rate following a Vasicek Model. The Optimal Portfolio is split into a mean variance component and an inter-temporal hedging demand.

Clone Algorithm

95

Loading...

There was an error loading this backtest.

Backtest from
to
with
initial capital

Cumulative performance:

Algorithm
Benchmark

Custom data:

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 |

import pandas as pd import numpy as np import numpy.linalg as la import math import scipy.stats as sp def rename_col(df): df = df.rename(columns={'Value': 'price'}) df = df.fillna(method='ffill') df = df[['price', 'sid']] return df def initialize(context): #parameters context.riskAversion = 10 context.nobs = 252 context.recalibrate = 126 #re-estimate every so often (in days) context.leverage= 1 context.horizon = 100000 context.bondProxy = sid(23870) #setup the identifiers and data storage #context.tickers = ['spy', 'ief'] #context.sids = [ sid(8554), sid(23870) ] context.tickers = ['spy'] context.sids = [ sid(8554)] context.data = pd.DataFrame({ k : pd.Series() for k in context.tickers } ) context.daysToRecalibration = 0 context.onevec = np.asmatrix(np.ones((len(context.tickers), 1))) fetch_csv('http://www.quandl.com/api/v1/datasets/FRED/DTB6.csv?&trim_start=1958-12-09&trim_end=2013-12-05&sort_order=desc', date_column='Date', symbol='treasury', post_func=rename_col, usecols=['Value'], date_format='%Y-%m-%d') context.treas = [] context.t = 0 def handle_data(context, data): if context.portfolio.starting_cash == context.portfolio.cash: #buy into the benchmark while we build the starting data set order(sid(8554), math.floor(context.portfolio.starting_cash/data[sid(8554)].close_price) ) if len(context.data.index) < context.nobs: #still recording data newRow = pd.DataFrame({k:float(data[e].returns()) for k,e in zip(context.tickers, context.sids) },index=[0]) context.data = context.data.append(newRow, ignore_index = True) if data['treasury'].price <= 0: context.treas.append(0.00001) else: context.treas.append(data['treasury'].price/100.0) else: context.t = context.t+(1.0/252.0) newRow = pd.DataFrame({k:float(data[e].returns()) for k,e in zip(context.tickers, context.sids) },index=[0]) context.data = context.data.append(newRow, ignore_index = True) if data['treasury'].price <= 0: data['treasury'].price = 0.0001 context.treas.append(0.00001) #log.info(context.treas[-1]) else: context.treas.append(data['treasury'].price/100.0) #context.data = context.data[1:len(context.data.index)] context.data = context.data[1:] context.treas = context.treas[1:] #log.info(context.daysToRecalibration) if context.portfolio.positions[sid(8554)].amount != 0: #data gathering time is done, get out of the benchmark #order(sid(8554), -1.0*context.portfolio.positions[sid(8554)].amount) #wait a day for the trades to clear before placing the new trades. #return pass if context.daysToRecalibration == 0: context.daysToRecalibration = context.recalibrate #recalibrate #log.info('recalibrating...') #calculate the mean variance portfolio; precision = np.asmatrix(la.inv(context.data.cov())) pimv = precision*context.onevec / (context.onevec.T*precision*context.onevec) mu = np.asmatrix(context.data.mean()).T mumv = mu.T*pimv #log.info({'precision': precision, 'mumv':mumv}) pimeanv = precision*(mu-context.onevec*context.treas[-1]/252.0) log.info(pimeanv) pimeanv = { e:pimeanv[i,0] for i,e in enumerate(context.tickers) } delta = 1.0/252.0 rho = 1- (1.0/context.riskAversion) #calibrate the vasicek model slope, intercept, r_value, p_value, std_err = sp.linregress(np.array(context.treas[1:]),np.array(context.treas[:(context.nobs-1)])) kappa = -math.log(slope)/delta rbar = intercept/(1-slope) if slope <= 1.1 and slope >= 0.9: sigmar = std_err/math.sqrt(delta) pih= rho*sigmar/2.0 log.info('bad slope, zero hedge') else: sigmar = std_err*math.sqrt((-2*math.log(slope))/(delta*(1-math.pow(slope,2)))) #calculate the intertemporal hedging portfolio pih = rho*(sigmar/(2*kappa))*(1-math.exp(-kappa*(context.horizon-context.t))) #open all positions:1 startingCash = (context.portfolio.starting_cash+context.portfolio.pnl)*context.leverage for i, e in enumerate(context.sids): currentPosition = context.portfolio.positions[e].amount newPosition = math.floor(startingCash*(pimeanv[context.tickers[i]]/context.riskAversion+pih)/data[e].price) if newPosition-currentPosition < 10000: order(e, newPosition - currentPosition) else: log.info('explosion dected.. no order made.') currentPosition = context.portfolio.positions[context.bondProxy].amount newPosition = math.floor(startingCash*(1-pih-pimeanv['spy']/context.riskAversion)/data[context.bondProxy].price) if newPosition-currentPosition < 10000: order(context.bondProxy, newPosition - currentPosition) else: log.info('explosion dected.. no order made.') record(mean_variance = pimeanv['spy']/context.riskAversion) record(hedging = pih) else: context.daysToRecalibration -= 1 #record(c = context.portfolio.positions_value)

This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.