Python Quant Platform

User Administration and Collaboration

In [1]:
from DX_connect import *
In [2]:
print info()

Dexision user id: 1826
Username:		  yhilpisch
Company:		  pythonquants
User directory:   /srv/www/hidden/notebooks/pythonquants/yhilpisch

In [3]:
import os
os.environ['DX_USER']
Out[3]:
'1826'

Doing Financial Analytics

On the Python Quant Platform you can do any kind of financial analytics with Python.

In [4]:
import pandas.io.data as web
In [5]:
AAPL = web.DataReader('AAPL', data_source='google')
In [6]:
%matplotlib inline
AAPL['Close'].plot()
Out[6]:
<matplotlib.axes.AxesSubplot at 0x256a2d0>

Simple Example with DEXISION

Currently you implement many different applications by using the DEXISION Web service for Rapid Financial Engineering and for pretty complex Derivatives Analytics tasks.

A full access to the DEXISION Python objects from IPython Notebook is planned.

The following uses a portfolio modelled in DEXISION to implement different option and portfolio valuations. The portfolio consists of a put and a call option on the DAX index.

Getting Results from DEXISION

In [7]:
import urllib
from IPython.display import HTML
In [8]:
# DEXISION Web service URL for example portfolio
url1 = "http://analytics.dexision.com/DEXISIONeval.py?"
url2 = "user=yhilpisch&company=pythonquants&pwd=xyz"
url3 = "&portfolio=dax_put_call/portfolio"
url4 = "&strike=%s&format=%s"
dx_service = url1 + url2 + url3 + url4
In [9]:
dx_connect = urllib.urlopen(dx_service % (10000, 'text')).read()
In [10]:
HTML(dx_connect)
Out[10]:
portfolioportfolio721.110672745
optioncall_option146.834404199
stockdax9622.48358507
optionput_option574.276268546
stockdax9622.48358507

Parsing and Using XML Output

In [11]:
import xml.dom.minidom as md
In [12]:
dx_connect = urllib.urlopen(dx_service % (10000, 'xml')).read()
In [13]:
results = md.parseString(dx_connect)
portfolio = results.getElementsByTagName("portfolio")[0]
name = portfolio.getAttribute("name")
value = portfolio.getAttribute("value")
In [14]:
print "Value of %s is %.3f." % (name, float(value))
Value of portfolio is 710.563.

In [15]:
%%time
valuation_results = []
for strike in range(9800, 10201, 100):
    dx_connect = urllib.urlopen(dx_service % (strike, 'xml')).read()
    results = md.parseString(dx_connect)
    options = results.getElementsByTagName("option")
    for option in options:
        valuation_results.append({
                    'name' : option.getAttribute("name"),
                    'strike' : strike,
                    'value' : float(option.getAttribute("value"))})
CPU times: user 92 ms, sys: 10 ms, total: 102 ms
Wall time: 3.91 s

In [16]:
valuation_results
Out[16]:
[{'name': u'call_option', 'strike': 9800, 'value': 211.172113716},
 {'name': u'put_option', 'strike': 9800, 'value': 438.954309996},
 {'name': u'call_option', 'strike': 9900, 'value': 175.53891501},
 {'name': u'put_option', 'strike': 9900, 'value': 503.439636473},
 {'name': u'call_option', 'strike': 10000, 'value': 146.677869845},
 {'name': u'put_option', 'strike': 10000, 'value': 574.368703205},
 {'name': u'call_option', 'strike': 10100, 'value': 120.820959131},
 {'name': u'put_option', 'strike': 10100, 'value': 647.979275049},
 {'name': u'call_option', 'strike': 10200, 'value': 96.0810472709},
 {'name': u'put_option', 'strike': 10200, 'value': 723.737079842}]
In [17]:
print "       option  |   strike |    value"
print 36 * "-"
for result in valuation_results:
    print "%14s | %8d | %8.3f" % (result['name'], result['strike'], result['value'])
    
       option  |   strike |    value
------------------------------------
   call_option |     9800 |  211.172
    put_option |     9800 |  438.954
   call_option |     9900 |  175.539
    put_option |     9900 |  503.440
   call_option |    10000 |  146.678
    put_option |    10000 |  574.369
   call_option |    10100 |  120.821
    put_option |    10100 |  647.979
   call_option |    10200 |   96.081
    put_option |    10200 |  723.737

pandas for Results Analysis

In [18]:
import pandas as pd
In [19]:
results = pd.DataFrame(valuation_results)
results
Out[19]:
name strike value
0 call_option 9800 211.172114
1 put_option 9800 438.954310
2 call_option 9900 175.538915
3 put_option 9900 503.439636
4 call_option 10000 146.677870
5 put_option 10000 574.368703
6 call_option 10100 120.820959
7 put_option 10100 647.979275
8 call_option 10200 96.081047
9 put_option 10200 723.737080

