# Python for Quant Finance¶

Conquering the Analytics Space

Dr. Yves J. Hilpisch | The Python Quants GmbH

PyData New York City, 22. November 2014

## The Python Language¶

Black-Scholes-Merton (1973) SDE of geometric Brownian motion. The "Hello World example of Quant Finance."

$$dS_t = rS_tdt + \sigma S_t dZ_t$$

Monte Carlo simulation: draw $I$ standard normally distributed random number $z_t^i$ and apply them to the following by Euler disctretization scheme to simulate $I$ end values of the GBM:

$$S_{T} = S_0 \exp \left(\left( r - \frac{1}{2} \sigma^2\right) T + \sigma \sqrt{T} z_T \right)$$

Latex description of Euler discretization.

S_T = S_0 \exp (( r - 0.5 \sigma^2 ) T + \sigma \sqrt{T} z_T)

Python implementation of algorithm.

In [61]:
from pylab import *
S_0 = 100.; r = 0.01; T = 0.5; sigma = 0.2
z_T = standard_normal(10000)

In [62]:
S_T = S_0 * exp((r - 0.5 * sigma ** 2) * T + sigma * sqrt(T) * z_T)


Again, Latex for comparison:

S_T = S_0 \exp (( r - 0.5 \sigma^2 ) T + \sigma \sqrt{T} z_T)

Interactive visualization of simulation results.

In [63]:
%matplotlib inline
pyfig = figure()
hist(S_T, bins=40);
grid()


## The Python Ecosystem¶

The Python ecosystem can be considered one of the major competitive advantages of the language.

• IPython (Notebook)
• NumPy (fast, vectorized array operations)
• SciPy (collection of scientific classes/functions)
• pandas (times series and tabular data)
• PyTables (hardware-bound IO operations)
• scikit-learn (machine learning algorithms)
• statsmodels (statistical classes/functions)
• xlwings (Python-Excel integration)

## Integration – No "Either Or" with Python

Python integrates pretty well with almost any other language used for scientific and financial computing.

• C/C++ (natively)
• Julia (IPython)
• JavaScript (IPython)
• R (IPython/rpy2)
• Matlab (NumPy)
• ...

### Example: Statistics with R¶

We analyze the statistical correlation between the EURO STOXX 50 stock index and the VSTOXX volatility index.

First, reading the EURO STOXX 50 & VSTOXX data.

In [68]:
import pandas as pd
es = pd.HDFStore('data/SX5E.h5', 'r')['SX5E']
vs = pd.HDFStore('data/V2TX.h5', 'r')['V2TX']

In [69]:
es['SX5E'].plot(figsize=(9, 6))

Out[69]:
<matplotlib.axes.AxesSubplot at 0x10f9d5f10>


Generating log returns with Python and pandas.

In [70]:
import numpy as np
# log returns for the major indices' time series data
datv = pd.DataFrame({'SX5E' : es['SX5E'], 'V2TX': vs['V2TX']}).dropna()
rets = np.log(datv / datv.shift(1)).dropna()
ES = rets['SX5E'].values
VS = rets['V2TX'].values


Bridging to R from within IPython Notebook and pushing Python data to the R run-time.

In [71]:
%load_ext rpy2.ipython

The rpy2.ipython extension is already loaded. To reload it, use:


In [72]:
%Rpush ES VS


Plotting with R in IPython Notebook.

In [73]:
%R plot(ES, VS, pch=19, col='blue'); grid(); title("Log returns ES50 & VSTOXX")


Linear regression with R.

In [74]:
%R c = coef(lm(VS~ES))

Out[74]:
<FloatVector - Python:0x115c647e8 / R:0x10bd931c8>
[-0.000074, -2.752754]

In [75]:
%R print(summary(lm(VS~ES)))


Call:
lm(formula = VS ~ ES)

Residuals:
Min       1Q   Median       3Q      Max
-0.32412 -0.02188 -0.00213  0.02015  0.53675

Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -7.416e-05  6.169e-04   -0.12    0.904
ES          -2.753e+00  4.078e-02  -67.50   <2e-16 ***
---
Signif. codes:  0 â€˜***â€™ 0.001 â€˜**â€™ 0.01 â€˜*â€™ 0.05 â€˜.â€™ 0.1 â€˜ â€™ 1

Residual standard error: 0.03905 on 4006 degrees of freedom
Multiple R-squared:  0.5321,	Adjusted R-squared:  0.532
F-statistic:  4556 on 1 and 4006 DF,  p-value: < 2.2e-16



Regression line visualized.

In [76]:
%R plot(ES, VS, pch=19, col='blue'); grid(); abline(c, col='red', lwd=5)


Pulling data from R to Python and using it.

In [77]:
%Rpull c

In [78]:
plt.figure(figsize=(9, 6))
plt.plot(ES, VS, 'b.')
plt.plot(ES, c[0] + c[1] * ES, 'r', lw=3)
plt.grid(); plt.xlabel('ES'); plt.ylabel('VS')

Out[78]:
<matplotlib.text.Text at 0x116005690>


If you want to have it nicer, interactive and embeddable anywhere – use plot.ly

In [79]:
import plotly.plotly as ply
ply.sign_in('yves', 'token')


Let us generate a plot with fewer data points.

In [80]:
pyfig = plt.figure(figsize=(9, 6)); n = 100
plt.plot(ES[:n], VS[:n], 'b.')
plt.plot(ES[:n], c[0] + c[1] * ES[:n], 'r', lw=3)
plt.grid(); plt.xlabel('ES'); plt.ylabel('VS')

Out[80]:
<matplotlib.text.Text at 0x11623f510>


Only single line of code needed to convert matplotlib plot into interactive D3 plot.

In [81]:
ply.iplot_mpl(pyfig)  # convert mpl plot into interactive D3

Out[81]:

### Example: Working with Julia¶

Julia is, for example, often faster for iterative function formulations. As an example, consider the Fibonacci sequence.

In [104]:
# quite slow in Python
def fib_rec(n):
if n < 2:
return n
else:
return fib_rec(n - 1) + fib_rec(n - 2)
%time fib_rec(35)

CPU times: user 2.87 s, sys: 29 ms, total: 2.9 s
Wall time: 2.85 s


Out[104]:
9227465

In [105]:
%%julia
# much faster in Julia
fib_rec(n) = n < 2 ? n : fib_rec(n - 1) + fib_rec(n - 2)
@elapsed fib_rec(35)

fib_rec (generic function with 1 method)
0.072275267



For comparison, an iterative function implementation.

In [106]:
# iterative version in Python
def fib_it(n):
x,y = 0, 1
for i in xrange(1, n + 1):
x, y = y, x + y
return x
%time fn = fib_it(1000000)  # with 1,000,000

CPU times: user 9.6 s, sys: 32 ms, total: 9.64 s
Wall time: 9.41 s


In [103]:
%%julia
# iterative version in Julia
function fib_it(n)
x, y = (0,1)
for i = 1:n
x, y = (y, x + y)
end
return x
end
fib_it(5)  # initial call
@elapsed fib_it(10000000)  # with 10,000,000

fib_it (generic function with 1 method)
5
0.005006169



For final comparison, the dynamically compiled Python version with Numba.

In [107]:
import numba
fib_nb = numba.jit(fib_it)
%timeit fib_nb(10000000)  # with 10,000,000

100 loops, best of 3: 4.91 ms per loop



## Performance – Numerical Algorithms

Finance algorithms are loop-heavy; Python loops are slow; Python is too slow for finance.

In [64]:
def counting_py(N):
s = 0
for i in xrange(N):
for j in xrange(N):
s += int(cos(log(1)))
return s

In [65]:
N = 2000
%time counting_py(N)
# memory efficient but slow

CPU times: user 10.7 s, sys: 782 ms, total: 11.5 s
Wall time: 10.9 s


Out[65]:
4000000


First approach: vectorization with NumPy.

In [66]:
%%time
arr = ones((N, N))
print int(sum(cos(log(arr))))

4000000
CPU times: user 109 ms, sys: 49.4 ms, total: 158 ms
Wall time: 201 ms


In [67]:
arr.nbytes # much faster but NOT memory efficient

Out[67]:
32000000


Second approach: dynamic compiling with Numba.

In [68]:
import numba
counting_nb = numba.jit(counting_py)

In [69]:
%time counting_nb(N)
# some overhead the first time

CPU times: user 177 ms, sys: 56.5 ms, total: 234 ms
Wall time: 367 ms


Out[69]:
4000000

In [70]:
%timeit counting_nb(N)
# even faster AND memory efficient

10 loops, best of 3: 56.7 ms per loop



## Performance – Hardware-bound IO

Hardware-bound IO operations are standard for Python.

In [71]:
%time one_gb = standard_normal((12500, 10000))
one_gb.nbytes
# a giga byte worth of data

CPU times: user 5.66 s, sys: 435 ms, total: 6.1 s
Wall time: 7.11 s


Out[71]:
1000000000

In [73]:
%time save('one_gb', one_gb)

CPU times: user 54.9 ms, sys: 1.77 s, total: 1.82 s
Wall time: 2.58 s


In [74]:
!ls -n one_gb*

-rw-r--r--  1 501  20  1000000080 21 Nov 20:50 one_gb.npy



## Python Quant Platform¶

