Code covered by the BSD License  

Highlights from
Analyzing Investment Strategies with CVaR Portfolio Optimization

image thumbnail

Analyzing Investment Strategies with CVaR Portfolio Optimization

by

Bob Taylor

 

18 Dec 2012 (Updated )

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

covered_engine(X, T, mu, sigma, ...
function [rC, WC, CC, HC, KC] = covered_engine(X, T, mu, sigma, ...
	initial_equity, distribution, no_reinvestment, initiating, ...
	strike_cushion, contract_expiration, next_contract_expiration, risk_free_rate, ...
	stock_cost, contract_cost, exercise_likelihood, ...
	confirmation_delay, reinvestment_delay, settlement_delay)
%covered_engine - Generate scenarios for a covered-call strategy with expiration.
%
%	[rC, WC, CC, HC, KC] = covered_engine(X, T, mu, sigma, ...
%		initial_equity, distribution, no_reinvestment, initiating, ...
%		strike_cushion, contract_expiration, next_contract_expiration, risk_free_rate, ...
%		stock_cost, contract_cost, exercise_likelihood, ...
%		confirmation_delay, reinvestment_delay, settlement_delay);
%
% Inputs:
%	Stock Information
%		X - Stock total return prices including initial price [scalar].
%		T - Duration of investment period in years (terminal time - initial time) [scalar].
%		mu - Annaulized stock drift parameter [scalar].
%		sigma - Annualized stock volatility parameter [scalar].
%	Fund Details
%		initial_equity - Initial (uncovered) total value of stock and cash held in asset [scalar].
%		distribution - Annualized fund distribution [scalar].
%		no_reinvestment - If true, then no reinvestment permitted until next investment period if
%			assigned during current investment period. If false, then reinvestment is permitted
%			[scalar].
%		initiating - If true, then initiating covered-call during current investment period period.
%			If false, then ongoing covered-call so that initial call premium does not contribute to
%			total return for the period [scalar].
%	Option Details
%		strike_cushion - Strike price "cushion" as a percentage above current price [scalar].
%		contract_expiration - Option expiration from initial time in years [scalar].
%		next_contract_expiration - Next option contract expiration from initial time in years
%			[scalar].
%		risk_free_rate - Annualized risk-free rate [scalar].
%	Costs/Frictions
%		stock_cost - Proportional cost to buy or sell stock [scalar].
%		contract_cost - Proportional cost to write option [scalar].
%		exercise_likelihood - Probability than an at-the-money or in-the-money call will be
%			exercised early (see comments) [scalar].
%	Strategy Information
%		confirmation_delay - Number of periods delay to confirm assignment [scalar].
%		reinvestment_delay - Number of periods delay to reinvest in stock after assignment [scalar].
%		settlement_delay - Number of periods delay to settle reinvested stock position [scalar].
%
% Outputs:
%	rC - Total return for covered strategy [scalar].
%	WC - Sequence of total wealth for covered strategy [vector].
%	CC - Sequence of cash for covered strategy [vector].
%	HC - Sequence of holdings for covered strategy [vector].
%	KC - Sequence of strike prices for covered strategy [vector].
%
% Comments:
%	1) This function generates scenarios for a covered-call strategy based on numerous assumptions.
%		At a fundamental level, it employs two distinct approaches to generate such scenarios that
%		depend upon whether the scenario is an initiating or an ongoing covered-call position. To
%		introduce some degree of "realism" into the model, numerous inputs can be specified to
%		control the simulations. The next few comments provide additional details on these inputs.
%	2) X and T are stock total return prices and "times" in years that are assumed to be generated
%		by a geometric Brownian motion process with stochastic differential equation in the form
%			dX(t) = mu*X(t)*dt + sigma*X(t)*dB(t)
%		for t > 0.
%	3) No naked or recursive calls and no rollovers allowed.
%	4) If exercise_likelihood is a number, it is the probability that an assignment might occur at
%		any time between the initial time and the contract expiration. If the exercise_likelihood
%		is a NaN, this function computes a probability based on heuristics that generally results in
%		very low probabilities of exercise. Both approaches have increasing probabilities as the
%		time approaches contract expiration.
%	5) The probability of early exercise is derived from the underlying parameters for the geometric
%		Brownian motion of the stock price, where the probability of early exercise is assumed to be
%		equal to the probability that the stock price will be above the strike price at expiration.
%		Note that this probability is different than the probability associated with moneyness.
%	6) Assignment occurs when a call option is exercised which means that the investor must deliver
%		stock in exchange for the strike price of the call option. This function loops over an
%		investment period and, depending upon the state of the position at each time, determines
%		whether to exercise, assign, reinvest, settle, or write a new call. If the option expires
%		during the investment period, assignment always occurs if the option expires in-the-money
%		and the new covered call position is written with the next contract expiration.
%	7) The last three inputs are the variables confirmation_delay, reinvestment_delay, and
%		settlement_delay. They indicate fixed amounts of time that an assigned covered call position
%		spends in different states associated with the assignment and reinvestment process. These
%		variables are specified as durations at the periodicity of the simulation. For example, if
%		the time between samples is 30 minutes, a delay of 4 implies a 2-hour delay.
%	8) The delays are:
%		confirmation_delay
% 			The first delay is a confirmation delay. Once an assignment has occurred (based on the
% 			probability of early exercise), a small delay may exist before the assignment is
% 			confirmed. This delay can be due to either delays in notification or delays in response.
% 			Typically it is a small value although index options can have 24 hour confirmation
% 			delays. At the end of this period, the cash from exercise shows up in the account and
% 			the stock has been delivered.
% 		reinvestment_delay
% 			Once assignment has occurred, the next delay is the time needed to reinvest (if the
% 			no_reinvestment flag is false). Although small portfolios many have no delay for
% 			reinvestment of the cash in the underlying assets, large portfolios can experience
% 			significant delays that contribute to opportunity costs. For this model, reinvestment is
% 			assumed to be a "laddered" approach over the period specified by reinvestment_delay. For
% 			example, if reinvestment_delay is 10, then the cash is reinvested in the stock in 10
% 			equal portions at the periodicity of the simulation.
% 		settlement_delay
% 			Once reinvestment has been completed, the next delay is settlement of the position. This
% 			is important since it is not possible to write a naked call on a position that has not
% 			yet settled. Typically, this delay is the "T+3" settlement time although, in this
% 			simulation, it is treated as an equivalent delay at the periodicity of the simulation.
% 			Once the settlement delay is complete, it is then possible to write a new call on the
% 			settled stock position.

% Copyright (C) 2012 The MathWorks, Inc.

% initialization

N = numel(X) - 1;				% N is the number of samples in X excluding the initial price
tau = T/N;						% tau is the time interval between samples in "years"

periods_per_day = floor(N/(252*T) + 0.5);	% periods_per_day is number of periods in a "day"

% event flags

assignment_state = false;		% true when an assignment is in progress, false otherwise

confirmation_state = false;		% assignment step 1, true until confirmation step has been completed
reinvestment_state = false;		% assignment step 2, true until reinvestment step has been completed
settlement_state = false;		% assignment step 3, true until settlement step has been completed

% initial position

current_price = X(1);									% initial price
current_time = 0;										% initial time

current_shares = floor(initial_equity/current_price);	% initial number of shares
current_cash = max(0, (initial_equity - current_shares*current_price));	% initial cash

uncovered_shares = current_shares;
uncovered_cash = current_cash;

% strike price is above stock price by the "cushion"
strike_price = (1 + strike_cushion)*current_price;

% strike prices in $1 increments for shares above $10 and $0.50 for shares below $10
if current_price <= 10
	strike_price = ceil(2*strike_price)/2;
else
	strike_price = ceil(strike_price);
end

% can use Black-Scholes option pricing formula for American options since no dividends
dt = contract_expiration - current_time;
current_call = gbm_call_price(current_price, strike_price, risk_free_rate, dt, sigma);

% call price is rounded down to nearest $0.01
current_call = floor(100*current_call)/100;

current_cash = current_cash + current_shares*(current_call - contract_cost);
current_strike = strike_price;

% distinguish between initiating and ongoing positions
uncovered_initial_wealth = uncovered_cash + uncovered_shares*current_price;
covered_initial_wealth = current_cash + current_shares*current_price;

% generate scenarios

% track shares, strike, cash, and wealth over time for testing

if nargout > 2
	HC = zeros(N+1,1);		% (H)oldings in stocks
	KC = zeros(N+1,1);		% stri(K)e price for current written call
	CC = zeros(N+1,1);		% (C)ash
	WC = zeros(N+1,1);		% (W)ealth
	
	HC(1) = current_shares;
	KC(1) = current_strike;
	CC(1) = current_cash;
	WC(1) = current_cash + current_shares*current_price;
end

% loop over investment period for covered strategy