10 rows × 3 columns

In [20]:
grouped = results.groupby(['name', 'strike']).sum()
grouped
Out[20]:
value
name strike
call_option 9800 211.172114
9900 175.538915
10000 146.677870
10100 120.820959
10200 96.081047
put_option 9800 438.954310
9900 503.439636
10000 574.368703
10100 647.979275
10200 723.737080

10 rows × 1 columns

In [21]:
grouped.loc['call_option'].plot(title='call_option')
grouped.loc['put_option'].plot(title='put_option')
Out[21]:
<matplotlib.axes.AxesSubplot at 0x7f2444b36710>

More Complex Example with DEXISION

This example uses the sample portfolio in DEXISION but allows for a more flexible parametrization. In addition, the main functionality is embedded in reusable Python functions.

In [22]:
#
# Invoking DEXISION from IPython
#
# (c) The Python Quants GmbH
# May 2014
#
from datetime import datetime
import time
from urllib2 import *
import xml.dom.minidom as minidom

Function to get data in XML format from the DEXISION Web service.

In [23]:
def get_data(I, M, initial_value, strike, dd, mm, yyyy):
    ''' Establishing connection to DEXISION Web service. '''
    url1 = "http://analytics.dexision.com/DEXISIONeval.py?"
    url2 = "user=yhilpisch&company=pythonquants&pwd=xyz"
    url3 = "&paths=%s&steps=%s" % (I, M)
    url4 = "&portfolio=dax_put_call/portfolio"
    url5 = "&initial_value=%s" % initial_value
    url5 = "&format=xml&strike=%s&dd=%s&mm=%s&yyyy=%s" % (strike, dd, mm, yyyy)
    req = Request(url1 + url2 + url3 + url4 + url5)
    try:
        connection = urlopen(req)
        content = connection.read()
        return content
    except:
        return False

Function to parse data from the Web service.

In [24]:
def parse_data(data):
    ''' Parses data given back by DEXISION in XML format. '''
    dom = minidom.parseString(data)
    port1 = dom.getElementsByTagName("portfolio")[0]
    port2 = dom.getElementsByTagName("stock")[0]
    value = port1.getAttribute("value")
    name = port1.getAttribute("name")
    return value, name

Main valuation routine.

In [25]:
def valuation(initial_value=9556.02):
    ''' Main Routine that implements the valuation loop. '''
    I = 10000  # simulation paths
    M = 50  # time steps
    z = 0   # option counter
    for strike in range(9750, 10250, 50):  # Strike Values
        print 96 * "-"
        for dd, mm, yyyy in ((30, 6, 2014), (30, 9, 2014)):  # Exercise Dates
            data = get_data(str(I), str(M), str(initial_value), str(strike),
                            str(dd), str(mm), str(yyyy))
            if data is False:
                print "Connection failed. Try again in 5 Sec."
            else:
                value, name = parse_data(data)
                print ("Time %s |Value of %13s: %8.3f |"
                       + "DAX %s - K %5d - T %s") \
                      % (datetime.now().replace(microsecond=0),
                         name, float(value), initial_value,
                         strike, datetime(yyyy, mm, dd).date())
            z += 1
    return z

And the valuation itself.

