├── .DS_Store ├── pics ├── .DS_Store ├── ScatterPlot.png ├── SurfacePlot.png └── scatterplot3D.png ├── helper.py ├── LICENSE ├── README.md └── Volatility Surface Class.py /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengjin2/Derbit-Volatility-Visulization/HEAD/.DS_Store -------------------------------------------------------------------------------- /pics/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengjin2/Derbit-Volatility-Visulization/HEAD/pics/.DS_Store -------------------------------------------------------------------------------- /pics/ScatterPlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengjin2/Derbit-Volatility-Visulization/HEAD/pics/ScatterPlot.png -------------------------------------------------------------------------------- /pics/SurfacePlot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengjin2/Derbit-Volatility-Visulization/HEAD/pics/SurfacePlot.png -------------------------------------------------------------------------------- /pics/scatterplot3D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pengjin2/Derbit-Volatility-Visulization/HEAD/pics/scatterplot3D.png -------------------------------------------------------------------------------- /helper.py: -------------------------------------------------------------------------------- 1 | import hashlib, time, base64 2 | import numpy as np 3 | from scipy.stats import norm 4 | 5 | access_key = '' 6 | secret_key = '' 7 | 8 | def get_signature(action, arguments): 9 | nonce = str(int(time.time() * 1000)) 10 | 11 | signature_string = '_=%s&_ackey=%s&_acsec=%s&_action=%s' % ( 12 | nonce, access_key, secret_key, action 13 | ) 14 | 15 | for key, value in sorted(arguments.items()): 16 | if isinstance(value, list): 17 | value = "".join(str(v) for v in value) 18 | else: 19 | value = str(value) 20 | 21 | signature_string += "&%s=%s" % (key, value) 22 | 23 | sha256 = hashlib.sha256() 24 | sha256.update(signature_string.encode("utf-8")) 25 | signature_hash = base64.b64encode(sha256.digest()).decode() 26 | 27 | return "%s.%s.%s" % (access_key, nonce, signature_hash) 28 | 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 pengjin2 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deribit Volatility Visualization Tool 2 | A visualization tool created to help gauge the volatility of Deribit options 3 | 4 | This tool can connect to Deribit's public endpoint (registration required) and get real-time analysis. 5 | 6 | **Note:** 7 | This project seems to be getting a lot attention. 8 | I will try to update this project in the next 2 weeks. 9 | 10 | ## Installing 11 | ```bash 12 | git clone https://github.com/pengjin2/Derbit-Volatility-Visulization.git 13 | ``` 14 | 15 | ## Traceback 16 | Data to be included in the plot and data stream. 17 | 18 | ## Save data to local 19 | 20 | Data can be saved to local directory. When you set `save_local` = `True`, the data used to draw the session graphs will be updated automatically. 21 | 22 | ## Plot Types 23 | Currently only 3 kinds of plots are supported. More will be provided if deem necessary. 24 | 25 | ### ScatterPlot with polynomial fitted line 26 | ![Screenshot](pics/ScatterPlot.png) 27 | 28 | ### ScatterPlot in 3D dimension 29 | ![Screenshot](pics/scatterplot3D.png) 30 | 31 | ### Volatility Surface Plot 32 | ![Screenshot](pics/SurfacePlot.png) 33 | 34 | ## Author 35 | [Peng Jin](https://www.linkedin.com/in/peng-jin-24a23b117/) 36 | 37 | ## License 38 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 39 | -------------------------------------------------------------------------------- /Volatility Surface Class.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: Peng Jin 3 | Date: 01/30/2019 4 | """ 5 | 6 | import json 7 | import websocket 8 | import traceback 9 | import helper 10 | import ssl 11 | import time as time 12 | import pandas as pd 13 | import numpy as np 14 | import matplotlib.pyplot as plt 15 | from scipy.interpolate import griddata 16 | from mpl_toolkits.mplot3d import Axes3D 17 | 18 | 19 | class vol_surface(object): 20 | 21 | """Derbit volatiolity analytics tool for decision making""" 22 | 23 | def __init__(self, url='', on_message=None, traceback=2, save_local=False, plot_type=None): 24 | """ 25 | Program constructor 26 | :param url: Requested websocket address 27 | :param on_message: event message 28 | :param traceback: number of hours to look back from 29 | :param save_local: True if data is stored to local 30 | :param plot_type: Plot type (currently support scatter plot 2D, scatter plot 3D, and surface plot 3D 31 | """ 32 | self.url = url 33 | self.traceback = traceback 34 | self.save_local = save_local 35 | self.plot_type = plot_type 36 | try: 37 | self.vol_data = pd.read_csv("volatility.csv") 38 | except FileNotFoundError: 39 | self.vol_data = pd.DataFrame() 40 | self.ws = None 41 | self.active = False 42 | self.on_message = on_message 43 | self.action = "/api/v1/public/getlasttrades" 44 | 45 | def on_message(self, message): 46 | """ 47 | Websocket response message 48 | :param message: response message in dict format. 49 | """ 50 | if self.on_message: 51 | self.on_message() 52 | else: 53 | print(message) 54 | 55 | def start(self): 56 | """ 57 | Start websocket 58 | """ 59 | self.ws = websocket.create_connection(self.url, sslopt={'cert_reqs': ssl.CERT_NONE}) 60 | self.active = True 61 | self.on_connect() 62 | self.run() 63 | 64 | 65 | def on_connect(self): 66 | """ 67 | Call when websocket is connected. 68 | """ 69 | print('connected') 70 | 71 | def reconnect(self): 72 | """ 73 | Reconnect to websocket server. 74 | """ 75 | self.ws = websocket.create_connection(self.url, sslopt={'cert_reqs': ssl.CERT_NONE}) 76 | self.on_connect() 77 | 78 | def on_error(self, err): 79 | """ 80 | Print message when error occur 81 | """ 82 | print(err) 83 | 84 | def send_req(self, req): 85 | """ 86 | Send request to websocket server 87 | """ 88 | self.ws.send(json.dumps(req)) 89 | print(req) 90 | 91 | @staticmethod 92 | def concurrent_data_handler(message): 93 | """ 94 | using pandas to transform the message into format we intended 95 | :param message: message received from websocket 96 | :return: revised data-stream 97 | """ 98 | temp_df = pd.DataFrame(message['result']) 99 | temp_df = temp_df[['instrument', 'direction', 'indexPrice', 'price', 'quantity', 'iv', 'timeStamp', 'tradeId']] 100 | temp_df['timeStamp'] = temp_df['timeStamp'] / 1000 101 | temp_df['C-P'] = temp_df['instrument'].str.split('-', expand=True)[3] 102 | temp_df['strike'] = temp_df['instrument'].str.split('-', expand=True)[2].astype(float) 103 | temp_df['end_ts'] = pd.DataFrame( 104 | pd.to_datetime(temp_df['instrument'].str.split('-', expand=True)[1]).values.astype(np.int64) / 1000000000) 105 | temp_df['expiration_t'] = (temp_df['end_ts'] - temp_df['timeStamp']) / (365 * 24 * 3600) 106 | temp_df['option_price'] = temp_df['price'] * temp_df['indexPrice'] 107 | return temp_df 108 | 109 | @staticmethod 110 | def vis_tool(df, exp_ts, plot_type="scatter_3D"): 111 | """ 112 | Help to visualize the volatility skew/smile of past trades 113 | :param df: A dictionary object passed from the previous function 114 | :param exp_ts: expiration time 115 | :param plot_type: Plot type (currently support scatter plot 2D, scatter plot 3D, and surface plot 3D) 116 | :return: A PyPlot object 117 | """ 118 | x = df['strike'] 119 | y = df['expiration_t'] 120 | z = df['iv'] 121 | area = df['quantity'] * 3 # this is a scalar used for drawing 122 | 123 | def make_surf(x, y, z): 124 | x_grids, y_grids = np.meshgrid(np.linspace(min(x), max(x), 100), np.linspace(min(y), max(y), 100)) 125 | z_grids = griddata(np.array([x, y]).T, np.array(z), (x_grids, y_grids), method='linear') 126 | return x_grids, y_grids, z_grids 127 | 128 | x_grids, y_grids, z_grids = make_surf(x, y, z) 129 | 130 | 131 | if plot_type == "scatter_2D": 132 | # Plot axes 133 | fig = plt.figure() 134 | ax = plt.axes() 135 | scat = plt.scatter(x=x, y=z, s=area, c=z) 136 | plt.set_cmap('viridis_r') 137 | fig.colorbar(scat, shrink=0.5, aspect=5) 138 | # Add fitted line for the scatter plot 139 | fitted_data = np.polyfit(x, z, 3) 140 | p = np.poly1d(fitted_data) 141 | xp = np.linspace(x.min(), x.max(), 100) 142 | plt.plot(xp, p(xp), '-', alpha=0.3, color='red') 143 | # Set x axis label 144 | plt.xlabel('Strike') 145 | # Set y axis label 146 | plt.ylabel('Implied Volatility') 147 | # Set size legend 148 | for area in [area.min(), area.mean(), area.max()]: 149 | plt.scatter([], [], alpha=0.3, s=area, color='grey', label=str(round(area / 3, 2))) 150 | handles, labels = ax.get_legend_handles_labels() 151 | plt.legend(handles[-3:], labels[-3:], scatterpoints=1, labelspacing=1, title='Order Size') 152 | 153 | if plot_type == "surface_3D": 154 | fig = plt.figure() 155 | ax = plt.axes(projection='3d') 156 | surf = ax.plot_surface(x_grids, y_grids, z_grids, cmap='viridis', 157 | vmax=z.max(), vmin=z.min(), cstride=5, rstride=5, 158 | antialiased=True) 159 | fig.colorbar(surf, shrink=0.5, aspect=5) 160 | ax.set_xlabel('Strike Price') 161 | ax.set_ylabel('Time Remain to Expiration') 162 | ax.set_zlabel('Implied Volatility') 163 | 164 | time_object = time.gmtime(exp_ts) 165 | plt.title("Options expiring on %s/%s/%s %s:00:00 (GMT Time)" % (time_object.tm_mon, time_object.tm_mday, 166 | time_object.tm_year, time_object.tm_hour)) 167 | plt.show() 168 | 169 | def save_data(self, data, path='volatility.csv'): 170 | """ 171 | Save streaming data to local 172 | :param data: Websocket data stream 173 | :param path: Name of the file 174 | :return: None 175 | """ 176 | self.vol_data = pd.concat([self.vol_data, data], axis=0) 177 | self.vol_data = self.vol_data.drop_duplicates(subset='tradeId', keep='last') 178 | # self.vol_data = self.vol_data.reset_index(inplace=True) 179 | self.vol_data.to_csv(path, index=False) 180 | 181 | def run(self): 182 | """ 183 | listen to ws messages 184 | :return: volatility analytic plots 185 | """ 186 | while self.active: 187 | arguments = { 188 | "instrument": "options", 189 | "startTimestamp": (time.time() - self.traceback * 60 * 60) * 1000, # Get trades in the last **self.traceback** hours 190 | "count": 1000 191 | } 192 | try: 193 | self.send_req(req={ 194 | "action": self.action, 195 | "id": 666, 196 | "arguments": arguments, 197 | "sig": helper.get_signature(action=self.action, arguments=arguments), 198 | "message": "heartbeat" 199 | }) 200 | stream = self.ws.recv() 201 | message = json.loads(stream) 202 | data = self.concurrent_data_handler(message) 203 | if self.save_local: 204 | self.save_data(data=data) 205 | dfs = dict(tuple(data.groupby('end_ts'))) # Here we break down the dataframe by end_ts 206 | for i in dfs: 207 | self.vis_tool(df=dfs[i], exp_ts=i, plot_type=self.plot_type) 208 | except ConnectionError: 209 | msg = traceback.print_exc() 210 | self.on_error(msg) 211 | self.reconnect() 212 | 213 | 214 | if __name__ == '__main__': 215 | 216 | url = "wss://www.deribit.com/ws/api/v1" 217 | 218 | test = vol_surface(url, traceback=12, plot_type="scatter_2D", save_local=True) 219 | 220 | test.start() 221 | --------------------------------------------------------------------------------