Code covered by the BSD License  

Highlights from
Using MATLAB to Optimize Portfolios with Financial Toolbox

image thumbnail

Using MATLAB to Optimize Portfolios with Financial Toolbox

by

 

Scripts and data to demonstrate the new Portfolio object in Financial Toolbox.

part4_turnover.m
%% part4_turnover - Impact of Turnover on Portfolio Optimization
%
% Copyright 2011 The MathWorks, Inc.

load BlueChipStocks

% control parameters for backtest

numportfolio = 20;					% number of portfolios on each efficient frontier
window = 60;						% historical estimation window in months
offset = 3;							% shift in time for each frontier in months
cutoff = 0.4;						% this fraction of data in a series must be non-NaN values
relative = true;					% true if relative returns, false if absolute returns
accumulate = true;					% true if accumulation of assets, false if current universe only

buycost = 0.0020;					% proportional cost to purchase shares
sellcost = 0.0020;					% proportional cost to sell shares
maxturnover = 0.4;					% upper bound for portfolio turnover (annual)

imarket = strcmpi('Market', Asset);	% locate "market" series
icash = strcmpi('Cash', Asset);		% locate "cash" series (riskfree rate proxy)

% bookkeeping

pfactor = 12/offset;				% factor to convert periodicity to annual period

if relative
	criterion = 'Information Ratio';
else
	criterion = 'Sharpe Ratio';
end

% form cumulative map of assets (include all prior active assets that are still listed)

if accumulate
	for t = 2:size(Map,1)
		Map(t,:) = Map(t - 1,:) | Map(t,:);
	end
end

% ex-ante analysis

PortDate = [];
PortRisk = [];
PortReturn = [];
PortSigma = [];
PortMean = [];

PerfDate = [];
GrossPerfPort = [];
ExtrinsicPerfPort = [];
IntrinsicPerfPort = [];
PerfMarket = [];
PerfCash = [];

