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.

part3_costs.m
%% part3_costs - Impact of Transaction Costs 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;
sellcost = 0.0020;

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

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
		p = p.setInitPort(pinit(iasset));
	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
		r = r.setInitPort(rinit(iasset));
	end
	
	% 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);
	
	% 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

%% plot results

figure(1);
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