In [26]:
%time valuation()
------------------------------------------------------------------------------------------------
Time 2014-05-11 15:35:02 |Value of     portfolio:  632.355 |DAX 9556.02 - K  9750 - T 2014-06-30
Time 2014-05-11 15:35:02 |Value of     portfolio: 1017.695 |DAX 9556.02 - K  9750 - T 2014-09-30
------------------------------------------------------------------------------------------------
Time 2014-05-11 15:35:03 |Value of     portfolio:  660.872 |DAX 9556.02 - K  9800 - T 2014-06-30
Time 2014-05-11 15:35:03 |Value of     portfolio: 1017.125 |DAX 9556.02 - K  9800 - T 2014-09-30
------------------------------------------------------------------------------------------------
Time 2014-05-11 15:35:04 |Value of     portfolio:  659.092 |DAX 9556.02 - K  9850 - T 2014-06-30
Time 2014-05-11 15:35:05 |Value of     portfolio: 1011.993 |DAX 9556.02 - K  9850 - T 2014-09-30
------------------------------------------------------------------------------------------------
Time 2014-05-11 15:35:05 |Value of     portfolio:  683.143 |DAX 9556.02 - K  9900 - T 2014-06-30
Time 2014-05-11 15:35:06 |Value of     portfolio: 1029.035 |DAX 9556.02 - K  9900 - T 2014-09-30
------------------------------------------------------------------------------------------------
Time 2014-05-11 15:35:07 |Value of     portfolio:  687.386 |DAX 9556.02 - K  9950 - T 2014-06-30
Time 2014-05-11 15:35:08 |Value of     portfolio: 1040.883 |DAX 9556.02 - K  9950 - T 2014-09-30
------------------------------------------------------------------------------------------------
Time 2014-05-11 15:35:08 |Value of     portfolio:  707.173 |DAX 9556.02 - K 10000 - T 2014-06-30
Time 2014-05-11 15:35:09 |Value of     portfolio: 1074.312 |DAX 9556.02 - K 10000 - T 2014-09-30
------------------------------------------------------------------------------------------------
Time 2014-05-11 15:35:09 |Value of     portfolio:  737.954 |DAX 9556.02 - K 10050 - T 2014-06-30
Time 2014-05-11 15:35:10 |Value of     portfolio: 1069.570 |DAX 9556.02 - K 10050 - T 2014-09-30
------------------------------------------------------------------------------------------------
Time 2014-05-11 15:35:10 |Value of     portfolio:  764.444 |DAX 9556.02 - K 10100 - T 2014-06-30
Time 2014-05-11 15:35:11 |Value of     portfolio: 1069.544 |DAX 9556.02 - K 10100 - T 2014-09-30
------------------------------------------------------------------------------------------------
Time 2014-05-11 15:35:12 |Value of     portfolio:  793.602 |DAX 9556.02 - K 10150 - T 2014-06-30
Time 2014-05-11 15:35:13 |Value of     portfolio: 1103.828 |DAX 9556.02 - K 10150 - T 2014-09-30
------------------------------------------------------------------------------------------------
Time 2014-05-11 15:35:13 |Value of     portfolio:  817.212 |DAX 9556.02 - K 10200 - T 2014-06-30
Time 2014-05-11 15:35:14 |Value of     portfolio: 1118.565 |DAX 9556.02 - K 10200 - T 2014-09-30
CPU times: user 483 ms, sys: 22 ms, total: 505 ms
Wall time: 12.3 s

Out[26]:
20

Distributed Computing

The example is based on a Monte Carlo simulation algorithm.

In [27]:
def bsm_mcs_value(S0):
    ''' Dynamic Black-Scholes-Merton MCS Estimator for European Calls. '''
    import numpy as np
    K = 100.; T = 1.0; r = 0.05; vola = 0.2
    M = 50; I = 50000
    dt = T / M
    rand = np.random.standard_normal((M + 1, I))
    S = np.zeros((M + 1, I)); S[0] = S0
    for t in range(1, M + 1):
        S[t] = S[t-1] * np.exp((r - 0.5 * vola ** 2) * dt + vola * np.sqrt(dt) * rand[t])
    option_value = np.sum(np.maximum(S[-1] - K, 0)) / I
    return option_value

The benchmark is the sequential calculation.

In [28]:
import numpy as np
def seq_value(n):
    option_values = []
    for S in np.linspace(80, 100, n):
        option_values.append(bsm_mcs_value(S))
    return np.array(option_values)
In [29]:
n = 25
%time seq_values = seq_value(n)
CPU times: user 10.6 s, sys: 393 ms, total: 11 s
Wall time: 11 s

In [30]:
seq_values.round(3)
Out[30]:
array([  1.96 ,   2.113,   2.407,   2.556,   2.783,   3.021,   3.341,
         3.674,   3.962,   4.244,   4.609,   4.979,   5.342,   5.651,
         6.197,   6.486,   6.998,   7.417,   7.849,   8.324,   8.861,
         9.284,  10.003,  10.418,  10.88 ])

Setting up IPython.parallel.

In [31]:
from IPython.parallel import Client
c = Client(profile="default")
view = c.load_balanced_view()

The parallel valuation function.

In [32]:
def par_value(n):
    option_values = []
    for s0 in np.linspace(80, 100, n):
        value = view.apply_async(bsm_mcs_value, s0)
        # asynchronously calculate the option values
        option_values.append(value)
    c.wait(option_values)
    return option_values
In [33]:
%time par_values = par_value(n)
CPU times: user 455 ms, sys: 41 ms, total: 496 ms
Wall time: 3.31 s

In [34]:
results = np.array(map(lambda x: x.result, par_values))
results.round(3)
Out[34]:
array([  1.963,   2.125,   2.367,   2.623,   2.884,   3.108,   3.364,
         3.705,   4.012,   4.253,   4.625,   5.008,   5.405,   5.835,
         6.096,   6.678,   6.944,   7.44 ,   7.893,   8.309,   8.835,
         9.356,   9.896,  10.403,  11.029])

