Quantitative finance is a broad term, here I am referring to solving pricing problems in the capital markets space (with all their regulatory and other side effects).
I am going to explore the possibility of creating a quant library that:
Put differently
To be useful on both sides of the job offer gap the library would need
When we do quant work we are aiming to
Unfortunately it seems that these two lists do not have a lot of overlap.
Sometimes we explicitly teach the cashflows for a particular contingent claim
Doing requires delivering working software to the consumers of the models, but
Can we teach in a way that increases the overlap?
That is what we are going to try:
We assume that we have:
We further assume that the measure $\mathbb{Q}$ is already the risk neutral one so that for any tradable asset $P$ (see later) with a cashflow only at $0<t_i<T$ we have
$$ \frac{P(t)}{N(t)} = \mathbb{E}^\mathbb{Q} \left[ \frac{P(t_i)}{N(t_i)} \middle| \mathcal{F}(t) \right] $$We assume the cashflows on any product or portfolio
(
This does not practically limit the types of products. E.g.
)
In general if there is a cashflow at $u_i$ it will depend on market observables at times on or before $u_i$
$$ X_i = f(S_{j_0}(v_{k_0}), S_{j_1}(v_{k_1}), ...) $$with $(j_l, k_l)$ in some set $\mathcal{J}(u_i)$ that depends on the product and $u_i$; and $v_{k_l} \leq u_i$
The value of any product at $t_0$ is then:
$$ V(t_0) = \mathbb{E}^\mathbb{Q} \left[ \sum_{u_i>t_0}{\frac{S_{xn}(u_i)X_i}{N(u_i)}} \right] $$Where
Any product without optionality can then be represented by
The types of $S$ that currently exist in QuantSA are:
And each of these has a specific sub-type, e.g.:
The specific sub-type of each of these has a value that is observable on a well defined screen at a well defined time.
The cashflows on a product can be written as functions of these observables.
cs
Date date = new Date(2017, 08, 28);
FloatingIndex jibar = FloatingIndex.JIBAR3M;
double dt = 91.0/365.0;
double fixedRate = 0.071;
double notional = 1000000.0;
Currency currency = Currency.ZAR;
public override List<Cashflow> GetCFs()
{
double reset = Get(jibar, date);
double cfAmount = notional * (reset - fixedRate)*dt/(1+dt*reset);
return new List<Cashflow>() { new Cashflow(date, cfAmount, currency) };
}
cs
Date exerciseDate = new Date(2017, 08, 28);
Share share = new Share("AAA", Currency.ZAR);
double strike = 100.0;
public override List<Cashflow> GetCFs()
{
double amount = Math.Max(0, Get(share, exerciseDate) - strike);
return new List<Cashflow>() {new Cashflow(exerciseDate, amount, share.currency) };
}
This product description script is common to many quant libraries available on the market.
As you can see we have "taught" exactly what a FRA and a call option are in a completely model independent way.
The next step is to look at how to value these products.
The library is written in C#. There are many reasons why this makes sense:
Nevertheless Python (or Matlab) remain much more convenient for scientific computing where you are experimenting with different models and methods.
In the following we will use QuantSA from Python.
In [1]:
import clr # to be able to use the C# library
clr.AddReference("System.Collections")
clr.AddReference(r'C:\Dev\QuantSA\QuantSA\Valuation\bin\Debug\QuantSA.General.dll')
clr.AddReference(r'C:\Dev\QuantSA\QuantSA\Valuation\bin\Debug\QuantSA.Valuation.dll')
from System.Collections.Generic import List
from QuantSA.General import *
from QuantSA.Valuation import *
print("The library is ready to use!")
In [2]:
source = """Date date = new Date(2017, 8, 28);
FloatingIndex jibar = FloatingIndex.JIBAR3M;
double dt = 91.0/365.0;
double fixedRate = 0.069;
double notional = 1000000.0;
Currency currency = Currency.ZAR;
public override List<Cashflow> GetCFs()
{
double reset = Get(jibar, date);
double cfAmount = notional * (reset - fixedRate)*dt/(1+dt*reset);
return new List<Cashflow>() { new Cashflow(date, cfAmount, currency) };
}
"""
# Make a product at runtime
fra = RuntimeProduct.CreateFromString("MyFRA", source);
print("Now we have a FRA:")
print(fra)
In [3]:
# Set up the model
valueDate = Date(2016, 9, 17)
maximumDate = Date(2026, 9, 17)
dates = [Date(2016, 9, 17) , Date(2026, 9, 17)]
rates = [ 0.07, 0.07 ]
discountCurve = DatesAndRates(Currency.ZAR, valueDate, dates, rates, maximumDate)
numeraireModel = DeterminsiticCurves(discountCurve);
otherModels = List[Simulator]() # no model other than discounting for now.
coordinator = Coordinator(numeraireModel, otherModels, 1) # the magic ingredient that gets
# models and products to work
# together
print("A model is ready.")
In [4]:
# Run the valuation
portfolio = [fra]
try:
value = coordinator.Value(portfolio, valueDate)
except Exception as e:
print(e)
Aha, this is good. You can't value a FRA with a discounting model because its cashflow depends on 3 month Jibar and your model does not know anything about 3 month Jibar.
With this type of constraint (which is deeply embedded in the library):
For our problem at hand we need to fix the model by setting it up to forecast some rates:
In [5]:
# add a forecast curve
forwardRates = [0.070614, 0.070614]
forecastCurve = ForecastCurve(valueDate, FloatingIndex.JIBAR3M, dates, forwardRates) # use flat 7% rates for forecasting
numeraireModel.AddRateForecast(forecastCurve) # add the forecast curve to the model
# value the product
portfolio = [fra]
value = coordinator.Value(portfolio, valueDate)
print("value is: {:.2f}".format(value))
Is the value right?
In [6]:
# check the value
import numpy as np
date = Date(2017, 8, 28)
t = (date.value - valueDate.value) / 365.0 # C# operator overloading does not work in Python
dt = 91.0 / 365.0
fixedRate = 0.069
notional = 1000000.0
fwdRate = 0.070614
refValue = (notional * (fwdRate - fixedRate) * dt / (1 + fwdRate * dt) *
np.exp(-t * 0.07))
print("value is: {:.2f}. Expected {:.2f}".format(value, refValue))
And just like that the cashflow definition can be turned into a value.
In [7]:
valueDate = Date(2016, 9, 17)
flatRate = 0.07
newModel = HullWhite1F(Currency.ZAR, 0.05, 0.01, flatRate, flatRate, valueDate)
# tell HW model it is allowed to make some forecasts
newModel.AddForecast(FloatingIndex.JIBAR3M)
newCoordinator = Coordinator(newModel, List[Simulator](), 100000)
value = newCoordinator.Value(portfolio, valueDate)
print("value with the new model is: {:.2f}".format(value))
I won't spend much time describing how to implement models, that remains roughly the same as in the "olden days":
The coordinating component moves the simulated values and cashflows between the products and models.
The canonical numerical scheme is Monte Carlo.
Other numerical schemes can be implemented:
The simulation based valuation boils down to estimating: $$ V(t_0) = \mathbb{E}^\mathbb{Q} \left[ \sum_{u_i>t_0}{\frac{S_{xn}(u_i)X_i}{N(u_i)}} \right] $$
as
$$ V(t_0) \approx \frac{1}{j_{max}+1} \sum_{j=0}^{j_{max}} \left[ \sum_{u_i>t_0}{\frac{S^{(j)}_{xn}(u_i)X^{(j)}_i}{N^{(j)}(u_i)}} \right] $$The deterministic model does one simulation
In addition to the cashflows on a product that are explicitly defined by the bilateral contract, there are many other financial effects of trading a product such as:
In general these depend on the bank's fair value of these products at future dates
If we require the forward value at time $t_i > t_0$ we need to evaluate
$$ V(t_i) = N(t_i)\mathbb{E}^\mathbb{Q}\left[ \sum_{u_i>t_i}{\frac{S_{xn}(u_i)X_i}{N(u_i)}} \middle| \mathcal{F}(t_i) \right] $$This could be evaluated with another Monte Carlo simulation beyond $t_i$ for each state of the world observed up to $t_i$ but this is prohibitively expensive.
We rather assume that the $X$s, $S$s and $N$ are Markov and note (see Shreve [4] Def 2.3.6) that
$$ V(t_i) = g(t_i, \textbf{W}(t_i)) $$Longstaff and Schwartz [3] describe how to use regression to estimate this function $g$ given realizations of $\sum_{u_i>t_i}{\frac{S_{xn}(u_i)X_i}{N(u_i)}}$
In general $g$ is exactly that function of $\textbf{W}(t_i)$ that minimizes $\mathbb{E}^\mathbb{Q}\left[ \left(g(\textbf{W}(t_i)) - \sum_{u_i>t_i}{\frac{S_{xn}(u_i)X_i}{N(u_i)}} \right)^2 \right]$
The approximation comes because we are estimating it from a finite sample.
Because of the finite sample:
In the QuantSA library this problem is solved by the coordinating component
Lets look at an example of it working:
In [8]:
# Set up a swap, which has a more interesting value profile than a FRA
rate = 0.08
payFixed = True
notional = 1000000
startDate = Date(2016, 9, 17)
tenor = Tenor.Years(5)
swap = IRSwap.CreateZARSwap(rate, payFixed, notional, startDate, tenor)
print("Now we have a swap:")
print(swap)
In [9]:
# Set up a stochastic model
valueDate = Date(2016, 9, 17)
a = 0.05
vol = 0.01
flatCurveRate = 0.07
hullWiteSim = HullWhite1F(Currency.ZAR, a, vol, flatCurveRate, flatCurveRate, valueDate)
hullWiteSim.AddForecast(FloatingIndex.JIBAR3M)
hwCoordinator = Coordinator(hullWiteSim, List[Simulator](), 5000)
print("A stochastic rate model is ready (Hull White).")
In [11]:
# make the forward dates on which the values are required
from datetime import datetime
from datetime import timedelta
step = timedelta(days=10)
pyDates = []
date = datetime(2016, 9, 17)
endDate = datetime(2021,9,17)
while date<endDate:
date += step
pyDates.append(date)
csDates = [Date(d.year, d.month, d.day) for d in pyDates]
In [13]:
# Get the simulated forward values and the regressors used to obtain them
hwCoordinator.SetThreadedness(True)
valuePaths = hwCoordinator.GetValuePaths([swap], valueDate, csDates)
print("Available data:")
for s in valuePaths.GetNames():
print(" " + s)
In [14]:
import sys
sys.path.insert(0, r'..\Python')
import quantsa as qsa
fwdCashflowPVs = qsa.getnumpy(valuePaths.Get("fwdCashflowPVs"))
regressor0 = qsa.getnumpy(valuePaths.Get("regressor0"))
regressedFwdsPVs = qsa.getnumpy(valuePaths.Get("regressedFwdsPVs"))
In [15]:
%matplotlib inline
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_xlim([0, 0.15])
ax.set_ylim([-150000, 100000])
col = 17 # up to 181
plt.plot(regressor0[:,col], fwdCashflowPVs[:,col],'.')
plt.plot(regressor0[:,col], regressedFwdsPVs[:,col],'k.')
Out[15]:
Example 1: The expected positive exposure depends on
Example 2: The CVA depends on:
Again these are separate from the models and the products.
If you implement a way to estimate, say, the pricing impact of initial margin it will work with anybody else's models and or products.
In [16]:
positive_mtm = regressedFwdsPVs
positive_mtm[positive_mtm<0] = 0
epe = np.mean(positive_mtm, 0)
plt.plot(epe)
Out[16]:
Always, one knows the cashflows as functions of states of the world both
The optimal stopping time for the person who owns this right is the one that chooses the alternative with the higher expected value.
This is again a general problem that does not need to be solved for each product and model.
Recall that we defined a product as:
$$ P = \left\{ \left(X_1, u_1\right), ..., \left(X_M, u_M\right) \right\} $$Similarly we can define a product with early exercise as
$$ O = P_{noex} \text{ and } \left\{ \left(Q_1, e_1\right), ..., \left(Q_M, e_M\right) \right\} $$Where
The extra pieces that need to be implemented on a product are then:
cs
List<Product> GetPostExProducts();
List<Date> GetExerciseDates()
In [17]:
# Make a Bermudan Swaption
exDates = List[Date]()
exDates.Add(Date(2017, 9, 17))
exDates.Add(Date(2018, 9, 17))
exDates.Add(Date(2019, 9, 17))
exDates.Add(Date(2020, 9, 17))
bermudan = BermudanSwaption(swap, exDates, True)
# Get the simulated forward values and the regressors used to obtain them
valuePaths = hwCoordinator.GetValuePaths([bermudan], valueDate, csDates)
fwdCashflowPVs = qsa.getnumpy(valuePaths.Get("fwdCashflowPVs"))
regressor0 = qsa.getnumpy(valuePaths.Get("regressor0"))
regressedFwdsPVs = qsa.getnumpy(valuePaths.Get("regressedFwdsPVs"))
In [18]:
# Examine the values after optimal exercise rule is applied
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_xlim([0, 0.15])
ax.set_ylim([-150000, 100000])
col = 20 # up to 181.
plt.plot(regressor0[:,col], fwdCashflowPVs[:,col],'.')
plt.plot(regressor0[:,col], regressedFwdsPVs[:,col],'k.')
Out[18]:
When students go to work they
The definition of product/model/measure interaction in terms only of market observables is fundamental and is unlikely to prove inadequate in the future.
This library does not change the way all courses would be taught.
For me there are also other topics in the course that I teach that do not have a place directly in the main library framework such as:
But, these tools and more will be required for calibrating the models in the library...
until that also just becomes another module :)
With careful software management we can have a library that creates as a bridge between teaching and doing.
The library has a separable design and a plugin framework that will meet the needs for proprietary models, special cases and user customization without damaging the clarity and teaching suitability.
I am hoping that many parts of quant finance can become like optimization: we all learn how to do it but in real life we use standard implementations.
In [19]:
import numpy as np
with plt.xkcd():
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
plt.xticks([])
plt.yticks([])
ax.set_ylim([-30, 10])
data = np.ones(100)
data[70:] -= np.arange(30)
plt.annotate(
'THE DAY PEOPLE STARTED\nUSING QUANTSA',
xy=(70, 0.9), arrowprops=dict(arrowstyle='->'), xytext=(15, -10))
plt.plot(data)
plt.xlabel('year')
plt.ylabel('quant hours wasted \n repeating the same work')
plt.show()
In [20]:
with plt.xkcd():
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.bar([-0.125, 1.0-0.125], [100, 10], 0.25)
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.set_xticks([0, 1])
ax.set_xlim([-0.5, 1.5])
ax.set_ylim([0, 110])
ax.set_xticklabels(['BUSINESS AS\n USUAL', 'QUANTSA ON \n LEFT AND RIGHT'])
plt.yticks([])
plt.ylabel('Difficulty')
plt.title("TRANSITIONING FROM THEORY TO PRACTICE")
plt.show()
[1] Serguei Issakov Alexandre Antonov and Serguei Mechkov. Backward induction for future values. Risk, pages 92-97, 2015.
[2] G. Cesari, J. Aquilina, and N. Charpillon. Modelling, Pricing, and Hedging Counterparty Credit Exposure. Springer, 2010.
[3] Francis A. Longstaff and Eduardo S. Schwartz. Valuing american options by simulation: A simple least-squares approach. Review of Financial Studies, pages 113-147, 2001.
[4] S.E. Shreve. Stochastic Calculus for Finance II: Continuous-Time Models. Number v. 11 in Springer Finance Textbooks. Springer, 2004.
In [ ]: