Skip to content

Instantly share code, notes, and snippets.

@arthurpham
Last active January 14, 2022 13:47
Show Gist options
  • Save arthurpham/f7c15000f7e0686af05ecc028f2f810f to your computer and use it in GitHub Desktop.
Save arthurpham/f7c15000f7e0686af05ecc028f2f810f to your computer and use it in GitHub Desktop.
AP_EuropeanOptions_GPU.ipynb
Display the source blob
Display the rendered blob
Raw
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"name": "AP_EuropeanOptions_GPU.ipynb",
"provenance": [],
"collapsed_sections": [],
"authorship_tag": "ABX9TyNH53H25eGda3fVl4ADxT1R",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
},
"accelerator": "GPU"
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/gist/arthurpham/f7c15000f7e0686af05ecc028f2f810f/ap_europeanoptions_gpu.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"id": "PXo9FVHCnFuU",
"colab": {
"base_uri": "https://localhost:8080/"
},
"outputId": "0f38aeb3-6542-4946-f9d8-d93efd87441e"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Requirement already satisfied: tensorflow in /usr/local/lib/python3.7/dist-packages (2.7.0)\n",
"Requirement already satisfied: six>=1.12.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (1.15.0)\n",
"Requirement already satisfied: wrapt>=1.11.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (1.13.3)\n",
"Requirement already satisfied: gast<0.5.0,>=0.2.1 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (0.4.0)\n",
"Requirement already satisfied: tensorflow-estimator<2.8,~=2.7.0rc0 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (2.7.0)\n",
"Requirement already satisfied: protobuf>=3.9.2 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (3.17.3)\n",
"Requirement already satisfied: google-pasta>=0.1.1 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (0.2.0)\n",
"Requirement already satisfied: wheel<1.0,>=0.32.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (0.37.1)\n",
"Requirement already satisfied: flatbuffers<3.0,>=1.12 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (2.0)\n",
"Requirement already satisfied: tensorboard~=2.6 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (2.7.0)\n",
"Requirement already satisfied: termcolor>=1.1.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (1.1.0)\n",
"Requirement already satisfied: h5py>=2.9.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (3.1.0)\n",
"Requirement already satisfied: keras<2.8,>=2.7.0rc0 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (2.7.0)\n",
"Requirement already satisfied: typing-extensions>=3.6.6 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (3.10.0.2)\n",
"Requirement already satisfied: libclang>=9.0.1 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (12.0.0)\n",
"Requirement already satisfied: astunparse>=1.6.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (1.6.3)\n",
"Requirement already satisfied: opt-einsum>=2.3.2 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (3.3.0)\n",
"Requirement already satisfied: numpy>=1.14.5 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (1.19.5)\n",
"Requirement already satisfied: absl-py>=0.4.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (0.12.0)\n",
"Requirement already satisfied: tensorflow-io-gcs-filesystem>=0.21.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (0.23.1)\n",
"Requirement already satisfied: grpcio<2.0,>=1.24.3 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (1.43.0)\n",
"Requirement already satisfied: keras-preprocessing>=1.1.1 in /usr/local/lib/python3.7/dist-packages (from tensorflow) (1.1.2)\n",
"Requirement already satisfied: cached-property in /usr/local/lib/python3.7/dist-packages (from h5py>=2.9.0->tensorflow) (1.5.2)\n",
"Requirement already satisfied: tensorboard-data-server<0.7.0,>=0.6.0 in /usr/local/lib/python3.7/dist-packages (from tensorboard~=2.6->tensorflow) (0.6.1)\n",
"Requirement already satisfied: setuptools>=41.0.0 in /usr/local/lib/python3.7/dist-packages (from tensorboard~=2.6->tensorflow) (57.4.0)\n",
"Requirement already satisfied: google-auth<3,>=1.6.3 in /usr/local/lib/python3.7/dist-packages (from tensorboard~=2.6->tensorflow) (1.35.0)\n",
"Requirement already satisfied: tensorboard-plugin-wit>=1.6.0 in /usr/local/lib/python3.7/dist-packages (from tensorboard~=2.6->tensorflow) (1.8.1)\n",
"Requirement already satisfied: google-auth-oauthlib<0.5,>=0.4.1 in /usr/local/lib/python3.7/dist-packages (from tensorboard~=2.6->tensorflow) (0.4.6)\n",
"Requirement already satisfied: markdown>=2.6.8 in /usr/local/lib/python3.7/dist-packages (from tensorboard~=2.6->tensorflow) (3.3.6)\n",
"Requirement already satisfied: requests<3,>=2.21.0 in /usr/local/lib/python3.7/dist-packages (from tensorboard~=2.6->tensorflow) (2.23.0)\n",
"Requirement already satisfied: werkzeug>=0.11.15 in /usr/local/lib/python3.7/dist-packages (from tensorboard~=2.6->tensorflow) (1.0.1)\n",
"Requirement already satisfied: cachetools<5.0,>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from google-auth<3,>=1.6.3->tensorboard~=2.6->tensorflow) (4.2.4)\n",
"Requirement already satisfied: rsa<5,>=3.1.4 in /usr/local/lib/python3.7/dist-packages (from google-auth<3,>=1.6.3->tensorboard~=2.6->tensorflow) (4.8)\n",
"Requirement already satisfied: pyasn1-modules>=0.2.1 in /usr/local/lib/python3.7/dist-packages (from google-auth<3,>=1.6.3->tensorboard~=2.6->tensorflow) (0.2.8)\n",
"Requirement already satisfied: requests-oauthlib>=0.7.0 in /usr/local/lib/python3.7/dist-packages (from google-auth-oauthlib<0.5,>=0.4.1->tensorboard~=2.6->tensorflow) (1.3.0)\n",
"Requirement already satisfied: importlib-metadata>=4.4 in /usr/local/lib/python3.7/dist-packages (from markdown>=2.6.8->tensorboard~=2.6->tensorflow) (4.10.0)\n",
"Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata>=4.4->markdown>=2.6.8->tensorboard~=2.6->tensorflow) (3.7.0)\n",
"Requirement already satisfied: pyasn1<0.5.0,>=0.4.6 in /usr/local/lib/python3.7/dist-packages (from pyasn1-modules>=0.2.1->google-auth<3,>=1.6.3->tensorboard~=2.6->tensorflow) (0.4.8)\n",
"Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests<3,>=2.21.0->tensorboard~=2.6->tensorflow) (2021.10.8)\n",
"Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests<3,>=2.21.0->tensorboard~=2.6->tensorflow) (2.10)\n",
"Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests<3,>=2.21.0->tensorboard~=2.6->tensorflow) (3.0.4)\n",
"Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests<3,>=2.21.0->tensorboard~=2.6->tensorflow) (1.24.3)\n",
"Requirement already satisfied: oauthlib>=3.0.0 in /usr/local/lib/python3.7/dist-packages (from requests-oauthlib>=0.7.0->google-auth-oauthlib<0.5,>=0.4.1->tensorboard~=2.6->tensorflow) (3.1.1)\n"
]
}
],
"source": [
"#@title Upgrade to TensorFlow 2.5+\n",
"!pip install --upgrade tensorflow --user"
]
},
{
"cell_type": "code",
"source": [
"#@title Install TF Quant Finance\n",
"!pip install tf-quant-finance"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "RCaqdqFMjElx",
"outputId": "6c4ea795-9c3a-42dd-bd20-f4a8f0fcc567"
},
"execution_count": 2,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Requirement already satisfied: tf-quant-finance in /usr/local/lib/python3.7/dist-packages (0.0.1.dev30)\n",
"Requirement already satisfied: tensorflow-probability>=0.11.0 in /usr/local/lib/python3.7/dist-packages (from tf-quant-finance) (0.15.0)\n",
"Requirement already satisfied: numpy>=1.19.2 in /usr/local/lib/python3.7/dist-packages (from tf-quant-finance) (1.19.5)\n",
"Requirement already satisfied: protobuf in /usr/local/lib/python3.7/dist-packages (from tf-quant-finance) (3.17.3)\n",
"Requirement already satisfied: attrs>=18.2.0 in /usr/local/lib/python3.7/dist-packages (from tf-quant-finance) (21.4.0)\n",
"Requirement already satisfied: gast>=0.3.2 in /usr/local/lib/python3.7/dist-packages (from tensorflow-probability>=0.11.0->tf-quant-finance) (0.4.0)\n",
"Requirement already satisfied: absl-py in /usr/local/lib/python3.7/dist-packages (from tensorflow-probability>=0.11.0->tf-quant-finance) (0.12.0)\n",
"Requirement already satisfied: dm-tree in /usr/local/lib/python3.7/dist-packages (from tensorflow-probability>=0.11.0->tf-quant-finance) (0.1.6)\n",
"Requirement already satisfied: cloudpickle>=1.3 in /usr/local/lib/python3.7/dist-packages (from tensorflow-probability>=0.11.0->tf-quant-finance) (1.3.0)\n",
"Requirement already satisfied: six>=1.10.0 in /usr/local/lib/python3.7/dist-packages (from tensorflow-probability>=0.11.0->tf-quant-finance) (1.15.0)\n",
"Requirement already satisfied: decorator in /usr/local/lib/python3.7/dist-packages (from tensorflow-probability>=0.11.0->tf-quant-finance) (4.4.2)\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"#@title Install QuantLib\n",
"!pip install QuantLib-Python"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "dbD4hfOl2cp2",
"outputId": "1c8ea7bd-777d-4536-ba27-5454cf8d7b3f"
},
"execution_count": 3,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Requirement already satisfied: QuantLib-Python in /usr/local/lib/python3.7/dist-packages (1.18)\n",
"Requirement already satisfied: QuantLib in /usr/local/lib/python3.7/dist-packages (from QuantLib-Python) (1.24)\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"# Get GPU info\n",
"!nvidia-smi"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "nex58FOXRL71",
"outputId": "b41793b5-55f6-40df-d11e-0e611060efc3"
},
"execution_count": 4,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Fri Jan 14 13:47:03 2022 \n",
"+-----------------------------------------------------------------------------+\n",
"| NVIDIA-SMI 495.46 Driver Version: 460.32.03 CUDA Version: 11.2 |\n",
"|-------------------------------+----------------------+----------------------+\n",
"| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\n",
"| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\n",
"| | | MIG M. |\n",
"|===============================+======================+======================|\n",
"| 0 Tesla T4 Off | 00000000:00:04.0 Off | 0 |\n",
"| N/A 42C P8 9W / 70W | 0MiB / 15109MiB | 0% Default |\n",
"| | | N/A |\n",
"+-------------------------------+----------------------+----------------------+\n",
" \n",
"+-----------------------------------------------------------------------------+\n",
"| Processes: |\n",
"| GPU GI CI PID Type Process name GPU Memory |\n",
"| ID ID Usage |\n",
"|=============================================================================|\n",
"| No running processes found |\n",
"+-----------------------------------------------------------------------------+\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"# Get CPU info\n",
"!cat /proc/cpuinfo"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "_KHJZlmfRMB0",
"outputId": "7bc27f45-2c08-4b3e-c4c5-742fbb9a725c"
},
"execution_count": 5,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"processor\t: 0\n",
"vendor_id\t: GenuineIntel\n",
"cpu family\t: 6\n",
"model\t\t: 79\n",
"model name\t: Intel(R) Xeon(R) CPU @ 2.20GHz\n",
"stepping\t: 0\n",
"microcode\t: 0x1\n",
"cpu MHz\t\t: 2199.998\n",
"cache size\t: 56320 KB\n",
"physical id\t: 0\n",
"siblings\t: 2\n",
"core id\t\t: 0\n",
"cpu cores\t: 1\n",
"apicid\t\t: 0\n",
"initial apicid\t: 0\n",
"fpu\t\t: yes\n",
"fpu_exception\t: yes\n",
"cpuid level\t: 13\n",
"wp\t\t: yes\n",
"flags\t\t: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm rdseed adx smap xsaveopt arat md_clear arch_capabilities\n",
"bugs\t\t: cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa\n",
"bogomips\t: 4399.99\n",
"clflush size\t: 64\n",
"cache_alignment\t: 64\n",
"address sizes\t: 46 bits physical, 48 bits virtual\n",
"power management:\n",
"\n",
"processor\t: 1\n",
"vendor_id\t: GenuineIntel\n",
"cpu family\t: 6\n",
"model\t\t: 79\n",
"model name\t: Intel(R) Xeon(R) CPU @ 2.20GHz\n",
"stepping\t: 0\n",
"microcode\t: 0x1\n",
"cpu MHz\t\t: 2199.998\n",
"cache size\t: 56320 KB\n",
"physical id\t: 0\n",
"siblings\t: 2\n",
"core id\t\t: 0\n",
"cpu cores\t: 1\n",
"apicid\t\t: 1\n",
"initial apicid\t: 1\n",
"fpu\t\t: yes\n",
"fpu_exception\t: yes\n",
"cpuid level\t: 13\n",
"wp\t\t: yes\n",
"flags\t\t: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm rdseed adx smap xsaveopt arat md_clear arch_capabilities\n",
"bugs\t\t: cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa\n",
"bogomips\t: 4399.99\n",
"clflush size\t: 64\n",
"cache_alignment\t: 64\n",
"address sizes\t: 46 bits physical, 48 bits virtual\n",
"power management:\n",
"\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"#@title **Imports** { display-mode: \"form\" }\n",
"\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"import time\n",
"\n",
"import pandas as pd\n",
"import seaborn as sns\n",
"\n",
"import tensorflow as tf\n",
"\n",
"# Import for Tensorflow Quant Finance\n",
"import tf_quant_finance as tff \n",
"\n",
"# Shortcut alias\n",
"pde = tff.math.pde\n",
"option_price = tff.black_scholes.option_price\n",
"implied_vol = tff.black_scholes.implied_vol\n",
"\n",
"from IPython.core.pylabtools import figsize\n",
"figsize(21, 14) # better graph size for Colab \n",
"\n",
"import QuantLib as ql"
],
"metadata": {
"id": "rV_CcOgCEimY"
},
"execution_count": 6,
"outputs": []
},
{
"cell_type": "code",
"source": [
"#@title **European Option pricer**\n",
"\n",
"\n",
"def option_param(number_of_options, dtype, seed=42):\n",
" \"\"\" Function to generate volatilities, rates, strikes \"\"\"\n",
" np.random.seed(seed)\n",
" volatility = tf.random.uniform(shape=(number_of_options, 1),\n",
" dtype=dtype) * 0.1 + 0.3\n",
" # Random risk free rate between 0 and 0.2.\n",
" risk_free_rate = tf.constant(\n",
" np.random.rand(number_of_options, 1) * 0.05, dtype)\n",
" # Random strike between 20 and 120.\n",
" strike = tf.constant(\n",
" np.random.rand(number_of_options, 1) * 100 + 50, dtype)\n",
" \n",
" # Random expiry between 1/360 and 3\n",
" expiry = tf.constant(\n",
" np.random.rand(number_of_options, 1) * 3.0 + 1.0/360.0, dtype)\n",
" \n",
" # Random spot between 20 and 120.\n",
" spot = tf.constant(\n",
" np.random.rand(number_of_options, 1) * 100 + 50, dtype)\n",
" \n",
" discount_factor = np.exp(-risk_free_rate * expiry)\n",
"\n",
" price = option_price(\n",
" volatilities=volatility,\n",
" strikes=strike,\n",
" expiries=expiry,\n",
" spots=spot,\n",
" discount_factors=discount_factor,\n",
" #is_call_options=is_call_options\n",
" )\n",
" \n",
" return volatility, risk_free_rate, strike, expiry, spot, discount_factor, price"
],
"metadata": {
"id": "7A0GlPmYjCX9"
},
"execution_count": 7,
"outputs": []
},
{
"cell_type": "code",
"source": [
"number_of_options = 1\n",
"spot = 110.0\n",
"volatility = 0.3\n",
"risk_free_rate = 0.05\n",
"strike = 50.0\n",
"expiry = 1.0\n",
"\n",
"\n",
"price = option_price(\n",
" volatilities=np.array([volatility, ]),\n",
" strikes=np.array([strike, ]),\n",
" expiries=np.array([expiry, ]),\n",
" spots=np.array([spot, ]),\n",
" discount_rates=np.array([risk_free_rate, ]),\n",
" is_call_options=np.array([True, ]))\n",
"\n",
"print(price.numpy())"
],
"metadata": {
"id": "Q9UZaVQmEi5P",
"colab": {
"base_uri": "https://localhost:8080/"
},
"outputId": "ea254976-0797-4353-d4ca-c3327bd7d206"
},
"execution_count": 8,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[62.45517072]\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"number_of_options = 50000 #@param\n",
"dtype = tf.float64\n",
"# Generate volatilities, rates, strikes\n",
"volatility, risk_free_rate, strike, expiry, spot, discount_factor, price \\\n",
"= option_param(number_of_options, dtype)"
],
"metadata": {
"id": "Dqo934SiE381"
},
"execution_count": 9,
"outputs": []
},
{
"cell_type": "code",
"source": [
"#@title Tensorflow Finance pricing on CPU\n",
"device = \"/cpu:0\"\n",
"with tf.device(device):\n",
" # Timed run\n",
" t = time.time()\n",
" implied_vol = tff.black_scholes.implied_vol(\n",
" prices=price,\n",
" strikes=strike,\n",
" expiries=expiry,\n",
" spots=spot,\n",
" discount_factors=discount_factor,\n",
" validate_args=False,\n",
" tolerance=1e-10,\n",
" max_iterations=200)\n",
" time_cpu = time.time() - t\n",
"\n",
"difference = volatility-implied_vol\n",
"print('Number of nan IV', tf.math.count_nonzero(tf.math.is_nan(difference)).numpy())\n",
"\n",
"cpu_options_per_second = number_of_options / time_cpu\n",
"print('Tensorflow CPU')\n",
"print('wall time: ', time_cpu)\n",
"print('options per second: ', cpu_options_per_second)\n",
"print('------------------------')"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "HSd0DEdxnwbG",
"outputId": "c72f0e67-692c-4a42-b89d-959b2d864ccf"
},
"execution_count": 10,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Number of nan IV 336\n",
"Tensorflow CPU\n",
"wall time: 3.002220392227173\n",
"options per second: 16654.340277433097\n",
"------------------------\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"#@title Tensorflow Finance pricing on GPU\n",
"device = \"/gpu:0\"\n",
"with tf.device(device):\n",
" # Timed run\n",
" t = time.time()\n",
" implied_vol = tff.black_scholes.implied_vol(\n",
" prices=price,\n",
" strikes=strike,\n",
" expiries=expiry,\n",
" spots=spot,\n",
" discount_factors=discount_factor,\n",
" validate_args=False,\n",
" tolerance=1e-10,\n",
" max_iterations=200)\n",
" time_gpu = time.time() - t\n",
"\n",
"difference = volatility-implied_vol\n",
"print('Number of nan IV', tf.math.count_nonzero(tf.math.is_nan(difference)).numpy())\n",
"\n",
"gpu_options_per_second = number_of_options / time_gpu\n",
"print('Tensorflow GPU')\n",
"print('wall time: ', time_gpu)\n",
"print('options per second: ', gpu_options_per_second)\n",
"print('------------------------')"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "W2GVuypvDbp1",
"outputId": "cb8eff4f-6513-42ee-ee9b-ae5823ac55ce"
},
"execution_count": 11,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Number of nan IV 337\n",
"Tensorflow GPU\n",
"wall time: 0.4950549602508545\n",
"options per second: 100998.88702188536\n",
"------------------------\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"list_price = price.numpy()\n",
"list_risk_free_rate = risk_free_rate.numpy()\n",
"list_strike = strike.numpy()\n",
"list_expiry = expiry.numpy()\n",
"list_spot = spot.numpy()\n",
"\n",
"spot_quote = ql.SimpleQuote(0.0)\n",
"strike_quote = ql.SimpleQuote(0.0)\n",
"rate_quote = ql.SimpleQuote(0.0)\n",
"vol_quote = ql.SimpleQuote(0.0)\n",
"\n",
"# Initializers for American option pricer inputs\n",
"spot_handle = ql.QuoteHandle(spot_quote)\n",
"strike_handle = ql.QuoteHandle(strike_quote)\n",
"rate_handle = ql.QuoteHandle(rate_quote)\n",
"vol_handle = ql.QuoteHandle(vol_quote)\n",
"\n",
"\n",
"calculation_date = ql.Date(1, 1, 2010)\n",
"day_count = ql.Thirty360()\n",
"calendar = ql.NullCalendar()\n",
"\n",
"# Create option data\n",
"ql.Settings.instance().evaluationDate = calculation_date\n",
"\n",
"flat_ts = ql.YieldTermStructureHandle(\n",
" ql.FlatForward(calculation_date, rate_handle, day_count)\n",
")\n",
"\n",
"flat_vol_ts = ql.BlackVolTermStructureHandle(\n",
" ql.BlackConstantVol(calculation_date, calendar, vol_handle, day_count)\n",
")\n",
"\n",
"\n",
"bsm_process = ql.BlackScholesProcess(spot_handle,\n",
" flat_ts,\n",
" flat_vol_ts)\n",
"ql_vols = []\n",
"t = time.time()\n",
"for cur_price, cur_rate, cur_strike, cur_expiry, cur_spot in zip(list_price, list_risk_free_rate, list_strike, list_expiry, list_spot): \n",
" strike_quote.setValue(cur_strike[0])\n",
" spot_quote.setValue(cur_spot[0])\n",
" rate_quote.setValue(cur_rate[0])\n",
" vol_quote.setValue(0.0) # dummy value\n",
" option_type = ql.Option.Call #ql.Option.Put\n",
"\n",
" maturity_date = calculation_date + ql.Period(int(cur_expiry[0]*360), ql.Days);\n",
" exercise = ql.EuropeanExercise(maturity_date)\n",
" payoff = ql.PlainVanillaPayoff(option_type, cur_strike[0])\n",
" option_ql = ql.VanillaOption(payoff, exercise)\n",
" try:\n",
" implied_vol = option_ql.impliedVolatility(cur_price[0], bsm_process)\n",
" except RuntimeError:\n",
" implied_vol = np.nan\n",
" ql_vols.append([implied_vol])\n",
"time_ql = time.time() - t\n",
"\n",
"difference = volatility-tf.convert_to_tensor(ql_vols, tf.float64)\n",
"print('Number of nan IV', tf.math.count_nonzero(tf.math.is_nan(difference)).numpy())\n",
"\n",
"ql_options_per_second = number_of_options / time_ql\n",
"print('Quantlib')\n",
"print('wall time: ', time_ql)\n",
"print('options per second: ', ql_options_per_second)\n",
"print('------------------------')"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "26xuKXUbIFXx",
"outputId": "e70a2ad2-c922-4e13-e852-1d352aff4a4c"
},
"execution_count": 12,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Number of nan IV 207\n",
"Quantlib\n",
"wall time: 2.9966378211975098\n",
"options per second: 16685.366395068428\n",
"------------------------\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"#@title Tensorflow Finance vs QuantLib\n",
"# Throughput\n",
"times_pd = pd.DataFrame([('QuantLib', ql_options_per_second), \n",
" ('CPU', cpu_options_per_second),\n",
" ('GPU', gpu_options_per_second)],\n",
" columns=['Device', 'Options/sec'])\n",
"sns.set(style=\"darkgrid\", palette=\"Paired\")\n",
"sns.set_context(\"notebook\", font_scale=1.25, rc={\"lines.linewidth\": 2.5})\n",
"plt.figure(figsize=(12, 6))\n",
"pt = sns.barplot(y=\"Device\", x=\"Options/sec\", data=times_pd)\n",
"pt.axes.set_title(\"Device European Option IV Speed\", fontsize=25)\n",
"xlabel = pt.axes.get_xlabel()\n",
"ylabel = pt.axes.get_ylabel()\n",
"pt.axes.set_xlabel(xlabel, fontsize=20)\n",
"pt.axes.set_ylabel(ylabel, fontsize=20)\n",
"plt.show()"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 429
},
"id": "xHiBwWevIFih",
"outputId": "a2bfb9c2-5475-4d89-911e-0753d8c3c1a3"
},
"execution_count": 13,
"outputs": [
{
"output_type": "display_data",
"data": {
"image/png": "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sdz58+fVs2dPnT17Vk899ZQWLFigPXv2aNeuXdqyZYv69esnZ2dnff7551qzZk2GaslroqOjNX78eLVv317r169XaGio9uzZo379+kmSDhw4oGHDhikhIUG1atXSkiVLtGvXLu3Zs0eTJ0+Wj4+P/vnnH/Xp00dXrlxJ83nefvtt1axZU8uWLdPOnTsVFhamL774QoUKFdLp06c1ePBgJSUlpVgmq/uSXcWKFTVy5EitXLlSe/fu1fbt27Vv3z6FhISoUaNGiouL05AhQ3T9+vU06543b57+/PNPjRs3Tjt37tTOnTu1YcMGBQUFSZImT56sEydOZOo937Ztm/nzU089laFlSpcurUqVKkm6dQUhLZl9nzdv3qwRI0ZIkkqVKqXNmzen+NezZ88M1ff777+bYbNZs2Zas2aNQkNDtWvXLo0fP17u7u46cuSIBg4caNnOdhcvXtQ777yjdu3amfvirl27NHLkSLm6uuqvv/7S9OnTM1RPRrm4uJgnHJYvX66rV6+mOt/SpUt1/fp12Ww2dejQIVPPUa1aNfPn999/X+fOnctyvWPHjpWrq6tmzpypsLAw7d69WyEhIfL399eNGzc0ZMgQ86TG7d577z2tXLlSDz/8sD799FNzX96zZ4++/fZbPfzwwwoLC9M777xjWXbz5s166623lJycrFdffVVr167V3r17FRYWpuXLl+vpp5/WlStXNGjQIJ05cybFsmfPntWQIUOUkJCggIAAhYSEaPfu3QoLC9O0adPk7OxM/yykiaAA/Eu5u7vr4YcflnSrScrtvv/+ex0/flx169bVzJkzVbduXfNytn34xQkTJki6dZB18+ZNc1n7F/aePXsUERGR6nPbQ0SbNm0y3JF60aJF2rVrl2w2myZNmqRevXqlOHNaokQJderUSf/9739TLPfll1/qwoULatWqlSZOnKgqVaqYIyp5e3tr0KBBevPNNyXdapaRVcuWLVODBg3S/bdr164srz89N27cULNmzTR27FhzNBtnZ2eVKVNGkvTFF18oMTFRZcuW1cyZM80mEE5OTmratKmmTp0qFxcXnTx5Uj/99FOaz+Pt7a1p06aZVx1cXFz07LPP6ssvv5Qk7du3T6tWrUqxzL3sS5I0YsQIvfTSSypXrpycnJzM561WrZq+++47BQQE6Pz58ynOst7p8uXLmjRpktq3b292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3+rZs6c5FGhycrI2bNigXr166ebNmypTpox5QJKaqKgo9e7d2zzIuXnzplasWKFBgwZJunX14s6bj93LvmQfoWXjxo365ptvzA7LsbGxmjJlij766KMUHfFzW2BgoHnfjm3btql9+/Zavnx5ijB28OBBDR8+XO+9956kW2fu0xv9Scra+/z//t//k3SrqVpWh0594403JN0aZWrgwIFmCEtMTNSSJUvM11qzZs0UVyHzmrZt28rV1VUJCQlmH5dmzZplqrnjnf788081a9ZMgwcP1vLly1M0Ebpx44ZCQ0PVp08f84C5RYsWeuihh1JdV758+fTqq69qy5Yt5hWgvXv3qkePHoqJiZG7u7tluOO6devqueeekySNGjVKH3/8cYqQnJCQoLCwME2YMEFBQUEp/m64uLjoww8/lIuLi3bu3KmuXbtq69atKTqUnzp1Sj/++KM6dOhgCSKvvfaa3N3dFRMTo1deecW8KmEYhv744w/16tUr20ewwv2DnlhAHmS/MZB06wskPj4+RcfSIkWKaPDgweaY3Xdyd3fX9OnTNXDgQIWFhWn06NH66KOP5OnpqeTk5BQH2mkNhSjdalterFgxRUdHm2eFM9vsyM7FxUWTJk3SgAEDFBoaqk8++USffvqpPD09dfPmTfMg0n4Vw6548eKaNWuW+vXrpxMnTuitt96Sk5OTvLy8lJCQkGK0nPSGD72bZcuWpRi7PDUlS5a0jITy3nvvqWvXroqKilKPHj2UL18+OTs76+rVqypWrJgmTJiQ5j0SMqpy5cqaMGGChg0bpp07d6pNmzby8PBQYmKieea6VKlSmjJlSrqj1owbN06DBw/WM888I09PT924ccM8O+nr66uvvvrK0kH3Xvaldu3aafHixQoNDdXXX3+tiRMnysvLS3FxcUpOTlaTJk1UqVIlh3RmtuvVq5d8fHw0ZswYHT9+XIMHD5bNZpOXl5euXr2a4mCsVatWGj169F0PqrLyPpctW1aBgYHaunWrhgwZonfffdcMUd26dVP37t3v+lqCgoI0fPhwjRs3TmvWrNGaNWvk5eWla9euma/D399fX331VZ7tQC/datvftGlTrVy50vy7d6/NjlxcXJSYmKjly5eb9xXIly+f8ufPbxlat2HDhvr444/TXNfw4cP1xRdfqEePHipQoIBsNpv5d8jNzU2ff/55qqO7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OihERESoYsWK6c4TEBCgiIiILBcFAAAAwLEyHRTi4uLk5eWV7jweHh66fPlylosCAAAA4FiZDgo+Pj46cuRIuvMcOXJERYsWzXJRAAAAABwr00GhXr162rRpk0JDQ1OdHhoaqo0bNyowMPCeiwMAAADgGJm+j0KvXr20fPly9ejRQ126dFGjRo1UokQJnTt3Ths3btSPP/4oNzc39erVKyfqBQAAAJALMh0Uypcvry+//FJDhw7V7NmzNWfOHHOaYRjy8PDQZ599pgoVKmRroQAAAAByT5buzNykSROtWbNGixYt0p49exQXFydPT0/VqFFD7dq1U5EiRbK7TgAAAAC5KEtBQZKKFCmiV155JTtrAQAAAJBHZLozMwAAAID7312vKOzYsUOSVK1aNeXLl8/8PSPq1KmT9coAAAAAOMxdg8LLL78sm82mZcuWyc/Pz/w9Iw4dOnTPBQIAAADIfXcNCq+//rpsNpvZQdn++/3o3Llz+uabb7Rp0yZFRUWpePHiqlq1ql599VUVKVJEzZo1M+ctXLiwKleurDfeeEOPPvqoJKlp06Z65ZVX1LVr1xTrHT9+vPbv36+5c+fm6usBAAAAsuquQWHAgAHp/n6/OHnypDp37qyHH35Y77//vsqXL68rV65o7dq1Gj9+vMaNGydJmjt3rvz8/HThwgV99tln5n0lvLy8HPwKAAAAgOyT5VGP7jcffvihihcvrnnz5snZ2dl8vFKlSurWrZtiY2Ml3bqS4OPjIx8fH3300Ud64okntGfPHjVq1MhRpQMAAADZLtOjHnXs2FHz5s3T5cuXc6Ieh4iJidHmzZvVs2fPFCHBLq2rBfny5ZMkJSYm5mh9AAAAQG7LdFA4ePCgRo8erUaNGmngwIFav369kpKScqK2XHPy5EkZhpGpu0lfuXJFn332mQoWLKhq1arlYHUAAABA7st006P169frl19+0eLFi7Vq1SqtXr1aRYsWVevWrdWuXTtVrFgxJ+rMM55//nk5OTnp2rVrKl26tL788ksVK1bM0WUBAAAA2SrTQaF48eLq1auXevXqpf3792vRokX67bffNGvWLM2ePVsBAQFq3769WrduraJFi+ZEzdmuTJkystlsCg8PN0cwSsvXX38tPz8/FS5c2NIkyd3dXXFxcZZlYmNj5eHhka01AwAAADnpnu7MXKVKFb333nva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\n",
"text/plain": [
"<Figure size 864x432 with 1 Axes>"
]
},
"metadata": {}
}
]
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment