Last active
January 14, 2022 13:47
-
-
Save arthurpham/f7c15000f7e0686af05ecc028f2f810f to your computer and use it in GitHub Desktop.
AP_EuropeanOptions_GPU.ipynb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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+++47BQQE6Pz58ynOst7p8uXLmjRpktq3b292Ii5ZsqS+/vprFS9eXMnJyVq+fHkG3un/Y7864urqagmn6bEHhYiICMtrtcvK+5wd7P0tHnvsMU2cONH8++Dm5qZ27drp008/lSTt3r3bPDN/p2vXrqlly5b66KOPzH2xQIECeumll8xO1L/99lu2124/8L9y5UqaAyLYmx3VqVPH/GxkVN26ddWgQQNJt0JiUFCQOnfurI8//li//PJLpoLm9evXNX36dDVo0MD8+1etWjXNmjVLhQsXVnx8vGWo19DQUC1ZskTe3t6aO3euWrdubTYVzJcvn5o1a6bg4GAVLFhQa9asSdFUKjk5WaNGjVJycrJGjhypN998U6VLl5bNZpPNZlP58uX11VdfqWnTpoqPj9f333+f4rmnTJmi+Ph4FS5cWN9//70ZmpycnPTEE09o2rRpunbtWqbeTzw4CArAv5j98vTly5dTPG7/Qu3evXuaw5Q2b95cHh4eiomJMZtgSFKNGjXMy/KpXVXYu3ev+aWambbh9poaN26cZnv2O924cUNLly6VJPXq1SvN+ex1HD58WNHR0Rmu6c7nio6OTvdfYmJiltadEWmN9hIbG6s//vhDktSzZ89U29E/+uijevLJJyWlfxD36quvpjpaT/369c0+HsuWLUsx7V72pbtxdnZWo0aNJMlsypaaWrVq6fHHH7c87ubmZjazOHLkSIafV5I5FGqhQoXMAJMR9n47hmFYPnd2WXmf79Xhw4cVHh4uSerbt2+qzXKaNm1qHiSmt5+k1Wbd3rb/77//zvYDy/Lly5tNjlLr1Hzs2DHt3btXkjJ9NcFu0qRJ6tKli1xdXZWUlKTdu3dr9uzZGjZsmFq0aKGmTZtq0qRJio+PT3c9Tz/9dKpN/Ly9vdW5c2dJ1u07f/58SVLr1q3THNq2ZMmS5pCsmzZtMh/fsWOHTpw4oSJFiuj5559Psy77SR773wvp1n5qD9GdO3eWt7e3ZTl/f3+1aNEizfXiwUYfBeA+c+7cOfMGYiNGjNDIkSPTnNd+if/06dOqXr26+XibNm309ddfa8mSJRo0aFCKqwb28FC9enXzngl3c/PmTe3fv1+SzOYiGbF//37duHFDkjLc/OLMmTMp7kORUY68mVr+/PlVuXLlVKcdOHDAbG9ev379NNfRoEEDLV++XEeOHFFiYmKqB/WpHWzfPm337t3mdpKyZ1+Sbp1NnT9/vsLCwnTu3LlUm5ak1xTkzvXdzt7WPq2DdkfI7PucHezrc3FxUd26ddOcr379+tq7d2+az1+4cGGVLVs21Wm392uIjY1Nt/N3VnTo0EE7d+5UaGioTp48meKqgT2wenp66umnn87S+gsWLKj3339f/fv319q1axUaGqr9+/frxIkTSkpK0unTpzVx4kQtWrRI33//fZpXLe62fadMmaJLly7p1KlT5lUd+9XI+fPnmyc/UhMXFydJKZoP2ZeNj483g3Vq7Ccybl82MjLSDMV3qzu9uvDgIigA/2L2g6PbRzy6/YArJiYmQ+u5s31427ZtNXHiRJ0+fVo7d+40O5omJiaaZyIzczXh0qVL5pdYeqNy3On8+fPmzxm9UvBvvIReuHDhNM9qX7x40fy5RIkSaa7DPu3mzZu6fPlyqmEpI8tfuHDBfCw79qVPPvkkRZt2Z2dnFSpUyAwyV69eNf+lJb2Osy4ut77G0moGlBb7Z+by5ctKTk7O8FWF29+HtEb/yez7nB3s+0mRIkXSHTWnZMmS6T5/eu/17VcpcuLq2jPPPKMxY8boypUrWrBggdnB9ubNm1qyZIkkqWXLlvd8Dwtvb2+98MILeuGFFyTdau60bds2zZgxQzt37lRkZKSGDBlihpM7pbd9bw9TFy9eNIOC/W9ZfHz8Xa9YSCk/R/ZlExMTM/R38PZlb9/OGdkvgTsRFIB/qStXrujUqVOSlOLMV3JysvnzsmXL7jpUX2pKly6txx57TDt27NDixYvNoLBp0ybFxMTI1dVVzz77bIbXl9Ubwt3+Wvbu3at8+fJlaT15XVZHb8lp97ovbd682QwJXbp00YsvvqgKFSqkeL1ffvmlJk+enD0FZ4K9X0JiYqL++usvy5CkabG3Hffz8zNDCrJHwYIF9eyzzyokJESLFy/WoEGD5OTkpPXr15sHyFltdpQed3d3NWvWTEFBQerevbu2b9+u/fv369ChQ2aflHtl7zz+wQcf6MUXX8zSstWrV9fPP/+cLfUAGUUfBeBfatOmTeYXyO1NDW4/k3zn6BeZYb9isGLFCrP5j73ZUePGjdO9x8Kdbj+DnJmabn8t9iYweYn9gNf+/qQmI2cP03N7h+/URlKxs5/9d3FxSfNMd3rNe+zTbm/DfK/7kv3qU8OGDfX+++/L39/fEoqy2qfkXgUGBpo/Z7Rj8alTp8ybdqXXjCOz73N2sO8nMTEx5hCYqbHvQ9n9/NnFPszz2bNnzbb29jP7/v7+KUYvym5OTk4p+gCkNZhDetv39qugt392fXx8JGXtc3Qvy96+nTOyXwJ3IigA/0IJCQnmqBqenp5q3ry5Oa106dLmZeTff/89y8/x9NNPK1++fIqLi9O6desUFxdnri8z906Qbh28Vq1aNdM1Va1a1QwY9/Jacor9gPzChQtpHpzt2bPnnp6jcuXKZrOYrVu3pjnfli1bJN26KVRanY5vHxL0TvbhPqtUqWI+dq/7kv2g9NFHH011umEY6daUk6pXr27W9cMPP6Ro4pWWyZMnm/1F0jsrnNn3WZK5je3rzyz7+m7evGnetyI19n3I/nnMa2rUqGFe7VmwYIGio6O1ceNGSZm7E3NW3X7PirSacKU3NK592xcuXNhsdiTJ7MS+fv36TNdUq1YtSVJUVJT27duXqWVLly5tNrPLSN3AnQgKwL/M9evXNXz4cPPMZu/evS13erW3vZ0/f745X1rsHd3u5OnpaY5y8ssvv5hXFgoXLpzhUYtuZ/+S37BhgzZs2JChZQoWLKjWrVtLkqZNm3bXM2ppvZacUrFiRUm3Du5SG27y+vXrmjVr1j09h5eXlzmyz4wZM1Ltg3H48GHzrHirVq3SXNfMmTNTvfqxbds2s8Ok/T4adveyL9mHfzx8+HCq8//4449m8zlHGDZsmJycnBQTE3PXezmEhISYZ7afe+65dJsqZeV9tr9XsbGxmX4d0q190X6APXny5FTvk7BhwwYzuLZs2TJLz5Mb7H8r1q1bp9mzZ+vmzZtydXVVmzZtsrzOo0ePZuis+e0jvaXV7GjFihU6fvy45fGLFy/qf//7nyTr9u3UqZNZx7x589Kt4erVqylOPNSrV8/sYD527Nh0rxhJKT+HNpvN7Pz9008/pRqIjx07lu7wxHiwERSAf4Hk5GQdPXpU33//vVq2bGmOTtG2bdtUhw3t0aOHefOfbt26KTg4OEUnzNjYWG3YsEHDhg1L8+7L9vVLt5o5BQcHS7r1BZheZ8n01lW7dm0ZhqEBAwZo+vTpKb60zp07p1mzZpljwdsNGTJExYsXV0xMjDp16qTFixenaM5z8eJFrVy5Uq+//rqGDh2a6bruRcmSJc0hHceOHastW7aYB2j79+9X9+7dM3Sm+m4GDx4sV1dX/f333+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