Integrating it all and adding collaboration and scalability (http://quant-platform.com).

At the moment, the Python Quant Platform comprises the following components and features:

• IPython Notebook: interactive data and financial analytics in the browser with full Python integration and much more (cf. IPython home page).
• IPython Shell, Python Shell, System Shell: all you typically do on the (local or remote) system shell (Vim, Git, file operations, etc.)
• Anaconda Python Distribution: complete Python stack for financial, scientific and data analytics workflows/applications (cf. Anaconda page); you can easily switch between Python 2.7 and 3.4.
• R Stack: for statistical analyses, integrated via rpy2 and IPython Notebook
• Julia: the "high-level, high-performance dynamic programming language for technical computing"
• DX Analytics: our library for advanced financial and derivatives analytics with Python based on Monte Carlo simulation.
• File Manager: a GUI-based File Manager to upload, download, copy, remove, rename files on the platform.
• Chat/Forum: there is a simple chat/forum application available via which you can share thoughts, documents and more.
• Collaboration: the platform features user/group administration as well as file sharing via public folders.
• Linux Server: the platform is powered by Linux servers to which you have full shell access.
• Deployment: the platform is easily scalable since it is cloud-based and can also be easily deployed on your own servers (via Docker containers).

## DX Analytics¶

DX Analytics is a Python library for advanced derivatives and risk analytics. Just recently open sourced (cf. http://dx-analytics.com, cf. http://github.com/yhilpisch/dx)

### Why DX Analytics? A brief History.¶

Important research milestones (I), from a rather personal perspective.

• Bachelier (1900) – Option Pricing with Arithmetic Brownian Motion
• Einstein (1905) – Rigorous Theory of Brownian Motion
• Samuelson (1950s) – Systematic Approaches to Option Pricing
• Black Scholes Merton (1973) – Geometric Brownian Motion, Analytical Option Pricing Formula
• Merton (1976) – Jump Diffusion, Option Pricing with Jumps
• Boyle (1977) – Monte Carlo for European options
• Cox Ross Rubinstein (1979) – Binomial Option Pricing

Important research milestones (II), from a rather personal perspective.

• Harrison Kreps Pliska (1979/1981) – General Risk-Neutral Valuation, Fundamental Theorem of Asset Pricing
• Cox Ingersoll Ross (1985) – Intertemporal General Equilibrium, Short Rates with Square-Root Diffusion
• Heston (1993) – Stochastic Volatility, Fourier-based Option Pricing
• Bates (1996) – Heston (1993) plus Merton (1976)
• Bakshi Cao Chen (1997) – Bates (1996) plus Cox Ingersoll Ross (1985)
• Carr Madan (1999) – Using Fast Fourier Transforms for Option Pricing
• Longstaff Schwartz (2001) – Monte Carlo for American Options
• Gamba (2003) – Monte Carlo for (Complex) Portfolios of Interdependent Options

DX Analytics leverages the experience of using Python for derivatives analytics since about 10 years.

• Ph.D. research – felt in love with Harrison-Kreps-Pliska general valuation approach
• Visixion (The Python Quants) foundation in 2004 – first steps with Python & Monte Carlo simulation
• DEXISION prototyping from 2007 – using Python to build the first prototype
• DEXISION analytics suite from 2009 until today – using Python and Web technologies to provide analytics as a service
• Derivatives Analytics with Python 2011-2014 – writing a book based on university lectures and Python experience (upcoming at Wiley Finance)
• Python for Finance 2013-2014 – wiriting a book teaching Python for finance (explaining basic DX architecture)
• DX Analytics since QIV 2013 – putting all lessons learned together to write a new, concise Python library

You can register for a PQP trial (incl. DX Analytics) here http://trial.quant-platform.com.

### The Quickstart¶

This brief section illustrates---without much explanation---the usage of the DX Analytics library. It models two risk factors, two derivatives instruments and values these in a portfolio context.

In [1]:
import dx
import datetime as dt
import pandas as pd


The first step is to define a model for the risk-neutral discounting.

In [2]:
r = dx.constant_short_rate('r', 0.01)


We then define a market environment containing the major parameter specifications needed,

In [3]:
me_1 = dx.market_environment('me', dt.datetime(2015, 1, 1))

In [4]:
me_1.add_constant('initial_value', 100.)
# starting value of simulated processes
# volatiltiy factor
# horizon for simulation
# currency of instrument
# frequency for discretization
# number of paths
# number of paths


Next, the model object for the first risk factor, based on the geometric Brownian motion (Black-Scholes-Merton (1973) model).

In [5]:
gbm_1 = dx.geometric_brownian_motion('gbm_1', me_1)


Some paths visualized.

In [6]:
pdf = pd.DataFrame(gbm_1.get_instrument_values(), index=gbm_1.time_grid)

In [7]:
%matplotlib inline
pdf.ix[:, :10].plot(legend=False)

Out[7]:
<matplotlib.axes.AxesSubplot at 0x10ad71790>


Second risk factor with higher volatility. We overwrite the respective value in the market environment.

In [8]:
me_2 = dx.market_environment('me_2', me_1.pricing_date)

In [9]:
gbm_2 = dx.geometric_brownian_motion('gbm_2', me_2)

In [10]:
pdf = pd.DataFrame(gbm_2.get_instrument_values(), index=gbm_2.time_grid)

In [11]:
pdf.ix[:, :10].plot(legend=False)

Out[11]:
<matplotlib.axes.AxesSubplot at 0x10b132450>


Based on the risk factors, we can then define derivatives models for valuation. To this end, we need to add at least one (the maturity), in general two (maturity and strike), parameters to the market environments.

In [12]:
me_opt = dx.market_environment('me_opt', me_1.pricing_date)


The first derivative is an American put option on the first risk factor gbm_1.

In [13]:
am_put = dx.valuation_mcs_american_single(
name='am_put',
underlying=gbm_1,
mar_env=me_opt,
payoff_func='np.maximum(strike - instrument_values, 0)')


Let us calculate a Monte Carlo present value estimate and estimates for the major Greeks.

In [14]:
am_put.present_value()

Out[14]:
15.019

In [15]:
am_put.delta()

Out[15]:
-0.539

In [16]:
am_put.vega()

Out[16]:
57.1


The second derivative is a European call option on the second risk factor gbm_2.

In [17]:
eur_call = dx.valuation_mcs_european_single(
name='eur_call',
underlying=gbm_2,
mar_env=me_opt,
payoff_func='np.maximum(maturity_value - strike, 0)')


Valuation and Greek estimation for this option.

In [18]:
eur_call.present_value()

Out[18]:
20.659128

In [19]:
eur_call.delta()

Out[19]:
0.8459

In [20]:
eur_call.vega()

Out[20]:
48.3196


In a portfolio context, we need to add information about the model class(es) to be used to the market environments of the risk factors.

In [21]:
me_1.add_constant('model', 'gbm')


To compose a portfolio consisting of our just defined options, we need to define derivatives positions. Note that this step is independent from the risk factor model and option model definitions. We only use the market environment data and some additional information needed (e.g. payoff functions).

In [22]:
put = dx.derivatives_position(
name='put',
quantity=2,
underlyings=['gbm_1'],
mar_env=me_opt,
otype='American single',
payoff_func='np.maximum(strike - instrument_values, 0)')

In [23]:
call = dx.derivatives_position(
name='call',
quantity=3,
underlyings=['gbm_2'],
mar_env=me_opt,
otype='European single',
payoff_func='np.maximum(maturity_value - strike, 0)')


Let us define the relevant market by 2 Python dictionaries, the correlation between the two risk factors and a valuation environment.

In [24]:
risk_factors = {'gbm_1': me_1, 'gbm_2' : me_2}
correlations = [['gbm_1', 'gbm_2', -0.4]]
positions = {'put' : put, 'call' : call}

In [25]:
val_env = dx.market_environment('general', dt.datetime(2015, 1, 1))


These are used to define the derivatives portfolio.

In [26]:
port = dx.derivatives_portfolio(
name='portfolio',  # name
positions=positions,  # derivatives positions
val_env=val_env,  # valuation environment
risk_factors=risk_factors, # relevant risk factors
correlations=correlations)  # correlation between risk factors


Now, we can get the position values for the portfolio via the get_values method.

In [27]:
port.get_values()

Total
pos_value    92.868033
dtype: float64


Out[27]:
position name quantity otype risk_facts value currency pos_value
0 put put 2 American single [gbm_1] 15.000000 EUR 30.000000
1 call call 3 European single [gbm_2] 20.956011 EUR 62.868033

Via the get_statistics methods delta and vega values are provided as well.

In [28]:
port.get_statistics()

Totals
pos_value     92.8680
pos_delta      0.5512
pos_vega     235.4243
dtype: float64


Out[28]:
position name quantity otype risk_facts value currency pos_value pos_delta pos_vega
0 put put 2 American single [gbm_1] 15.000 EUR 30.000 -1.1780 92.6000
1 call call 3 European single [gbm_2] 20.956 EUR 62.868 1.7292 142.8243

Having modeled the derivatives portfolio, risk reports are only two method calls away (I).

In [29]:
deltas = port.get_port_risk(Greek='Delta')

In [30]:
dx.risk_report(deltas)


gbm_1_Delta
0.8   0.9     1.0     1.1     1.2
factor  80.00  90.0  100.00  110.00  120.00
value   51.25  42.8   35.96   30.72   26.99

gbm_2_Delta
0.8    0.9     1.0     1.1     1.2
factor  80.00  90.00  100.00  110.00  120.00
value   25.96  30.59   35.96   41.96   48.52



Having modeled the derivatives portfolio, risk reports are only two method calls away (II).

In [31]:
vegas = port.get_port_risk(Greek='Vega', step=0.05)

In [32]:
dx.risk_report(vegas)


gbm_1_Vega
0.80   0.85   0.90   0.95   1.00   1.05   1.10   1.15   1.20
factor   0.16   0.17   0.18   0.19   0.20   0.21   0.22   0.23   0.24
value   34.15  34.60  35.05  35.50  35.96  36.42  36.89  37.35  37.81

gbm_2_Vega
0.80   0.85   0.90   0.95   1.00   1.05   1.10   1.15   1.20
factor   0.40   0.43   0.45   0.48   0.50   0.53   0.55   0.58   0.60
value   31.15  32.36  33.56  34.76  35.96  37.14  38.33  39.50  40.68



## A Realistic Example with DX Analytics¶

Bringing back office simulation and risk management practices to front office analytics.

The following more realistic example illustrates that you can model, value and risk manage quite complex derivatives portfolios with DX Analytics. The example has the following characteristics:

• portfolio over 2 years
• stochastic short rate for risk-neutral discounting
• 250 risk factors (gbm, jump diffusion, stochastic volatility)
• 1,000 derivatives (call and put options, European and American exercise, random maturites)
• monthly frequency for discretization
• 1,000 paths for simulation
In [33]:
from dx import *
np.random.seed(10000)


### Stochastic Short Rates¶

Let us start by defining a stochastic discounting object (based on CIR square-root diffusion process).

In [34]:
mer = market_environment(name='me', pricing_date=dt.datetime(2015, 1, 1))
mer.add_constant('final_date', dt.datetime(2015, 12, 31)) # dummy
ssr = stochastic_short_rate('ssr', mer)


Some simulated short rate paths visualized.

In [35]:
plt.figure(figsize=(9, 5))
plt.plot(ssr.process.time_grid, ssr.process.get_instrument_values()[:, :10]);
plt.gcf().autofmt_xdate(); plt.grid()


### Multiple Risk Factors¶

The example is based on a multiple, correlated risk factors (based on geomtetric Brownian motion, jump diffusion or stachastic volatility models). The basic assumptions.

In [36]:
# market environments
me = market_environment('gbm', dt.datetime(2015, 1, 1))

In [37]:
# geometric Brownian motion


In addition to the input parameters of the geometric Brownian motion, we also need the following for the jump diffusions and stochastic volatility models.

In [38]:
# jump diffusion

In [39]:
# stochastic volatility


For the portfolio valuation we also need a valuation environment.

In [40]:
# valuation environment
val_env = market_environment('val_env', dt.datetime(2015, 1, 1))

In [41]:
# add valuation environment to market environments


We generate a large number of risk factors (with some random parameter values).

In [42]:
no = 250
risk_factors = {}
for rf in range(no):
# random model choice
sm = np.random.choice(['gbm', 'jd', 'sv'])
key = '%3d_%s' % (rf + 1, sm)
risk_factors[key] = market_environment(key, me.pricing_date)
# random initial_value
np.random.random() * 40. + 20.)
np.random.random() * 0.6 + 0.05)
# the simulation model to choose


