voly 0.0.50__py3-none-any.whl → 0.0.51__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
voly/core/data.py CHANGED
@@ -12,7 +12,7 @@ import json
12
12
  import pandas as pd
13
13
  import requests
14
14
  import time
15
- import datetime
15
+ from datetime import datetime, timezone
16
16
  import re
17
17
  import numpy as np
18
18
  from typing import List, Dict, Any, Optional, Union
@@ -183,62 +183,39 @@ async def get_deribit_data(currency: str = "BTC") -> pd.DataFrame:
183
183
 
184
184
 
185
185
  @catch_exception
186
- def process_option_chain(df: pd.DataFrame, currency: str, min_dte: float = 2.0) -> pd.DataFrame:
186
+ def process_option_chain(df: pd.DataFrame, currency: str) -> pd.DataFrame:
187
187
  """
188
188
  Process raw option chain data into a standardized format.
189
189
 
190
190
  Parameters:
191
191
  df (pd.DataFrame): Raw option chain data
192
192
  currency (str): Currency code (e.g., 'BTC', 'ETH')
193
- min_dte (float): Minimum days to expiry to include
194
193
 
195
194
  Returns:
196
195
  pd.DataFrame: Processed option chain data
197
196
  """
198
197
  logger.info(f"Processing data for {currency}...")
199
198
 
200
- # Extract instrument details
201
- # Format is typically BTC-DDMMMYY-STRIKE-C/P or ETH-DDMMMYY-STRIKE-C/P
202
- def extract_details(instrument_name):
203
- pattern = f"{currency}-([\\d]{{1,2}})([A-Z]{{3}})(\\d{{2}})-([\\d]+)-([CP])"
204
- match = re.match(pattern, instrument_name)
205
- if match:
206
- day = int(match.group(1))
207
- month_str = match.group(2)
208
- year = 2000 + int(match.group(3))
209
- strike = float(match.group(4))
210
- option_type = match.group(5)
211
-
212
- month_dict = {'JAN': 1, 'FEB': 2, 'MAR': 3, 'APR': 4, 'MAY': 5, 'JUN': 6,
213
- 'JUL': 7, 'AUG': 8, 'SEP': 9, 'OCT': 10, 'NOV': 11, 'DEC': 12}
214
- month = month_dict.get(month_str)
215
-
216
- maturity_name = f"{day}{month_str}{str(year)[-2:]}"
217
-
218
- return {'day': day, 'month': month, 'year': year,
219
- 'strike': strike, 'option_type': option_type,
220
- 'maturity_name': maturity_name}
221
- return None
222
-
223
199
  # Apply extraction to create new columns
224
- df['details'] = df['instrument_name'].apply(lambda x: extract_details(x))
225
- df['strike'] = df['details'].apply(lambda x: x['strike'] if x else None)
226
- df['option_type'] = df['details'].apply(lambda x: x['option_type'] if x else None)
227
- df['maturity_name'] = df['details'].apply(lambda x: x['maturity_name'] if x else None)
200
+ df['currency'] = df['instrument_name'].split('-')[0]
201
+ df['maturity_name'] = df['instrument_name'].split('-')[1]
202
+ df['strike'] = df['instrument_name'].split('-')[2]
203
+ df['option_type'] = df['instrument_name'].split('-')[3]
228
204
 
229
- # Create expiry date at 8:00 AM UTC
230
- df['expiry_date'] = df['details'].apply(
231
- lambda x: datetime.datetime(x['year'], x['month'], x['day'], 8, 0, 0) if x else None
232
- )
205
+ # Create maturity date at 8:00 AM UTC
206
+ df['maturity_date'] = pd.to_datetime(df['maturity_name'].apply(
207
+ lambda x: int(datetime.strptime(x, "%d%b%y")
208
+ .replace(hour=8, minute=0, second=0, tzinfo=timezone.utc)
209
+ .timestamp() * 1000)), unit='ms')
233
210
 
234
211
  # Get reference time from timestamp
