Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 84 additions & 24 deletions QQuantLib/finance/classical_finance.py
Original file line number Diff line number Diff line change
Expand Up @@ -558,35 +558,28 @@ def bs_tree(
s_0: float,
risk_free_rate: float,
volatility: float,
maturity: float,
number_samples: int,
time_steps: int,
times: np.ndarray,
discretization: int,
bounds: float,
**kwargs
):
r"""
Computes the probabilities of all possible pahts from the
approximated solution of the Black-Scholes SDE using the
Euler-Maruyama discretization for a given discretization of the
approximated solution of the Black-Scholes SDE using
exact simulation for a given discretization of the
Brownian motion.

Parameters
----------

s_0 : float
current price of the underlying
risk_free_rate : float
risk free rate
volatility : float
the volatility
maturity : float
the maturity
strike : float
the strike
number_samples : int
number of samples
time steps : int
number of time steps
times : np.ndarray
the times of the tree
discretization : float
number of points to build the discrete version
of the gaussian density
Expand All @@ -595,10 +588,13 @@ def bs_tree(

Returns
-------
s_t : numpy array of floats
array of samples from the SDE.
s_t : list of numpy arrays
Each element of the list is an array with all the posible asset
values discretization
p_t : numpy array of floats
array with the probabilities for all the paths
"""
dt = maturity / time_steps
time_steps = len(times) - 1
x_ = np.linspace(-bounds, bounds, discretization)
p_x = norm.pdf(x_)
p_x = p_x / np.sum(p_x)
Expand All @@ -607,27 +603,91 @@ def bs_tree(
p_t = []
s_t.append(np.array([s_0]))
p_t.append(np.array([1.0]))

for i in range(time_steps):
dt = times[i+1] - times[i]
all_possible_paths = np.array(np.zeros(discretization ** (i + 1)))
all_possible_probabilities = np.array(np.zeros(discretization ** (i + 1)))
all_possible_probabilities = np.array(
np.zeros(discretization ** (i + 1)))

for j in range(len(s_t[i])):
single_possible_paths = (
s_t[i][j]
+ risk_free_rate * s_t[i][j] * dt
+ volatility * s_t[i][j] * x_ * np.sqrt(dt)
s_t[i][j] * np.exp((risk_free_rate - 0.5 * volatility**2) *\
dt + volatility * np.sqrt(dt) * x_)
)
single_possible_probabilities = p_t[i][j] * p_x

index = j * discretization
all_possible_paths[index : index + discretization] = single_possible_paths
all_possible_probabilities[
index : index + discretization
] = single_possible_probabilities
all_possible_paths[index : index + discretization] =\
single_possible_paths
all_possible_probabilities[index : index + discretization] =\
single_possible_probabilities

s_t.append(all_possible_paths)
p_t.append(all_possible_probabilities)

return s_t, p_t

def tree_to_paths(tree):
"""
Convert a tree structure from bs_tree to a path format (table form)

Parameters
----------

tree : list
list of lists with the tree structure from bs_tree

Returns
-------

paths : list of lists
table conversions of the tree structure input
"""
number_times = len(tree)
number_paths = len(tree[number_times - 1])

paths = np.zeros((number_times, number_paths))

for i in range(number_times - 1):
repeat = len(tree[-1]) / len(tree[i])
paths[i, :] = np.repeat(tree[i], repeat)
paths[-1, :] = tree[-1]

return paths

def cliquet_cashflows(local_cap, local_floor, global_cap, global_floor, paths):
"""
Calculate the cashflows for the Cliquet option.

Parameters
----------

local_cap : float
local cap for cliquet options
local_floor : float
local floor for cliqet options
global_cap : float
global cap for cliquet options
global_floor : float
global floor for cliqet options
paths : list
input paths for the cliquet option

Return
------
final_return : numpy array
The cashflows of the option for the input paths
"""

# Calculate period returns
period_returns = (paths[1:] / paths[:-1]) - 1.
# Apply local caps and floors
capped_floored_returns = np.clip(period_returns, local_floor, local_cap)
# Sum returns and apply global caps/floors
total_return = np.sum(capped_floored_returns, axis=0)
final_return = np.clip(total_return, global_floor, global_cap)
return final_return

def geometric_sum(base: float, exponent: int, coeficient: float = 1.0, **kwargs):
r"""
Expand Down
152 changes: 152 additions & 0 deletions QQuantLib/finance/cliquet_return_estimation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"""
This module implements the *ae_clique_return_estimation* function
that allows to the user configure a cliquet option, encode the expected
value integral to compute in a quantum state and estimate it using the
different **AE** algorithms implemented in the **QQuantLib.AE** package.


The function deals with all the mandatory normalisations for returning
the desired price estimation.

Authors: Alberto Pedro Manzano Herrero & Gonzalo Ferro Costas
"""
import numpy as np
import pandas as pd
from QQuantLib.finance.classical_finance import bs_tree
from QQuantLib.finance.classical_finance import tree_to_paths
from QQuantLib.finance.classical_finance import cliquet_cashflows
from QQuantLib.utils.utils import text_is_none
from QQuantLib.finance.quantum_integration import q_solve_integral

def ae_cliquet_estimation(**kwargs):
"""
Configures a cliquet option return estimation problem and solving it
using AE integration techniques
"""

ae_problem = kwargs

n_qbits = ae_problem.get("n_qbits", None)
s_0 = ae_problem.get("s_0", None)
text_is_none(s_0, "s_0", variable_type=float)
risk_free_rate = ae_problem.get("risk_free_rate", None)
text_is_none(risk_free_rate, "risk_free_rate", variable_type=float)
volatility = ae_problem.get("volatility", None)
text_is_none(volatility, "volatility", variable_type=float)
reset_dates = ae_problem.get("reset_dates", None)
text_is_none(reset_dates, "reset_dates", variable_type=list)
reset_dates = np.array(reset_dates)
bounds = ae_problem.get("bounds", None)
text_is_none(bounds, "bounds", variable_type=float)

# Built paths and probabilities
tree_s, bs_path_prob = bs_tree(
s_0=s_0,
risk_free_rate=risk_free_rate,
volatility=volatility,
times=reset_dates,
discretization=2**n_qbits,
bounds=bounds
)
#probability definition
p_x = bs_path_prob[-1]
#probability normalisation
p_x_normalisation = np.sum(p_x)
norm_p_x = p_x / p_x_normalisation

# Build Cliquet PayOffs for paths
local_cap = ae_problem.get("local_cap", None)
text_is_none(local_cap, "local_cap", variable_type=float)
local_floor = ae_problem.get("local_floor", None)
text_is_none(local_floor, "local_floor", variable_type=float)
global_cap = ae_problem.get("global_cap", None)
text_is_none(global_cap, "global_cap", variable_type=float)
global_floor = ae_problem.get("global_floor", None)
text_is_none(global_floor, "global_floor", variable_type=float)

# Table format paths
paths_s = tree_to_paths(tree_s)
# Build payoff for each possible path
cliqet_payoffs = cliquet_cashflows(
local_cap=local_cap,
local_floor=local_floor,
global_cap=global_cap,
global_floor=global_floor,
paths=paths_s
)
#Function definition
f_x = cliqet_payoffs
#Function normalisation
f_x_normalisation = np.max(np.abs(f_x))
norm_f_x = f_x / f_x_normalisation

#Now we update the input dictionary with the probability and the
#function arrays
ae_problem.update({
"array_function" : norm_f_x,
"array_probability" : norm_p_x,
})
#EXECUTE COMPUTATION
solution, solver_object = q_solve_integral(**ae_problem)

#For generating the output DataFrame we delete the arrays
del ae_problem["array_function"]
del ae_problem["array_probability"]

#Undoing the normalisations
ae_expectation = solution * p_x_normalisation * f_x_normalisation

#Creating the output DataFrame with the complete information

#The basis will be the input python dictionary for traceability
pdf = pd.DataFrame([ae_problem])
#Added normalisation constants
pdf["payoff_normalisation"] = f_x_normalisation
pdf["p_x_normalisation"] = p_x_normalisation

#Expectation calculation using Riemann sum
pdf["riemann_expectation"] = np.sum(p_x * f_x)
#Expectation calculation using AE integration techniques
pdf[
[col + "_expectation" for col in ae_expectation.columns]
] = ae_expectation
# Pure integration Absolute Error
pdf["absolute_error"] = np.abs(
pdf["ae_expectation"] - pdf["riemann_expectation"])
pdf["measured_epsilon"] = np.abs(
pdf["ae_u_expectation"] - pdf["ae_l_expectation"]) / 2.0
# Finance Info
#Exact option price under the Black-Scholes model
pdf["finance_exact_price"] = None
#Option price estimation using expectation computed as Riemann sum
pdf["finance_riemann_price"] = pdf["riemann_expectation"] * np.exp(
-pdf["risk_free_rate"] * reset_dates[-1]
)
#Option price estimation using expectation computed by AE integration
pdf["finance_price_estimation"] = pdf["ae_expectation"] * \
np.exp(-pdf["risk_free_rate"] * reset_dates[-1]).iloc[0]
#Computing Absolute with discount: Rieman vs AE techniques
pdf["finance_error_riemann"] = np.abs(
pdf["finance_price_estimation"] - pdf["finance_riemann_price"]
)

#Computing Absolute error: Exact BS price vs AE techniques
pdf["finance_error_exact"] = None

#Other interesting staff
if solver_object is None:
#Computation Fails Encoding 0 and RQAE
pdf["schedule_pdf"] = [None]
pdf["oracle_calls"] = [None]
pdf["max_oracle_depth"] = [None]
pdf["run_time"] = [None]
else:
if solver_object.schedule_pdf is None:
pdf["schedule_pdf"] = [None]
else:
pdf["schedule_pdf"] = [solver_object.schedule_pdf.to_dict()]
pdf["oracle_calls"] = solver_object.oracle_calls
pdf["max_oracle_depth"] = solver_object.max_oracle_depth
pdf["run_time"] = solver_object.solver_ae.run_time

return pdf
Loading
Loading