for t = window:offset:numel(Date)

	% set up date indices for current period

	startindex = t - window + 1;
	endindex = t;
	
	% select "market" series

	Xmarket = Data(startindex:endindex,imarket);
	
	% select assets that are active on the endindex date
	
	iasset = Map(endindex,:);

	% keep series with sufficient numbers of non-NaN values

	imissing = sum(isnan(Data(startindex:endindex,:))) > cutoff*window;
	
	% form active universe for current endindex date

	iasset = logical(iasset) & ~logical(imissing);
	iasset(end-1:end) = 0;		% last two series are not stocks (not used in this step)
		
	% select data for active universe

	A = Asset(iasset);
	X = Data(startindex:endindex,iasset);
	
	fprintf('Estimation period %s to %s with %d assets ...\n', ...
		datestr(Date(startindex)), datestr(Date(endindex)),numel(A));

	% map prior portfolios into current universe
	
	if t > window
		pinit = zeros(numel(iasset0),1);
		qinit = zeros(numel(iasset0),1);
		rinit = zeros(numel(iasset0),1);
		
		% adjust prior portfolio weights for prior period's returns
		retinit = Xret(iasset0);
		
		pinit(iasset0) = (1/(1 + retinit*pwgt))*((1 + retinit') .* pwgt);
		qinit(iasset0) = (1/(1 + retinit*qwgt))*((1 + retinit') .* qwgt);
		rinit(iasset0) = (1/(1 + retinit*rwgt))*((1 + retinit') .* rwgt);
	end

	% remove "market" from the data (market-neutral relative returns)

	if relative
		X = X - repmat(Xmarket, 1, numel(A));
	end
	
	% construct portfolio object (use RiskFreeRate if not market-neutral)

	p = PortfolioDemo('AssetList', A, 'Name', sprintf('Universe %s', datestr(Date(endindex))));
	if ~relative
		p = PortfolioDemo(p, 'RiskFreeRate', Data(endindex,icash));
	end
	p = p.setDefaultConstraints;
	p = p.estimateAssetMoments(X, 'MissingData', true);
	if t > window
		if sum(pinit(iasset)) < 0.99		% lift turnover constraint if asset kicked out
			p = p.setTurnover(min(1, maxturnover), pinit(iasset));
		else
			p = p.setTurnover(maxturnover/pfactor, pinit(iasset));
		end
	end
	
	% set up portfolio objects for net returns
	
	q = p;		% extrinsic net returns
	
	r = p.setCosts(buycost, sellcost, 0);		% intrinsic net returns
	if t > window
		if sum(rinit(iasset)) < 0.99		% lift turnover constraint if asset kicked out
			r = r.setTurnover(min(1, maxturnover), rinit(iasset));
		else
			r = r.setTurnover(maxturnover/pfactor, rinit(iasset));
		end
	end
	
	% estimate portfolios on turnover-constrained frontier
	
	fwgt = r.estimateFrontier(numportfolio);
	
	% estimate portfolio that maximizes the ratio of relative risk to relative return
	%	if absolute returns, then maximize the Sharpe ratio
	
	pwgt = p.maximizeSharpeRatio;
	[prsk, pret] = p.estimatePortMoments(pwgt);
	
	qwgt = q.maximizeSharpeRatio;
	[qrsk, qret] = q.estimatePortMoments(qwgt);

	rwgt = r.maximizeSharpeRatio;
	[rrsk, rret] = r.estimatePortMoments(rwgt);
	
	% enter data for 3D frontier
	
	PortDate = [ PortDate; Date(endindex) ];
	PortRisk = [ PortRisk; sqrt(pfactor)*(p.estimatePortRisk(fwgt))' ];
	PortReturn = [ PortReturn; pfactor*(p.estimatePortReturn(fwgt))' ];

	PortSigma = [ PortSigma; sqrt(pfactor)*rrsk ];
	PortMean = [ PortMean; pfactor*rret ];

	% evaluate performance

	if (endindex + offset) <= numel(Date)
		Xret = ret2tick(Data(endindex+1:endindex+offset,:));
		Xret = Xret(end,:) - 1;

		PerfDate = [ PerfDate; Date(endindex+offset) ];
		
		% gross portfolio return
		if t > window
			pcurrent = zeros(numel(iasset),1);
			pcurrent(iasset) = pwgt;
			pbuy = max(0, pcurrent - pinit);
			psell = max(0, pinit - pcurrent);
			pcost = 0;
			pturnover = pturnover + 0.5*(sum(pbuy) + sum(psell));
		else
			pcost = 0;
			pturnover = 0;
		end
		GrossPerfPort = [ GrossPerfPort; Xret(iasset)*pwgt ];

		% extrinsic net portfolio return
		if t > window
			qcurrent = zeros(numel(iasset),1);
			qcurrent(iasset) = qwgt;
			qbuy = max(0, qcurrent - qinit);
			qsell = max(0, qinit - qcurrent);
			qcost = buycost*sum(qbuy) + sellcost*sum(qsell);
			qturnover = qturnover + 0.5*(sum(qbuy) + sum(qsell));
		else
			qcost = 0;
			qturnover = 0;
		end
		ExtrinsicPerfPort = [ ExtrinsicPerfPort; (Xret(iasset)*qwgt - qcost) ];
	
		% intrinsic net portfolio return
		if t > window
			rcurrent = zeros(numel(iasset),1);
			rcurrent(iasset) = rwgt;
			rbuy = max(0, rcurrent - rinit);
			rsell = max(0, rinit - rcurrent);
			rcost = buycost*sum(rbuy) + sellcost*sum(rsell);
			rturnover = rturnover + 0.5*(sum(rbuy) + sum(rsell));
		else
			rcost = 0;
			rturnover = 0;
		end
		IntrinsicPerfPort = [ IntrinsicPerfPort; (Xret(iasset)*rwgt - rcost) ];

		PerfMarket = [ PerfMarket; Xret(imarket) ];
		PerfCash = [ PerfCash; Xret(icash) ];
	end
	
	% save information from current period to be used in next period
	
	iasset0 = iasset;
end

% set up dates across 3D frontier

PortDate = repmat(PortDate, 1, numportfolio);

%% plot results

figure(1);
surf(PortDate, PortRisk, PortReturn, ...
	'FaceColor', 'interp', 'EdgeColor', 'none', 'FaceLighting', 'phong');
hold on
plot3(PortDate(:,1), PortSigma, PortMean + 1.0e-6, 'w', 'LineWidth', 3);
datetick('x');
ylabel('Portfolio Risk');
zlabel('Portfolio Returns');
title('\bfTime Evolution of Efficient Frontier');
camlight right
view(30, 30);
hold off

figure(2);
plot([datenum(Date(window)); PerfDate], ...
	ret2tick([GrossPerfPort, ExtrinsicPerfPort, IntrinsicPerfPort, PerfMarket, PerfCash]));
datetick('x');
title('\bfBacktest Performance of Portfolio Strategy');
ylabel('Cumulative Value of $1 Invested 31-Dec-1984');
legend('Gross', 'Extrinsic', 'Intrinsic', 'Market', 'Cash', 'Location', 'NorthWest');

%% summarize results

perf = [GrossPerfPort, ExtrinsicPerfPort, IntrinsicPerfPort, PerfMarket, PerfCash];
pmean = pfactor*mean(perf);
pstdev = sqrt(pfactor)*std(perf);
perfret = ret2tick(perf);
ptotret = (perfret(end,:) .^ (pfactor/size(perf,1))) - 1;
pmaxdd = maxdrawdown(perfret);

fprintf('Results for Backtest Period from %s to %s\n',datestr(Date(window)),datestr(PerfDate(end)));
fprintf('%14s %12s %12s %12s %12s %12s\n','','Mean','Std.Dev.','Tot.Ret.','Max.DD','Turnover');
fprintf('%14s %12g %12g %12g %12g %12g\n','Gross', ...
	100*pmean(1),100*pstdev(1),100*ptotret(1),100*pmaxdd(1),100*pfactor*pturnover/numel(PerfDate));
fprintf('%14s %12g %12g %12g %12g %12g\n','Extrinsic Net', ...
	100*pmean(2),100*pstdev(2),100*ptotret(2),100*pmaxdd(2),100*pfactor*qturnover/numel(PerfDate));
fprintf('%14s %12g %12g %12g %12g %12g\n','Intrinsic Net', ...
	100*pmean(3),100*pstdev(3),100*ptotret(3),100*pmaxdd(3),100*pfactor*rturnover/numel(PerfDate));
fprintf('%14s %12g %12g %12g %12g\n','Market', ...
	100*pmean(4),100*pstdev(4),100*ptotret(4),100*pmaxdd(4));
fprintf('%14s %12g %12g %12g %12g\n','Cash', ...
	100*pmean(5),100*pstdev(5),100*ptotret(5),100*pmaxdd(5));

Contact us