Correlations are also randomly chosen.

In [43]:
correlations = []
keys = sorted(risk_factors.keys())
for key in keys[1:]:
correlations.append([keys[0], key, np.random.choice([-0.05, 0.0, 0.05])])
correlations[:3]

Out[43]:
[['  1_jd', '  2_jd', -0.050000000000000003],
['  1_jd', '  3_sv', 0.050000000000000003],
['  1_jd', '  4_sv', -0.050000000000000003]]


### Options Modeling¶

We model a certain number of derivative instruments with the following major assumptions.

In [44]:
me_option = market_environment('option', me.pricing_date)
# choose from a set of maturity dates (month ends)
maturities = pd.date_range(start=me.pricing_date,
end=val_env.get_constant('final_date'),
freq='M').to_pydatetime()


### Portfolio Modeling¶

The derivatives_portfolio object we compose consists of a large number derivatives positions. Each option differs with respect to the strike and the risk factor it is dependent on.

In [45]:
positions = {}
for i in range(4 * no):
ot = np.random.choice(['am_put', 'eur_call'])
if ot == 'am_put':
otype = 'American single'
payoff_func = 'np.maximum(%5.3f - instrument_values, 0)'
else:
otype = 'European single'
payoff_func = 'np.maximum(maturity_value - %5.3f, 0)'
# random strike
strike = np.random.randint(36, 40)
underlying = sorted(risk_factors.keys())[(i + no) % no]
positions[i] = derivatives_position(
name='option_pos_%d' % strike,
quantity=np.random.randint(1, 10),
underlyings=[underlying],
mar_env=me_option,
otype=otype,
payoff_func=payoff_func % strike)