235
212
  reference_time = datetime.datetime.fromtimestamp(df['timestamp'].iloc[0] / 1000)
236
213
 
237
214
  # Calculate days to expiry (DTE)
238
- df['dte'] = (df['expiry_date'] - reference_time).dt.total_seconds() / (24 * 60 * 60)
215
+ df['dtm'] = (df['maturity_date'] - reference_time).dt.total_seconds() / (24 * 60 * 60)
239
216
 
240
217
  # Calculate time to expiry in years
241
- df['yte'] = df['dte'] / 365.25
218
+ df['ytm'] = df['dtm'] / 365.25
242
219
 
243
220
  # Calculate implied volatility (convert from percentage)
244
221
  df['mark_iv'] = df['mark_iv'] / 100
voly/core/fit.py CHANGED
@@ -20,7 +20,7 @@ warnings.filterwarnings("ignore")
20
20
 
21
21
  @catch_exception
22
22
  def calculate_residuals(params: List[float],
23
- time_to_expiry: float,
23
+ ytm: float,
24
24
  market_data: pd.DataFrame,
25
25
  model: Any = SVIModel) -> np.ndarray:
26
26
  """
@@ -28,7 +28,7 @@ def calculate_residuals(params: List[float],
28
28
 
29
29
  Parameters:
30
30
  - params: Model parameters (e.g., SVI parameters [a, b, sigma, rho, m])
31
- - time_to_expiry: The time to expiry in years
31
+ - ytm: The time to maturity in years
32
32
  - market_data: DataFrame with market data
33
33
  - model: Model class to use (default: SVIModel)
34
34
 
@@ -36,16 +36,16 @@ def calculate_residuals(params: List[float],
36
36
  - Array of residuals
37
37
  """
38
38
  # Filter market data for the specific time to expiry
39
- specific_expiry_data = market_data[market_data['yte'] == time_to_expiry]
39
+ maturity_data = market_data[market_data['ytm'] == ytm]
40
40
 
41
- # Calculate the total implied variance using the model for filtered data
42
- w_model = np.array([model.svi(x, *params) for x in specific_expiry_data['log_moneyness']])
41
+ # Calculate the total implied variance (w) using the model for filtered data
42
+ w = np.array([model.svi(x, *params) for x in maturity_data['log_moneyness']])
43
43
 
44
44
  # Extract the actual market implied volatilities
45
- iv_actual = specific_expiry_data['mark_iv'].values
45
+ iv_actual = maturity_data['mark_iv'].values
46
46
 
47
47
  # Calculate residuals between market implied volatilities and model predictions
48
- residuals = iv_actual - np.sqrt(w_model / time_to_expiry)
48
+ residuals = iv_actual - np.sqrt(w / ytm)
49
49
 
50
50
  return residuals
51
51
 
