Current job as Market Risk Manager
define and implement pricing models
execute pricing routines
2.4 million contracts
78 million cashflows
17GB of uncompressed raw data in CSV format
more than US$ 150 billion in book value (2016 public balance sheet)
20 minutes to build the database from CSV files (done once)
t=0 t=T
N
┌──────────────┘
P
In general: $ P = N \times \text{discountfactor}(r, T) $
But, given $r$ and $T$, you must know the convention used to:
discountfactor
is definedA Term Structure of Interest Rates, also known as zero-coupon curve, is a function f(t) → y
that maps a given maturity t
onto the rate y
of a bond that matures at t
and pays no coupons (zero-coupon bond).
It's not feasible to observe prices for each possible maturity. We can observe only a set of discrete data points of the yield curve. Therefore, in order to determine the entire term structure, one must choose an interpolation method, or a term structure model.
$\tau$ is the maturity in years
In [1]:
using Plots; gr()
curve_date = Date(2017,3,2)
days_to_maturity = [ 1, 22, 83, 147, 208, 269,
332, 396, 458, 519, 581, 711, 834]
rates = [ 0.1213, 0.121875, 0.11359 , 0.10714 , 0.10255 , 0.100527,
0.09935 , 0.09859 , 0.098407, 0.098737, 0.099036, 0.099909, 0.101135]
plot(days_to_maturity, rates, markershape=:circle, linewidth=0, xlabel="maturity", ylabel="rates", legend=:none, ylims=(0, max(rates...)+0.05))
Out[1]:
In [2]:
# Pkg.add("InterestRates"); Pkg.add("BusinessDays")
using InterestRates, BusinessDays
const ir = InterestRates
method = ir.CompositeInterpolation(ir.StepFunction(), # before-first
ir.CubicSplineOnRates(), #inner
ir.FlatForward()) # after-last
curve_brl = ir.IRCurve("Curve BRL", # name
ir.BDays252(:Brazil), # DayCountConvention
ir.ExponentialCompounding(), # CompoundingType
method, # interpolation method
curve_date, # base date
days_to_maturity,
rates);
In [3]:
x_axis = collect((curve_date+Dates.Day(1)):Dates.Day(1):Date(2022,2,1))
y_axis = zero_rate(curve_brl, x_axis) # performs interpolation
plot(x_axis, y_axis, xlabel="maturity", ylabel="rates", ylims=(0, max(rates...)+0.05), legend=:none, linewidth=2)
plot!([ advancebdays(:Brazil, curve_date, d) for d in days_to_maturity ], rates, markershape=:circle, linewidth=0)
Out[3]:
In [4]:
fixed_maturity = Date(2018,5,3)
discountfactor(curve_brl, fixed_maturity) # JIT
@elapsed discountfactor(curve_brl, fixed_maturity)
Out[4]:
In [5]:
buffered_curve_brl = ir.BufferedIRCurve(curve_brl)
discountfactor(buffered_curve_brl, fixed_maturity) # stores in cache
@elapsed discountfactor(buffered_curve_brl, fixed_maturity) # retrieves stored value in cache
Out[5]:
In [6]:
using BusinessDays
bdays(:Brazil, Date(2017,6,22), Date(2017,6,26))
Out[6]:
In [7]:
using BusinessDays
const bd = BusinessDays
d0 = Date(2015, 06, 29) ; d1 = Date(2100, 12, 20)
cal = bd.Brazil()
@elapsed bd.initcache(cal)
Out[7]:
In [8]:
# this same benchmark takes 38 minutes to complete on QuantLib
@elapsed for i in 1:1_000_000 bdays(cal, d0, d1) end
Out[8]:
# contract search
contract = get_contract(db, code)
# get a unique identifier for the pricing model
model_key = infer_pricing_model_key(pricing_date, contract)
# creates an instance of the model.
# Connects to a database to retrieve market data (IO operation)
# This is done only once for each distinct value of model_key
model = get_pricing_model(conn, model_key)
# pricing formula
price(model, contract)
For fixed contracts, we can use the contract cash-flow directly, if available.
function price(model::FixedBond, c::Contract)
mtm = 0.0
for cf in c.cashflows
mtm += cf.value
* discountfactor(model.curve_riskfree, cf.date)
* discountfactor(model.curve_spread, cf.date)
end
return mtm * model.currency_spot_value
end
In the more general case, for floating-rate contracts, we must project cashflow using interest rate curves
function price(model::FloatingRate, c::Contract)
mtm = 0.0
for (dt, v) in CashFlowIterator(model, c)
mtm += v
* discountfactor(model.curve_riskfree, dt)
* discountfactor(model.curve_spread, dt)
end
return mtm * model.currency_spot_value
end
In [9]:
type MyType
a::Int
b::Char
end
vec = [ MyType(1,'a'), MyType(2, 'b') ]
Out[9]:
In [10]:
using GZip
filename = "my_type_vec.native"
out = GZip.open(filename, "w")
serialize(out, vec)
close(out)
Out[10]:
In [11]:
;ls
In [12]:
@assert isfile(filename)
input = GZip.open(filename, "r")
vec = deserialize(input)
close(input)
vec
Out[12]:
# maps contract id to a contract instance
const DictContract = Dict{Int, Contract}
# a subset of contracts mapped to a single file on disk
type Chunk
dt::Date # database base date
chunk_id::UInt16 # identifier for the chunk
is_loaded::Bool
d::DictContract
end
# database of contracts
# when created, loads contract_index from disk
type ContractDB
dt::Date # database base date
chunks::Dict{UInt16, Chunk} # chunk_id to chunk
contract_index::Dict{Int, UInt16} # contract id to chunk_id
end
type ParallelContractDB
db::ContractDB
worker_pids::Vector{Int} # bounded workers
chunk_to_worker_pids::Dict{UInt16, Int} # chunk_id to worker pid
worker_pids_to_chunks::Dict{Int, Vector{UInt16}} # worker pid to a vector of chunks
end
function price_all(pd::ParallelContractDB, dt::Date)
v_ids = Vector{Int}() # contract ids
v_mtms = Vector{Float64}() # price results
futures = Vector{Future}()
for p in pd.worker_pids
f = @spawnat p price_all_my_chunks(dt)
push!(futures, f)
end
for f in futures
v_ids_i, v_mtms_i = fetch(f)
append!(v_ids, v_ids_i)
append!(v_mtms, v_mtms_i)
end
return v_ids, v_mtms
end