%% Workflow for Plotting an Option Price Surface Using the Heston Model
% Use optByHestonFFT to calibrate a FFT strike grid and then compute option prices and plot an option price surface.
%% Define Option Variables and Heston Model Parameters

AssetPrice = 80;
Rate = 0.03;
DividendYield = 0.02;
OptSpec = 'call';

V0 = 0.04;
ThetaV = 0.05;
Kappa = 1.0;
SigmaV = 0.2;
RhoSV = -0.7;

%
%% Compute the Option Prices for the Entire FFT (or FRFT) Strike Grid, Without Specifying "Strike"
% Compute option prices and also output the corresponding strikes. If the Strike input is empty ( [] ), option prices will be computed on the entire FFT (or FRFT) strike grid. The FFT (or FRFT) strike grid is determined as exp(log-strike grid), where each column of the log-strike grid has NumFFT points with LogStrikeStep spacing that are roughly centered around each element of log(AssetPrice). The default value for NumFFT is 2^12. In addition to the prices in the first output, the optional last output contains the corresponding strikes.

Settle = datenum('29-Jun-2017');
Maturity = datemnth(Settle, 6);
Strike = []; % Strike is not specified (will use the entire FFT strike grid)

% Compute option prices for the entire FFT strike grid
[Call, Kout] =  optByHestonFFT(Rate, AssetPrice, Settle, Maturity, OptSpec, Strike, ...
    V0, ThetaV, Kappa, SigmaV, RhoSV, 'DividendYield', DividendYield);

% Show the lowest and highest strike values on the FFT strike grid
format
MinStrike = Kout(1) % Lowest possible strike in the current FFT strike grid
MaxStrike = Kout(end) % Highest possible strike in the current FFT strike grid

% Show a subset of the strikes and corresponding option prices
Range = (2046:2052);
[Kout(Range) Call(Range)]

%
%% Change the Number of FFT (or FRFT) Points, and Compare with optByHestonNI
% Try a different number of FFT (or FRFT) points, and compare the results with direct numerical integration. Unlike optByHestonFFT, which uses FFT (or FRFT) techniques for fast computation across the whole range of strikes, the optByHestonNI function uses direct numerical integration and it is typically slower, especially for multiple strikes. However, the values computed by optByHestonNI can serve as a benchmark for adjusting the settings for optByHestonFFT.

% Try a smaller number of FFT (or FRFT) points 
% (e.g. for faster performance or smaller memory footprint)
NumFFT = 2^10; % Smaller than the default value of 2^12
Strike = []; % Strike is not specified (will use the entire FFT strike grid)
[Call, Kout] = optByHestonFFT(Rate, AssetPrice, Settle, Maturity, OptSpec, Strike, ...
    V0, ThetaV, Kappa, SigmaV, RhoSV, 'DividendYield', DividendYield, 'NumFFT', NumFFT);

% Compare with numerical integration method
Range = (510:516);
Strike = Kout(Range);
CallFFT = Call(Range);
CallNI = optByHestonNI(Rate, AssetPrice, Settle, Maturity, OptSpec, Strike, V0, ...
    ThetaV, Kappa, SigmaV, RhoSV, 'DividendYield', DividendYield);
Error = abs(CallFFT-CallNI);
table(Strike, CallFFT, CallNI, Error)

%
%% Make Further Adjustments to FFT (or FRFT)
% If the values in the output CallFFT are significantly different from those in CallNI, try making adjustments to optByHestonFFT settings, such as CharacteristicFcnStep, LogStrikeStep, NumFFT, DampingFactor, and so on. Note that if (LogStrikeStep * CharacteristicFcnStep) is 2*pi/ NumFFT, FFT is used. Otherwise, FRFT is used.

Strike = []; % Strike is not specified (will use the entire FFT or FRFT strike grid)
[Call, Kout] = optByHestonFFT(Rate, AssetPrice, Settle, Maturity, OptSpec, Strike, ...
    V0, ThetaV, Kappa, SigmaV, RhoSV, 'DividendYield', DividendYield, ...
    'NumFFT', NumFFT, 'CharacteristicFcnStep', 0.065, 'LogStrikeStep', 0.001);

% Compare with numerical integration method
Strike = Kout(Range);
CallFFT = Call(Range);
CallNI = optByHestonNI(Rate, AssetPrice, Settle, Maturity, OptSpec, Strike, V0, ...
    ThetaV, Kappa, SigmaV, RhoSV, 'DividendYield', DividendYield);
Error = abs(CallFFT-CallNI);
table(Strike, CallFFT, CallNI, Error)

% Save the final FFT (or FRFT) strike grid for future reference. For
% example, it provides information about the range of Strike inputs for
% which the FFT (or FRFT) operation is valid.
FFTStrikeGrid = Kout;
MinStrike = FFTStrikeGrid(1) % Strike cannot be less than MinStrike
MaxStrike = FFTStrikeGrid(end) % Strike cannot be greater than MaxStrike

%
%% Compute the Option Price for a Single Strike
% Once the desired FFT (or FRFT) settings are determined, use the Strike input to specify the strikes rather than providing an empty array. If the specified strikes do not match a value on the FFT (or FRFT) strike grid, the outputs are interpolated on the specified strikes.

Settle = datenum('29-Jun-2017');
Maturity = datemnth(Settle, 6);
Strike = 80; 

Call =  optByHestonFFT(Rate, AssetPrice, Settle, Maturity, OptSpec, Strike, ...
    V0, ThetaV, Kappa, SigmaV, RhoSV, 'DividendYield', DividendYield, 'NumFFT', NumFFT, ...
    'CharacteristicFcnStep', 0.065, 'LogStrikeStep', 0.001)

%
%% Compute the Option Prices for a Vector of Strikes
% Use the Strike input to specify the strikes.

Settle = datenum('29-Jun-2017');
Maturity = datemnth(Settle, 6);
Strike = (76:2:84)';

Call =  optByHestonFFT(Rate, AssetPrice, Settle, Maturity, OptSpec, Strike, ...
    V0, ThetaV, Kappa, SigmaV, RhoSV, 'DividendYield', DividendYield, 'NumFFT', NumFFT, ...
    'CharacteristicFcnStep', 0.065, 'LogStrikeStep', 0.001)

%
%% Compute the Option Prices for a Vector of Strikes and a Vector of Dates of the Same Lengths
% Use the Strike input to specify the strikes. Also, the Maturity input can be a vector, but it must match the length of the Strike vector if the ExpandOutput name-value pair argument is not set to "true".

Settle = datenum('29-Jun-2017');
Maturity = datemnth(Settle, [12 18 24 30 36]); % Five maturities
Strike = [76 78 80 82 84]'; % Five strikes

Call =  optByHestonFFT(Rate, AssetPrice, Settle, Maturity, OptSpec, Strike, ...
    V0, ThetaV, Kappa, SigmaV, RhoSV, 'DividendYield', DividendYield, 'NumFFT', NumFFT, ...
    'CharacteristicFcnStep', 0.065, 'LogStrikeStep', 0.001) % Five values in vector output

%
%% Expand the Outputs for a Surface
% Set the ExpandOutput name-value pair argument to "true" to expand the outputs into NStrikes-by NMaturities matrices. In this case, they are square matrices.

[Call, Kout] =  optByHestonFFT(Rate, AssetPrice, Settle, Maturity, OptSpec, Strike, ...
    V0, ThetaV, Kappa, SigmaV, RhoSV, 'DividendYield', DividendYield, 'NumFFT', NumFFT, ...
    'CharacteristicFcnStep', 0.065, 'LogStrikeStep', 0.001, ...
    'ExpandOutput', true) % (5 x 5) matrix outputs

%
%% Compute the Option Prices for a Vector of Strikes and a Vector of Dates of Different Lengths
% When ExpandOutput is "true", NStrikes do not have to match NMaturities (that is, the output NStrikes-by-NMaturities matrix can be rectangular).

Settle = datenum('29-Jun-2017');
Maturity = datemnth(Settle, 12*(0.5:0.5:3)'); % Six maturities
Strike = (76:2:84)'; % Five strikes

Call =  optByHestonFFT(Rate, AssetPrice, Settle, Maturity, OptSpec, Strike, ...
    V0, ThetaV, Kappa, SigmaV, RhoSV, 'DividendYield', DividendYield, 'NumFFT', NumFFT, ...
    'CharacteristicFcnStep', 0.065, 'LogStrikeStep', 0.001, ...
    'ExpandOutput', true) % (5 x 6) matrix output

%
%% Compute the Option Prices for a Vector of Strikes and a Vector of Asset Prices
% When ExpandOutput is "true", the output can also be a NStrikes-by-NAssetPrices rectangular matrix by accepting a vector of asset prices.

Settle = datenum('29-Jun-2017');
Maturity = datemnth(Settle, 12); % Single maturity
ManyAssetPrices = [70 75 80 85]; % Four asset prices
Strike = (76:2:84)'; % Five strikes

Call =  optByHestonFFT(Rate, ManyAssetPrices, Settle, Maturity, OptSpec, Strike, ...
    V0, ThetaV, Kappa, SigmaV, RhoSV, 'DividendYield', DividendYield, 'NumFFT', NumFFT, ...
    'CharacteristicFcnStep', 0.065, 'LogStrikeStep', 0.001, ...
    'ExpandOutput', true) % (5 x 4) matrix output

%
%% Plot an Option Price Surface
% Use the Strike input to specify the strikes. Increase the value for NumFFT to support a wider range of strikes. Also, the Maturity input can be a vector. Set ExpandOutput to "true" to output the surface as a NStrikes-by-NMaturities matrix.

Settle = datenum('29-Jun-2017');
Maturity = datemnth(Settle, 12*[1/12 0.25 (0.5:0.5:3)]');
Times = yearfrac(Settle, Maturity);
Strike = (2:2:200)';

% Increase 'NumFFT' to support a wider range of strikes
NumFFT = 2^13;

Call =  optByHestonFFT(Rate, AssetPrice, Settle, Maturity, OptSpec, Strike, ...
    V0, ThetaV, Kappa, SigmaV, RhoSV, 'DividendYield', DividendYield, 'NumFFT', NumFFT, ...
    'CharacteristicFcnStep', 0.065, 'LogStrikeStep', 0.001, 'ExpandOutput', true);

[X,Y] = meshgrid(Times,Strike);

figure;
surf(X,Y,Call);
title('Price');
xlabel('Years to Option Expiry');
ylabel('Strike');
view(-112,34);
xlim([0 Times(end)]);
zlim([0 80]);

% Copyright 2012 The MathWorks, Inc.