@@ -74,17 +74,17 @@ def fit_svi_parameters(market_data: pd.DataFrame,
74
74
 
75
75
  # Initialize data for fit performance
76
76
  fit_data = {
77
- 'Maturity': [],
78
- 'DTE': [],
79
- 'YTE': [],
80
- 'Success': [],
81
- 'Cost': [],
82
- 'Optimality': [],
83
- 'RMSE': [],
84
- 'MAE': [],
85
- '': [],
86
- 'Max Error': [],
87
- 'Number of Points': []
77
+ 'maturity_name': [],
78
+ 'dtm': [],
79
+ 'ytm': [],
80
+ 'fit_success': [],
81
+ 'cost': [],
82
+ 'optimality': [],
83
+ 'rmse': [],
84
+ 'mae': [],
85
+ 'r2': [],
86
+ 'max_error': [],
87
+ 'n_point': []
88
88
  }
89
89
 
90
90
  # Dictionary to store parameters
@@ -96,13 +96,13 @@ def fit_svi_parameters(market_data: pd.DataFrame,
96
96
  RESET = '\033[0m'
97
97
 
98
98
  # Get unique expiries
99
- unique_expiries = sorted(market_data['yte'].unique())
99
+ unique_maturities = sorted(market_data['ytm'].unique())
100
100
 
101
- for yte in unique_expiries:
101
+ for ytm in unique_maturities:
102
102
  # Get maturity name for reporting
103
- expiry_data = market_data[market_data['yte'] == yte]
104
- maturity_name = expiry_data['maturity_name'].iloc[0]
105
- dte_value = expiry_data['dte'].iloc[0]
103
+ maturity_data = market_data[market_data['ytm'] == ytm]
104
+ maturity_name = maturity_data['maturity_name'].iloc[0]
105
+ dtm = maturity_data['dtm'].iloc[0]
106
106
 
107
107
  logger.info(f"Optimizing for {maturity_name}...")
108
108
 
@@ -111,7 +111,7 @@ def fit_svi_parameters(market_data: pd.DataFrame,
111
111
  result = least_squares(
112
112
  calculate_residuals,
113
113
  initial_params,
114
- args=(yte, market_data, SVIModel),
114
+ args=(ytm, market_data, SVIModel),
115
115
  bounds=param_bounds,
116
116
  max_nfev=1000
117
117
  )
@@ -122,14 +122,14 @@ def fit_svi_parameters(market_data: pd.DataFrame,
122
122
  params = result.x
123
123
  params_dict[maturity_name] = {
124
124
  'params': params,
125
- 'dte': dte_value,
126
- 'yte': yte
125
+ 'dtm': dtm,
126
+ 'ytm': ytm
127
127
  }
128
128
 
129
129
  # Calculate model predictions for statistics
130
- w_svi = np.array([SVIModel.svi(x, *params) for x in expiry_data['log_moneyness']])
131
- iv_model = np.sqrt(w_svi / yte)
132
- iv_market = expiry_data['mark_iv'].values
130
+ w = np.array([SVIModel.svi(x, *params) for x in maturity_data['log_moneyness']])
131
+ iv_model = np.sqrt(w / ytm)
132
+ iv_market = maturity_data['mark_iv'].values
133
133
 
134
134
  # Calculate statistics
135
135
  rmse = np.sqrt(mean_squared_error(iv_market, iv_model))
@@ -139,17 +139,17 @@ def fit_svi_parameters(market_data: pd.DataFrame,
139
139
  num_points = len(expiry_data)
140
140
 
141
141
  # Add to fit data
142
- fit_data['Maturity'].append(maturity_name)
143
- fit_data['DTE'].append(dte_value)
144
- fit_data['YTE'].append(yte)
145
- fit_data['Success'].append(result.success)
146
- fit_data['Cost'].append(result.cost)
147
- fit_data['Optimality'].append(result.optimality)
148
- fit_data['RMSE'].append(rmse)
149
- fit_data['MAE'].append(mae)
150
- fit_data[''].append(r2)
151
- fit_data['Max Error'].append(max_error)
152
- fit_data['Number of Points'].append(num_points)
142
+ fit_data['maturity'].append(maturity_name)
143
+ fit_data['dtm'].append(dtm)
144
+ fit_data['ytm'].append(ytm)
145
+ fit_data['fit_success'].append(result.success)
146
+ fit_data['cost'].append(result.cost)
147
+ fit_data['optimality'].append(result.optimality)
148
+ fit_data['rmse'].append(rmse)
149
+ fit_data['mae'].append(mae)
150
+ fit_data['r2'].append(r2)
151
+ fit_data['max_error'].append(max_error)
152
+ fit_data['n_points'].append(num_points)
153
153
 
154
154
  if result.success:
155
155
  logger.info(f'Optimization for {maturity_name}: {GREEN}SUCCESS{RESET}')
@@ -171,7 +171,7 @@ def create_parameters_matrix(params_dict: Dict[str, Dict]) -> Tuple[pd.DataFrame
171
171
  Uses maturity names as column names.
172
172
 
173
173
  Parameters:
174
- - params_dict: Dictionary of parameter results by maturity name
174
+ - params_dict: Dictionary of raw parameter results by maturity name
175
175
 
176
176
  Returns:
177
177
  - Tuple of DataFrames with optimized parameters:
@@ -180,23 +180,23 @@ def create_parameters_matrix(params_dict: Dict[str, Dict]) -> Tuple[pd.DataFrame
180
180
  """
181
181
  # Get maturity names in order by DTE
182
182
  maturity_names = sorted(params_dict.keys(),
183
- key=lambda x: params_dict[x]['dte'])
183
+ key=lambda x: params_dict[x]['dtm'])
184
184
 
185
- # Create DataFrame for raw parameters with maturity names as columns
186
- raw_param_matrix = pd.DataFrame(
185
+ # Create DataFrame for raw parameters
186
+ raw_params_matrix = pd.DataFrame(
187
187
  columns=maturity_names,
188
188
  index=SVIModel.PARAM_NAMES
189
189
  )
190
190
 
191
191
  # Create DataFrame for JW parameters
192
- jw_param_matrix = pd.DataFrame(
192
+ jw_params_matrix = pd.DataFrame(
193
193
  columns=maturity_names,
194
194
  index=SVIModel.JW_PARAM_NAMES
195
195
  )
196
196
 
197
- # Store YTE and DTE values for reference
198
- yte_values = {}
199
- dte_values = {}
197
+ # Store YTM and DTM values for reference
198
+ ytm_values = {}
199
+ dtm_values = {}
200
200
 
201
201
  # Fill the matrices with optimized parameters
202
202
  for maturity_name in maturity_names:
@@ -204,27 +204,27 @@ def create_parameters_matrix(params_dict: Dict[str, Dict]) -> Tuple[pd.DataFrame
204
204
 
205
205
  # Extract raw SVI parameters
206
206
  a, b, sigma, rho, m = result['params']
207
- raw_param_matrix[maturity_name] = [a, b, sigma, rho, m]
207
+ raw_params_matrix[maturity_name] = [a, b, sigma, rho, m]
208
208
 
209
209
  # Get time to expiry
210
- yte = result['yte']
211
- yte_values[maturity_name] = yte
212
- dte_values[maturity_name] = result['dte']
210
+ ytm = result['ytm']
211
+ ytm_values[maturity_name] = ytm
212
+ dtm_values[maturity_name] = result['dtm']
213
213
 
214
214
  # Calculate JW parameters
215
- nu, psi, p, c, nu_tilde = SVIModel.raw_to_jw_params(a, b, sigma, rho, m, yte)
216
- jw_param_matrix[maturity_name] = [nu, psi, p, c, nu_tilde]
215
+ nu, psi, p, c, nu_tilde = SVIModel.raw_to_jw_params(a, b, sigma, rho, m, ytm)
216
+ jw_params_matrix[maturity_name] = [nu, psi, p, c, nu_tilde]
217
217
 
218
218
  # Store YTE and DTE as attributes in all DataFrames for reference
219
219
  attrs = {
220
- 'yte_values': yte_values,
221
- 'dte_values': dte_values
220
+ 'ytm_values': ytm_values,
221
+ 'dtm_values': dtm_values
222
222
  }
223
223
 
224
- raw_param_matrix.attrs.update(attrs)
225
- jw_param_matrix.attrs.update(attrs)
224
+ raw_params_matrix.attrs.update(attrs)
225
+ jw_params_matrix.attrs.update(attrs)
226
226
 
227
- return raw_param_matrix, jw_param_matrix
227
+ return raw_params_matrix, jw_params_matrix
228
228
 
229
229
 
230
230
  @catch_exception
@@ -255,46 +255,46 @@ def fit_model(market_data: pd.DataFrame,
255
255
  )
256
256
 
257
257
  # Step 2: Create parameter matrices
258
- raw_param_matrix, jw_param_matrix = create_parameters_matrix(params_dict)
258
+ raw_params_matrix, jw_params_matrix = create_parameters_matrix(params_dict)
259
259
 
260
260
  return {
261
- 'raw_param_matrix': raw_param_matrix,
262
- 'jw_param_matrix': jw_param_matrix,
261
+ 'raw_params_matrix': raw_params_matrix,
262
+ 'jw_params_matrix': jw_params_matrix,
263
263
  'fit_performance': fit_performance,
264
264
  }
265
265
 
266
266
 
267
267
  @catch_exception
268
268
  def get_iv_surface(fit_results: Dict[str, Any],
269
- moneyness_params: Tuple[float, float, int] = (-2, 2, 500)
269
+ log_moneyness_params: Tuple[float, float, int] = (-2, 2, 500)
270
270
  ) -> Dict[str, Any]:
271
271
  """
272
272
  Generate implied volatility surface using optimized SVI parameters.
273
273
 
274
274
  Parameters:
275
275
  - fit_results: results from fit_model()
276
- - moneyness_params: Tuple of (min, max, num_points) for the moneyness grid
276
+ - log_moneyness_params: Tuple of (min, max, num_points) for the moneyness grid
277
277
 
278
278
  Returns:
279
- - moneyness_array, iv_surface
279
+ - x_domain, iv_surface
280
280
  """
281
281
  iv_surface = {}
282
282
 
283
283
  # Extract moneyness parameters
284
- min_m, max_m, num_points = moneyness_params
284
+ min_m, max_m, num_points = log_moneyness_params
285
285
 
286
- # Generate moneyness grid
287
- moneyness_array = np.linspace(min_m, max_m, num=num_points)
286
+ # Generate moneyness array
287
+ log_moneyness_array = np.linspace(min_m, max_m, num=num_points)
288
288
 
289
- # Get YTE values from the parameter matrix attributes
290
- yte_values = fit_results['fit_performance']['YTE']
291
- maturity_values = fit_results['fit_performance']['Maturity']
292
- param_matrix = fit_results['raw_param_matrix']
289
+ # Get YTM values from the parameter matrix attributes
290
+ ytm_values = fit_results['fit_performance']['ytm']
291
+ maturity_values = fit_results['fit_performance']['maturity_name']
292
+ raw_params_matrix = fit_results['raw_params_matrix']
293
293
 
294
294
  # Generate implied volatility for each expiry
295
- for maturity, yte in zip(maturity_values, yte_values):
296
- svi_params = param_matrix[maturity].values
297
- w_svi = [SVIModel.svi(x, *svi_params) for x in moneyness_array]
298
- iv_surface[maturity] = np.sqrt(np.array(w_svi) / yte)
295
+ for maturity, ytm in zip(maturity_values, ytm_values):
296
+ svi_params = raw_params_matrix[maturity].values
297
+ w_svi = [SVIModel.svi(x, *svi_params) for x in log_moneyness_array]
298
+ iv_surface[maturity] = np.sqrt(np.array(w_svi) / ytm)
299
299
 
300
300
  return moneyness_array, iv_surface
voly/models.py CHANGED
@@ -67,11 +67,11 @@ class SVIModel:
67
67
  return nu, psi, p, c, nu_tilde
68
68
 
69
69
  @staticmethod
70
- def jw_to_raw_params(nu: float, phi: float, p: float, c: float, nu_tilde: float, t: float) -> Tuple[float, float, float, float, float]:
70
+ def jw_to_raw_params(nu: float, psi: float, p: float, c: float, nu_tilde: float, t: float) -> Tuple[float, float, float, float, float]:
71
71
  w = nu * t
72
72
  b = (c + p) / 2
73
73
  rho = (c - p) / (c + p)
74
- beta = rho - ((2 * w * phi) / b)
74
+ beta = rho - ((2 * w * psi) / b)
75
75
  alpha = np.sign(beta) * (np.sqrt((1 / (beta ** 2)) - 1))
76
76
  m = (((nu ** 2) - (nu_tilde ** 2)) * t) / (
77
77
  b * ((-rho) + (np.sign(alpha) * np.sqrt(1 + alpha ** 2)) - (alpha * np.sqrt(1 - rho ** 2))))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: voly
3
- Version: 0.0.50
3
+ Version: 0.0.51
4
4
  Summary: Options & volatility research package
5
5
  Author-email: Manu de Cara <manu.de.cara@gmail.com>
6
6
  License: MIT
@@ -2,17 +2,17 @@ voly/__init__.py,sha256=8xyDk7rFCn_MOD5hxuv5cxxKZvBVRiSIM7TgaMPpwpw,211
2
2
  voly/client.py,sha256=zOYgZA0TTJ5bHDCBWqEyeaQ0IKuee1uAIbzk0uyW_Uw,20350
3
3
  voly/exceptions.py,sha256=PBsbn1vNMvKcCJwwJ4lBO6glD85jo1h2qiEmD7ArAjs,92
4
4
  voly/formulas.py,sha256=Xgaq4lx1fNzRfu9W84fMNeH6GRJ0FNFNUUUYn5ffjjE,8843
5
- voly/models.py,sha256=V2VI9yK-JyfKtI4nT5i42LQSwyGvexiymNiC2V1mQn4,3659
5
+ voly/models.py,sha256=LXXIlpXZQEfXTuCngxC8Hd3bWtw6wdXDCSGxTLmHM-c,3659
6
6
  voly/core/__init__.py,sha256=bu6fS2I1Pj9fPPnl-zY3L7NqrZSY5Zy6NY2uMUvdhKs,183
7
7
  voly/core/charts.py,sha256=T8cogkiHj8NcFxfARumNvAjLJUAxsMlHUOVgshwjye8,26474
8
- voly/core/data.py,sha256=Dfk-ByHpdteUiLXr0p-wRLr3VAmdyjdDBKwjwdTgCjA,9939
9
- voly/core/fit.py,sha256=O4PMihVWI1NIEFU4_RkKvT73p0Jk0tB-ot5moMXAW78,9950
8
+ voly/core/data.py,sha256=gEtmsp-cgCSdAWhnCrOHjrjUTfGJ_1NWkIEeHAkNkAg,8904
9
+ voly/core/fit.py,sha256=PCKedwjY2Xsl_Gv9t46dAnEnFbz0T1oJsZsb__GWCSw,9878
10
10
  voly/core/interpolate.py,sha256=ztVIePJZOh-CIbn69wkh1JW2rKywNe2FEewRN0zcSAo,8185
11
11
  voly/core/rnd.py,sha256=8FTU-Qp9epW9yE4XSOdiFGIRXrGyXqF6mVgZn1NMvxk,11813
12
12
  voly/utils/__init__.py,sha256=E05mWatyC-PDOsCxQV1p5Xi1IgpOomxrNURyCx_gB-w,200
13
13
  voly/utils/logger.py,sha256=4-_2bVJmq17Q0d7Rd2mPg1AeR8gxv6EPvcmBDMFWcSM,1744
14
- voly-0.0.50.dist-info/LICENSE,sha256=wcHIVbE12jfcBOai_wqBKY6xvNQU5E909xL1zZNq_2Q,1065
15
- voly-0.0.50.dist-info/METADATA,sha256=sLeP_KK8Rc3bqPeG3GOQIQUrALDoaIPAZ_iwXUUuO7M,4092
16
- voly-0.0.50.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
17
- voly-0.0.50.dist-info/top_level.txt,sha256=ZfLw2sSxF-LrKAkgGjOmeTcw6_gD-30zvtdEY5W4B7c,5
18
- voly-0.0.50.dist-info/RECORD,,
14
+ voly-0.0.51.dist-info/LICENSE,sha256=wcHIVbE12jfcBOai_wqBKY6xvNQU5E909xL1zZNq_2Q,1065
15
+ voly-0.0.51.dist-info/METADATA,sha256=FCA6dznopdWOB3Yt7cwscidzTclrUT4og6kHq3F5xUY,4092
16
+ voly-0.0.51.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
17
+ voly-0.0.51.dist-info/top_level.txt,sha256=ZfLw2sSxF-LrKAkgGjOmeTcw6_gD-30zvtdEY5W4B7c,5
18
+ voly-0.0.51.dist-info/RECORD,,
File without changes
File without changes