In [46]:
# number of derivivatives positions
len(positions)

Out[46]:
1000


### Portfolio Valuation¶

All is together to define the derivatives portfolio.

In [47]:
port_sequ = derivatives_portfolio(
name='portfolio',
positions=positions,
val_env=val_env,
risk_factors=risk_factors,
correlations=correlations,
parallel=False)  # sequential calculation


The correlation matrix illstrates the market complexity.

In [48]:
port_sequ.val_env.get_list('correlation_matrix')

Out[48]:
1_jd 2_jd 3_sv 4_sv 5_gbm 6_gbm 7_jd 8_gbm 9_sv 10_sv ... 241_jd 242_sv 243_sv 244_sv 245_gbm 246_jd 247_jd 248_sv 249_sv 250_jd
1_jd 1.00 -0.05 0.05 -0.05 -0.05 0.05 -0.05 -0.05 0.05 0.05 ... -0.05 0.05 0.05 -0.05 0.05 0 0.05 0.05 0 0
2_jd -0.05 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
3_sv 0.05 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
4_sv -0.05 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
5_gbm -0.05 0.00 0.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
6_gbm 0.05 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
7_jd -0.05 0.00 0.00 0.00 0.00 0.00 1.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
8_gbm -0.05 0.00 0.00 0.00 0.00 0.00 0.00 1.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
9_sv 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 1.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
10_sv 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 1.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
11_jd 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
12_jd 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
13_jd 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
14_sv 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
15_sv 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
16_jd 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
17_jd -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
18_sv -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
19_sv 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
20_jd -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
21_sv 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
22_gbm -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
23_gbm -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
24_sv -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
25_sv 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
26_jd 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
27_gbm 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
28_gbm -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
29_sv 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
30_sv -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
221_jd 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
222_sv 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
223_gbm 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
224_jd -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
225_jd -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
226_sv -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
227_sv 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
228_jd 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
229_sv 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
230_sv -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
231_gbm 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
232_sv 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
233_gbm 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
234_jd 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
235_gbm -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
236_gbm -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
237_jd -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
238_gbm 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
239_jd 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
240_gbm 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
241_jd -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 1.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 0
242_sv 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 1.00 0.00 0.00 0.00 0 0.00 0.00 0 0
243_sv 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 1.00 0.00 0.00 0 0.00 0.00 0 0
244_sv -0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 1.00 0.00 0 0.00 0.00 0 0
245_gbm 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 1.00 0 0.00 0.00 0 0
246_jd 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 1 0.00 0.00 0 0
247_jd 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 1.00 0.00 0 0
248_sv 0.05 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 1.00 0 0
249_sv 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 1 0
250_jd 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 ... 0.00 0.00 0.00 0.00 0.00 0 0.00 0.00 0 1