Rapid App Development with Widgets

In [35]:
from IPython.html.widgets import *
In [36]:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from pylab import cm
%matplotlib inline
In [37]:
class call_option(object):
    from math import log, sqrt, exp
    from scipy import stats
    global log, sqrt, exp, stats
    
    def __init__(self, S0, K, T, r, sigma):
        self.S0 = float(S0)
        self.K = K
        self.T = T
        self.r = r
        self.sigma = sigma
    
    def value(self):
        ''' Return option value. '''
        d1 = ((log(self.S0 / self.K) + (self.r + 0.5 * self.sigma ** 2) * self.T)
                / (self.sigma * sqrt(self.T)))
        d2 = ((log(self.S0 / self.K) + (self.r - 0.5 * self.sigma ** 2) * self.T)
                / (self.sigma * sqrt(self.T)))
        value = (self.S0 * stats.norm.cdf(d1, 0.0, 1.0)
                - self.K * exp(-self.r * self.T) * stats.norm.cdf(d2, 0.0, 1.0))
        return value
    
    def vega(self):
        ''' Return Vega of option. '''
        d1 = ((log(self.S0 / self.K)
            + (self.r + (0.5 * self.sigma ** 2)) * self.T)
            / (self.sigma * sqrt(self.T)))
        vega = self.S0 * stats.norm.cdf(d1, 0.0, 1.0) * sqrt(self.T)
        return vega
In [38]:
def vega_calculation(S0=100, T=1.0, r=0.01, sigma=0.2, n=10):
    np.set_printoptions(formatter={'all' : 
                        lambda x: '%6.2f' % x})
    o = call_option(S0, 100, T, r, sigma)
    strikes = np.linspace(80, 120, n)
    vegas = []
    for k in strikes:
        o.K = k
        vegas.append(o.vega())
    print "Strike:", strikes.round(3)
    print "Vega:  ", np.array(vegas).round(3)
In [39]:
interact(vega_calculation, S0=(75, 125, 1),
                       T=(0.01, 2.0, 0.01),
                       r=(0.0, 0.1, 0.005),
                       sigma=(0.01, 0.5, 0.01),
                       n=(1, 20, 1))
Strike: [ 80.00  84.44  88.89  93.33  97.78 102.22 106.67 111.11 115.56 120.00]
Vega:   [ 89.72  84.02  77.00  68.97  60.35  51.60  43.15  35.32  28.34  22.32]

Out[39]:
<function __main__.vega_calculation>

2d Plot

In [40]:
def vega_plot_2d(S0=100, T=1.0, r=0.01, sigma=0.2):
    o = call_option(S0, 100, T, r, sigma)
    strikes = np.linspace(80, 120, 20)
    vegas = []
    for k in strikes:
        o.K = k
        vegas.append(o.vega())
    plt.figure(figsize=(8, 5))
    plt.plot(strikes, vegas)
    plt.grid(True)
    plt.xlabel('strike')
    plt.ylabel('Vega')
In [41]:
interact(vega_plot_2d, S0=(75, 125, 1),
                       T=(0.01, 2.0, 0.01),
                       r=(0.0, 0.1, 0.005),
                       sigma=(0.01, 0.5, 0.01))
Out[41]:
<function __main__.vega_plot_2d>

3d Plot

In [42]:
def vega_plot_3d(S0=100, r=0.01, sigma=0.2):
    o = call_option(S0, 100, 1.0, r, sigma)
    maturities = np.linspace(0.05, 2.0, 20)
    strikes = np.linspace(80, 120, 20)
    T, K = np.meshgrid(strikes, maturities)
    V = np.zeros_like(K)
    for t in enumerate(maturities):
         for k in enumerate(strikes):
             o.T = t[1]
             o.K = k[1]
             V[t[0], k[0]] = o.vega()
    fig = plt.figure(figsize=(10, 5))
    ax = fig.gca(projection='3d')
    surf = ax.plot_surface(T, K, V, rstride=1, cstride=1,
             cmap=cm.coolwarm, linewidth=0.5, antialiased=True)
    ax.set_xlabel('strike')
    ax.set_ylabel('maturity')
    ax.set_zlabel('Vega of European call option')
    fig.colorbar(surf, shrink=0.5, aspect=5)
In [44]:
interact(vega_plot_3d, S0=(75, 125, 1),
                    r=(0.0, 0.1, 0.005),
                    sigma=(0.01, 0.5, 0.01))
Out[44]:
<function __main__.vega_plot_3d>