for iter = 2:N+1
	
	% get current price and current time
	current_price = X(iter);
	current_time = tau*(iter - 1);
	
	% determine current states and progress within states
	if assignment_state
		% note that the assignment_state does not end until a new covered position is created
		
		% covered position is in assignment
		if confirmation_state

			% countdown for confirmation step
			confirmation_count = confirmation_count - 1;
			if confirmation_count == 0
				% at last confirmation period, deliver stock and receive strike
				current_cash = current_cash + current_shares*current_strike;
				current_shares = 0;

				% at last confirmation period, initiate reinvestment step
				confirmation_state = false;
				reinvestment_state = true;
				reinvestment_count = reinvestment_delay;
			end

		elseif reinvestment_state

			% reinvest in stocks during reinvestment period by "laddering"
			if ~no_reinvestment
				adjusted_stock_price = current_price + stock_cost;
				shares_purchased = floor(current_cash/(adjusted_stock_price*reinvestment_count));
				current_shares = current_shares + shares_purchased;
				current_cash = current_cash - shares_purchased*adjusted_stock_price;
			end

			% countdown for reinvestment step
			reinvestment_count = reinvestment_count - 1;
			if reinvestment_count == 0
				% at last reinvestment period, initiate settlement step
				reinvestment_state = false;
				settlement_state = true;
				settlement_count = settlement_delay;
			end

		elseif settlement_state	

			% countdown for settlement step
			settlement_count = settlement_count - 1;
			if settlement_count == 0
				% at last settlement period, terminate assignment step
				settlement_state = false;
			end

		else	% write new call

			% strike price is above stock price by the "cushion"
			strike_price = (1 + strike_cushion)*current_price;

			% strike prices in $1 increments for shares above $10 and $0.50 for shares below $10
			if current_price <= 10
				strike_price = ceil(2*strike_price)/2;
			else
				strike_price = ceil(strike_price);
			end

			% can use Black-Scholes option pricing formula for American option since no dividends
			dt = contract_expiration - current_time;
			current_call = gbm_call_price(current_price, strike_price, risk_free_rate, dt, sigma);

			% call price is rounded down to nearest $0.01
			current_call = floor(100*current_call)/100;

			% write call if call price is greater than contract cost
			if ~no_reinvestment && current_call > contract_cost
				current_cash = current_cash + current_shares*(current_call - contract_cost);
				current_strike = strike_price;
				assignment_state = false;
			end
		end
	else
		% covered position is not in assignment
		
		% check if call is at-the-money or in-the-money
		if current_price >= current_strike
			% The exercise probability is the probability that an assignment will occur during the
			% current subperiod of duration dt. Although various models can be formulated, assume
			% either a heuristic "moneyness" probability or a fixed uniform probability of exercise.

			dt = contract_expiration - current_time;

			% option has expired at- or in-the-money
			if dt <= 0
				% If option expires in-the-money, always exercise.
				exercise_probability = 1;
			else
				if isnan(exercise_likelihood)
					% Estimate probability of early exercise for next period (heuristic model). Compute
					% a "risk-free rate" probability based on risk-neutral arguments and compute a
					% "drift rate" probability based on diffusion arguments. Use the maximum of these
					% two alternatives.

					numer = log(current_price/current_strike) ...
						+ (max(risk_free_rate, mu) - 0.5*sigma^2)*dt;
					denom = 2*sigma*sqrt(dt);

					if contract_expiration == current_time
						exercise_probability = 1;
					else
						prob = 0.5*(1 + erf(numer/denom));
						exercise_probability = 1 - (1 - prob)^(tau/dt);
					end
				else
					% Based on a specified probability of early exercise between the start of the
					% investment period and option expiration, compute the probability of assignment for
					% the current subperiod.

					exercise_probability = 1 - (1 - exercise_likelihood)^(tau/dt);
				end
			end
			
			% check for assignment for this period and if assigned, start assignment process
			if rand() <= exercise_probability

				% initiate assignment process
				assignment_state = true;

				% initiate assignment confirmation step
				confirmation_state = true;
				confirmation_count = confirmation_delay;
			end
		end
	end
	
	% if option expires, shift to next contract
	if contract_expiration == current_time
		contract_expiration = next_contract_expiration;
		
		if current_price >= current_strike
			% if option is in-the-money at expiration, then if not in assignment, exercise option
			if ~assignment_state
				% initiate assignment process
				assignment_state = true;
				
				% initiate assignment confirmation step
				confirmation_state = true;
				confirmation_count = confirmation_delay;
			end
		end
	end
	
	% accrue interest on cash account at end of each day
	if mod(iter, periods_per_day) == 1			% note that period 1 happens outside loop
		current_cash = current_cash*(1 + risk_free_rate*tau*periods_per_day);
	end
	
	% update test variables
	if nargout > 2
		HC(iter) = current_shares;
		KC(iter) = current_strike;
		CC(iter) = current_cash;
		WC(iter) = current_cash + current_shares*current_price;
	end
end

% at the end of the period, pay a distribution and sell shares if not enough cash

distribution = distribution*T;

needed_cash = distribution*(current_cash + current_shares*X(end)) - current_cash;

if needed_cash > 0
	adjusted_stock_price = X(end) + stock_cost;
	shares_sold = ceil(needed_cash/adjusted_stock_price);
	
	current_shares = current_shares - shares_sold;
	current_cash = current_cash + shares_sold*(X(end) - stock_cost);
	
	if nargout > 2
		HC(end) = current_shares;
		CC(end) = current_cash;
		WC(end) = current_cash + current_shares*X(end);
	end
end

% final scenario returns

% if initiating is true, then initiating a position so total return includes initial call premium
% otherwise, an ongoing position

if initiating
	rC = (current_cash + current_shares*X(end))/uncovered_initial_wealth - 1;
else
	rC = (current_cash + current_shares*X(end))/covered_initial_wealth - 1;
end

Contact us