250 rows Ã— 250 columns

The call of the get_values method to value all instruments.

In [49]:
%time res = port_sequ.get_statistics(fixed_seed=True)

Totals
pos_value    47818.6880
pos_delta      633.1349
pos_vega     45994.4110
dtype: float64
CPU times: user 16min 58s, sys: 1min 31s, total: 18min 29s
Wall time: 9min 33s



The resulting table with the results.

In [50]:
res.set_index('position', inplace=False)

Out[50]:
name quantity otype risk_facts value currency pos_value pos_delta pos_vega
position
0 option_pos_36 1 American single [ 1_jd] 14.017 EUR 14.017 -0.6000 14.8000
1 option_pos_37 3 European single [ 2_jd] 7.614 EUR 22.842 1.8135 42.2193
2 option_pos_39 8 American single [ 3_sv] 5.553 EUR 44.424 -1.2128 36.2088
3 option_pos_36 3 American single [ 4_sv] 9.766 EUR 29.298 -1.4400 18.0000
4 option_pos_37 1 American single [ 5_gbm] 5.145 EUR 5.145 -0.3270 20.5000
5 option_pos_36 5 European single [ 6_gbm] 7.660 EUR 38.300 3.1240 90.8225
6 option_pos_38 8 European single [ 7_jd] 7.889 EUR 63.112 5.3640 119.1528
7 option_pos_37 5 American single [ 8_gbm] 7.483 EUR 37.415 -1.4570 84.5000
8 option_pos_36 8 European single [ 9_sv] 1.524 EUR 12.192 2.2024 11.8104
9 option_pos_37 1 American single [ 10_sv] 7.063 EUR 7.063 -0.2703 -0.9000
10 option_pos_39 2 European single [ 11_jd] 7.743 EUR 15.486 1.2608 33.7394
11 option_pos_38 2 European single [ 12_jd] 19.152 EUR 38.304 1.8222 5.5712
12 option_pos_36 2 European single [ 13_jd] 8.983 EUR 17.966 1.2834 32.6318
13 option_pos_38 4 American single [ 14_sv] 12.286 EUR 49.144 -1.3168 11.6000
14 option_pos_39 4 American single [ 15_sv] 8.352 EUR 33.408 -1.1064 7.6000
15 option_pos_37 9 European single [ 16_jd] 24.621 EUR 221.589 7.2540 144.0027
16 option_pos_39 4 American single [ 17_jd] 10.497 EUR 41.988 -1.0556 77.2368
17 option_pos_39 9 European single [ 18_sv] 17.148 EUR 154.332 7.0758 19.3392
18 option_pos_39 2 American single [ 19_sv] 5.067 EUR 10.134 -0.2970 9.1704
19 option_pos_38 4 European single [ 20_jd] 19.272 EUR 77.088 3.4180 40.5884
20 option_pos_36 5 European single [ 21_sv] 25.646 EUR 128.230 4.4240 3.1845
21 option_pos_38 2 American single [ 22_gbm] 8.404 EUR 16.808 -0.2818 42.7032
22 option_pos_36 7 American single [ 23_gbm] 0.000 EUR 0.000 0.0000 0.0000
23 option_pos_37 4 European single [ 24_sv] 1.920 EUR 7.680 1.2920 1.4212
24 option_pos_38 9 American single [ 25_sv] 6.587 EUR 59.283 -2.1663 18.0000
25 option_pos_38 2 European single [ 26_jd] 4.173 EUR 8.346 1.3576 13.6942
26 option_pos_37 8 American single [ 27_gbm] 7.373 EUR 58.984 -6.5768 139.2000
27 option_pos_39 4 American single [ 28_gbm] 13.565 EUR 54.260 -2.6512 59.6000
28 option_pos_37 3 European single [ 29_sv] 24.181 EUR 72.543 2.5482 8.4126
29 option_pos_39 9 European single [ 30_sv] 0.974 EUR 8.766 1.6641 7.1325
... ... ... ... ... ... ... ... ... ...
970 option_pos_39 5 American single [221_jd] 8.319 EUR 41.595 -1.5370 82.5000
971 option_pos_37 7 American single [222_sv] 5.185 EUR 36.295 -0.9555 15.4000
972 option_pos_38 3 European single [223_gbm] 12.073 EUR 36.219 2.3721 54.3414
973 option_pos_39 9 European single [224_jd] 2.608 EUR 23.472 3.3786 97.9479
974 option_pos_36 1 American single [225_jd] 8.337 EUR 8.337 -0.4035 14.4000
975 option_pos_39 3 American single [226_sv] 16.553 EUR 49.659 -0.6471 -18.6000
976 option_pos_37 4 American single [227_sv] 5.536 EUR 22.144 -0.7632 18.4000
977 option_pos_38 6 American single [228_jd] 6.633 EUR 39.798 -1.7490 79.2000
978 option_pos_38 3 American single [229_sv] 5.359 EUR 16.077 -0.6081 5.0481
979 option_pos_39 9 European single [230_sv] 7.564 EUR 68.076 5.4657 27.8739
980 option_pos_38 8 European single [231_gbm] 12.054 EUR 96.432 7.3864 84.1928
981 option_pos_36 4 American single [232_sv] 3.858 EUR 15.432 -0.8972 14.0000
982 option_pos_39 8 American single [233_gbm] 6.920 EUR 55.360 -3.4248 176.8000
983 option_pos_38 7 American single [234_jd] 18.878 EUR 132.146 -4.9469 39.2000
984 option_pos_37 9 European single [235_gbm] 17.168 EUR 154.512 8.9217 11.8746
985 option_pos_38 5 American single [236_gbm] 18.892 EUR 94.460 -2.4340 74.6360
986 option_pos_38 3 European single [237_jd] 9.499 EUR 28.497 2.1438 36.3903
987 option_pos_36 4 American single [238_gbm] 0.068 EUR 0.272 -0.0504 15.2000
988 option_pos_38 8 American single [239_jd] 1.927 EUR 15.416 -0.2880 50.4000
989 option_pos_37 3 European single [240_gbm] 11.243 EUR 33.729 2.1039 59.7411
990 option_pos_36 7 European single [241_jd] 5.854 EUR 40.978 4.9770 59.3271
991 option_pos_37 6 American single [242_sv] 17.708 EUR 106.248 -3.1770 -26.4000
992 option_pos_38 4 European single [243_sv] 9.336 EUR 37.344 2.5724 13.1680
993 option_pos_39 9 European single [244_sv] 8.799 EUR 79.191 5.7591 15.1164
994 option_pos_37 7 European single [245_gbm] 17.538 EUR 122.766 5.5377 149.1252
995 option_pos_36 9 American single [246_jd] 1.896 EUR 17.064 -1.0944 134.1000
996 option_pos_37 9 European single [247_jd] 12.250 EUR 110.250 6.3801 148.0761
997 option_pos_37 5 American single [248_sv] 4.309 EUR 21.545 -0.9470 9.5000
998 option_pos_37 3 European single [249_sv] 4.663 EUR 13.989 1.5225 8.1852
999 option_pos_37 8 American single [250_jd] 10.327 EUR 82.616 -2.0448 109.2584

1000 rows Ã— 9 columns

### Risk Analysis¶

Full distribution of portfolio present values illustrated via histogram.

In [51]:
%time pvs = port_sequ.get_present_values()

CPU times: user 4min 22s, sys: 15.8 s, total: 4min 38s
Wall time: 2min 23s


In [52]:
plt.figure(figsize=(9, 6)); plt.hist(pvs, bins=30);
plt.xlabel('portfolio present values');plt.ylabel('frequency'); plt.grid()


Some statistics via pandas.

In [53]:
pdf = pd.DataFrame(pvs, columns=['values'])
pdf.describe()

Out[53]:
values
count 1000.000000
mean 47858.798330
std 3885.104955
min 38795.659239
25% 45115.795320
50% 47705.269666
75% 50224.427169
max 65496.998373

The delta risk (sensitivities) report.

In [54]:
%%time
deltas = port_sequ.get_port_risk(Greek='Delta', fixed_seed=True, step=0.2,
risk_factors=risk_factors.keys()[:4])
risk_report(deltas)


39_jd_Delta
0.8      1.0      1.2
factor    37.52    46.91    56.29
value   9482.22  9508.99  9537.94

107_sv_Delta
0.8      1.0      1.2
factor    24.43    30.54    36.65
value   9513.48  9504.40  9499.51

144_sv_Delta
0.8     1.0      1.2
factor    21.36    26.7    32.04
value   9502.22  9504.4  9509.62

246_jd_Delta
0.8      1.0      1.2
factor    44.73    55.91    67.09
value   9504.39  9507.45  9515.13
CPU times: user 55.9 s, sys: 473 ms, total: 56.4 s
Wall time: 54.4 s



The vega risk (sensitivities) report.

In [55]:
%%time
vegas = port_sequ.get_port_risk(Greek='Vega', fixed_seed=True, step=0.2,
risk_factors=risk_factors.keys()[:4])
risk_report(vegas)


39_jd_Vega
0.8      1.0      1.2
factor     0.49     0.61     0.73
value   9499.56  9507.45  9514.95

107_sv_Vega
0.8      1.0      1.2
factor     0.28     0.35     0.42
value   9506.97  9507.45  9508.34

144_sv_Vega
0.8      1.0      1.2
factor     0.50     0.62     0.75
value   9506.33  9507.45  9508.86

246_jd_Vega
0.8      1.0      1.2
factor     0.21     0.26     0.32
value   9505.67  9507.45  9510.28
CPU times: user 56.8 s, sys: 520 ms, total: 57.3 s
Wall time: 55.3 s



### Visualization of Selected Results¶

Selected results visualized.

In [56]:
res[['pos_value', 'pos_delta', 'pos_vega']].hist(bins=30, figsize=(9, 6))
plt.ylabel('frequency')

Out[56]:
<matplotlib.text.Text at 0x117bfbc90>


Sample paths for three underlyings.

In [57]:
paths_0 = port_sequ.underlying_objects.values()[0]
paths_0.generate_paths()
paths_1 = port_sequ.underlying_objects.values()[1]
paths_1.generate_paths()
paths_2 = port_sequ.underlying_objects.values()[2]
paths_2.generate_paths()


An the resulting plot.

In [58]:
pa = 5; plt.figure(figsize=(10, 6))
plt.plot(port_sequ.time_grid, paths_0.instrument_values[:, :pa], 'b');
plt.plot(port_sequ.time_grid, paths_1.instrument_values[:, :pa], 'r.-');
plt.plot(port_sequ.time_grid, paths_2.instrument_values[:, :pa], 'g-.', lw=2.5);
print 'Paths for %s (blue)' % paths_0.name
print 'Paths for %s (red)' % paths_1.name
print 'Paths for %s (green)' % paths_2.name; plt.grid()
plt.ylabel('risk factor level'); plt.gcf().autofmt_xdate()

Paths for 107_sv (blue)
Paths for 144_sv (red)
Paths for  39_jd (green)



## Books¶

By others:

• Python for Financial Modelling @ Wiley Finance (2009)
• Python for Finance @ Packt Publishing (2014)

By myself:

• Python for Finance – Analyze Big Financial Data @ O'Reilly (2014)
• Derivatives Analytics with Python @ Wiley Finance (2015)

Available as ebook and from December 2015 as print version.

Forthcoming 2015 at Wiley Finance ...

## For Python Quants Conference¶

• 1st conference in New York City on 14. March 2014
• 2nd conference in London on 28. November 2014
• 3rd conference planned for QI 2015 in Asia (eg Shanghai)

http://quant-platform.com/conf/

In [59]:
from IPython.display import HTML
HTML('<iframe src="http://forpythonquants.com" \
width=100% height=650></iframe>')

Out[59]:

## Final Thoughts¶

My wish for Python in the future: to become THE glue language and platform for

• data analytics
• financial analytics (derivatives, risk)
• development efforts in general
• performance technologies
• science and the technology world
• ...