ivolatility-backtesting 1.0.1__py3-none-any.whl → 1.2.0__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.
Potentially problematic release.
This version of ivolatility-backtesting might be problematic. Click here for more details.
- ivolatility_backtesting/__init__.py +10 -4
- ivolatility_backtesting/ivolatility_backtesting.py +362 -148
- {ivolatility_backtesting-1.0.1.dist-info → ivolatility_backtesting-1.2.0.dist-info}/METADATA +72 -70
- ivolatility_backtesting-1.2.0.dist-info/RECORD +7 -0
- {ivolatility_backtesting-1.0.1.dist-info → ivolatility_backtesting-1.2.0.dist-info}/WHEEL +1 -1
- ivolatility_backtesting-1.0.1.dist-info/RECORD +0 -7
- {ivolatility_backtesting-1.0.1.dist-info → ivolatility_backtesting-1.2.0.dist-info/licenses}/LICENSE +0 -0
- {ivolatility_backtesting-1.0.1.dist-info → ivolatility_backtesting-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
from .ivolatility_backtesting import (
|
|
2
2
|
BacktestResults, BacktestAnalyzer, ResultsReporter,
|
|
3
3
|
ChartGenerator, ResultsExporter, run_backtest,
|
|
4
|
-
init_api,
|
|
4
|
+
init_api, api_call, APIHelper, APIManager,
|
|
5
|
+
ResourceMonitor, create_progress_bar, update_progress, format_time
|
|
5
6
|
)
|
|
6
7
|
|
|
7
8
|
__all__ = [
|
|
8
9
|
'BacktestResults',
|
|
9
|
-
'BacktestAnalyzer',
|
|
10
|
+
'BacktestAnalyzer',
|
|
10
11
|
'ResultsReporter',
|
|
11
12
|
'ChartGenerator',
|
|
12
13
|
'ResultsExporter',
|
|
13
14
|
'run_backtest',
|
|
14
15
|
'init_api',
|
|
15
|
-
'
|
|
16
|
-
'
|
|
16
|
+
'api_call',
|
|
17
|
+
'APIHelper',
|
|
18
|
+
'APIManager',
|
|
19
|
+
'ResourceMonitor',
|
|
20
|
+
'create_progress_bar',
|
|
21
|
+
'update_progress',
|
|
22
|
+
'format_time'
|
|
17
23
|
]
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
"""
|
|
2
|
-
ivolatility_backtesting.py
|
|
3
|
-
|
|
2
|
+
ivolatility_backtesting.py - UNIVERSAL BACKTEST FRAMEWORK
|
|
3
|
+
Version 2.0 - With DataFrame/Dict Auto-Normalization
|
|
4
|
+
|
|
5
|
+
Key Features:
|
|
6
|
+
- Handles both dict and DataFrame API responses automatically
|
|
7
|
+
- Universal BacktestResults interface
|
|
8
|
+
- 30+ calculated metrics
|
|
9
|
+
- Automatic reporting, charts, and exports
|
|
10
|
+
- One-command runner: run_backtest()
|
|
4
11
|
|
|
5
12
|
Usage:
|
|
6
13
|
from ivolatility_backtesting import *
|
|
7
14
|
|
|
8
|
-
# Initialize API
|
|
15
|
+
# Initialize API
|
|
9
16
|
init_api(os.getenv("API_KEY"))
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
# Use api_call for normalized responses
|
|
19
|
+
data = api_call('/equities/eod/stock-prices', symbol='AAPL', from_='2024-01-01')
|
|
20
|
+
|
|
21
|
+
# Run backtest
|
|
12
22
|
analyzer = run_backtest(my_strategy, CONFIG)
|
|
13
23
|
"""
|
|
14
24
|
|
|
@@ -19,6 +29,8 @@ import seaborn as sns
|
|
|
19
29
|
from datetime import datetime, timedelta
|
|
20
30
|
import ivolatility as ivol
|
|
21
31
|
import os
|
|
32
|
+
import time
|
|
33
|
+
import psutil
|
|
22
34
|
|
|
23
35
|
# Set style
|
|
24
36
|
sns.set_style('darkgrid')
|
|
@@ -26,118 +38,363 @@ plt.rcParams['figure.figsize'] = (15, 8)
|
|
|
26
38
|
|
|
27
39
|
|
|
28
40
|
# ============================================================
|
|
29
|
-
#
|
|
41
|
+
# RESOURCE MONITOR - NEW!
|
|
30
42
|
# ============================================================
|
|
31
|
-
class
|
|
32
|
-
"""
|
|
33
|
-
|
|
43
|
+
class ResourceMonitor:
|
|
44
|
+
"""Monitor CPU and RAM using cgroups v2 and psutil fallback"""
|
|
45
|
+
|
|
46
|
+
def __init__(self):
|
|
47
|
+
self.process = psutil.Process()
|
|
48
|
+
self.cpu_count = psutil.cpu_count()
|
|
49
|
+
self.last_cpu_time = None
|
|
50
|
+
self.last_check_time = None
|
|
51
|
+
self.use_cgroups = self._check_cgroups_v2()
|
|
52
|
+
|
|
53
|
+
def _check_cgroups_v2(self):
|
|
54
|
+
"""Check if cgroups v2 is available"""
|
|
55
|
+
try:
|
|
56
|
+
return os.path.exists('/sys/fs/cgroup/cpu.stat') and \
|
|
57
|
+
os.path.exists('/sys/fs/cgroup/memory.current')
|
|
58
|
+
except:
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
def _read_cgroup_cpu(self):
|
|
62
|
+
"""Read CPU usage from cgroups v2"""
|
|
63
|
+
try:
|
|
64
|
+
with open('/sys/fs/cgroup/cpu.stat', 'r') as f:
|
|
65
|
+
for line in f:
|
|
66
|
+
if line.startswith('usage_usec'):
|
|
67
|
+
return int(line.split()[1])
|
|
68
|
+
except:
|
|
69
|
+
pass
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
def _read_cgroup_memory(self):
|
|
73
|
+
"""Read memory usage from cgroups v2"""
|
|
74
|
+
try:
|
|
75
|
+
with open('/sys/fs/cgroup/memory.current', 'r') as f:
|
|
76
|
+
current = int(f.read().strip())
|
|
77
|
+
with open('/sys/fs/cgroup/memory.max', 'r') as f:
|
|
78
|
+
max_mem = f.read().strip()
|
|
79
|
+
if max_mem == 'max':
|
|
80
|
+
max_mem = psutil.virtual_memory().total
|
|
81
|
+
else:
|
|
82
|
+
max_mem = int(max_mem)
|
|
83
|
+
return current, max_mem
|
|
84
|
+
except:
|
|
85
|
+
pass
|
|
86
|
+
return None, None
|
|
34
87
|
|
|
35
|
-
|
|
36
|
-
|
|
88
|
+
def get_cpu_percent(self):
|
|
89
|
+
"""Get CPU usage percentage with cgroups v2 fallback to psutil"""
|
|
90
|
+
if self.use_cgroups:
|
|
91
|
+
current_time = time.time()
|
|
92
|
+
current_cpu = self._read_cgroup_cpu()
|
|
93
|
+
|
|
94
|
+
if current_cpu and self.last_cpu_time and self.last_check_time:
|
|
95
|
+
time_delta = current_time - self.last_check_time
|
|
96
|
+
cpu_delta = current_cpu - self.last_cpu_time
|
|
97
|
+
|
|
98
|
+
if time_delta > 0:
|
|
99
|
+
# Convert microseconds to percentage
|
|
100
|
+
cpu_percent = (cpu_delta / (time_delta * 1_000_000)) * 100
|
|
101
|
+
cpu_percent = min(cpu_percent, 100 * self.cpu_count)
|
|
102
|
+
|
|
103
|
+
self.last_cpu_time = current_cpu
|
|
104
|
+
self.last_check_time = current_time
|
|
105
|
+
|
|
106
|
+
return round(cpu_percent, 1)
|
|
107
|
+
|
|
108
|
+
self.last_cpu_time = current_cpu
|
|
109
|
+
self.last_check_time = current_time
|
|
37
110
|
|
|
38
|
-
#
|
|
39
|
-
|
|
111
|
+
# Fallback to psutil
|
|
112
|
+
try:
|
|
113
|
+
cpu = self.process.cpu_percent(interval=0.1)
|
|
114
|
+
return round(cpu, 1) if cpu > 0 else round(psutil.cpu_percent(interval=0.1), 1)
|
|
115
|
+
except:
|
|
116
|
+
return 0.0
|
|
117
|
+
|
|
118
|
+
def get_memory_info(self):
|
|
119
|
+
"""Get memory usage (MB and %) with cgroups v2 fallback to psutil"""
|
|
120
|
+
if self.use_cgroups:
|
|
121
|
+
current, max_mem = self._read_cgroup_memory()
|
|
122
|
+
if current and max_mem:
|
|
123
|
+
mb = current / (1024 * 1024)
|
|
124
|
+
percent = (current / max_mem) * 100
|
|
125
|
+
return round(mb, 1), round(percent, 1)
|
|
126
|
+
|
|
127
|
+
# Fallback to psutil
|
|
128
|
+
try:
|
|
129
|
+
mem = self.process.memory_info()
|
|
130
|
+
mb = mem.rss / (1024 * 1024)
|
|
131
|
+
total = psutil.virtual_memory().total
|
|
132
|
+
percent = (mem.rss / total) * 100
|
|
133
|
+
return round(mb, 1), round(percent, 1)
|
|
134
|
+
except:
|
|
135
|
+
return 0.0, 0.0
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def create_progress_bar():
|
|
139
|
+
"""
|
|
140
|
+
Create enhanced progress bar with ETA, CPU%, RAM
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
tuple: (progress_bar, status_label, resource_monitor, start_time)
|
|
144
|
+
|
|
145
|
+
Example:
|
|
146
|
+
progress_bar, status_label, monitor, start_time = create_progress_bar()
|
|
40
147
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
data = getOptionsData(symbol='SPY', tradeDate='2024-01-01', ...)
|
|
148
|
+
for i in range(total):
|
|
149
|
+
update_progress(progress_bar, status_label, monitor, i, total, start_time)
|
|
44
150
|
"""
|
|
151
|
+
from IPython.display import display
|
|
152
|
+
import ipywidgets as widgets
|
|
153
|
+
|
|
154
|
+
progress_bar = widgets.FloatProgress(
|
|
155
|
+
value=0, min=0, max=100,
|
|
156
|
+
description='Progress:',
|
|
157
|
+
bar_style='info',
|
|
158
|
+
style={'bar_color': '#00ff00'},
|
|
159
|
+
layout=widgets.Layout(width='100%', height='30px')
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
status_label = widgets.HTML(
|
|
163
|
+
value="<b style='color:#0066cc'>Starting...</b>"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
display(widgets.VBox([progress_bar, status_label]))
|
|
167
|
+
|
|
168
|
+
monitor = ResourceMonitor()
|
|
169
|
+
start_time = time.time()
|
|
170
|
+
|
|
171
|
+
return progress_bar, status_label, monitor, start_time
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def update_progress(progress_bar, status_label, monitor, current, total, start_time, message="Processing"):
|
|
175
|
+
"""
|
|
176
|
+
Update progress bar with ETA, CPU%, RAM
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
progress_bar: Progress widget
|
|
180
|
+
status_label: Status HTML widget
|
|
181
|
+
monitor: ResourceMonitor instance
|
|
182
|
+
current: Current iteration (0-based)
|
|
183
|
+
total: Total iterations
|
|
184
|
+
start_time: Start timestamp
|
|
185
|
+
message: Status message
|
|
186
|
+
"""
|
|
187
|
+
progress = (current / total) * 100
|
|
188
|
+
progress_bar.value = progress
|
|
189
|
+
|
|
190
|
+
# Calculate ETA
|
|
191
|
+
elapsed = time.time() - start_time
|
|
192
|
+
if current > 0:
|
|
193
|
+
eta_seconds = (elapsed / current) * (total - current)
|
|
194
|
+
eta_str = format_time(eta_seconds)
|
|
195
|
+
else:
|
|
196
|
+
eta_str = "calculating..."
|
|
197
|
+
|
|
198
|
+
# Get resources
|
|
199
|
+
cpu = monitor.get_cpu_percent()
|
|
200
|
+
ram_mb, ram_pct = monitor.get_memory_info()
|
|
201
|
+
|
|
202
|
+
# Update status
|
|
203
|
+
status_label.value = (
|
|
204
|
+
f"<b style='color:#0066cc'>{message} ({current}/{total})</b><br>"
|
|
205
|
+
f"<span style='color:#666'>ETA: {eta_str} | CPU: {cpu}% | RAM: {ram_mb}MB ({ram_pct}%)</span>"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def format_time(seconds):
|
|
210
|
+
"""Format seconds to human readable time"""
|
|
211
|
+
if seconds < 60:
|
|
212
|
+
return f"{int(seconds)}s"
|
|
213
|
+
elif seconds < 3600:
|
|
214
|
+
return f"{int(seconds // 60)}m {int(seconds % 60)}s"
|
|
215
|
+
else:
|
|
216
|
+
hours = int(seconds // 3600)
|
|
217
|
+
minutes = int((seconds % 3600) // 60)
|
|
218
|
+
return f"{hours}h {minutes}m"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# ============================================================
|
|
222
|
+
# API HELPER - AUTOMATIC NORMALIZATION
|
|
223
|
+
# ============================================================
|
|
224
|
+
class APIHelper:
|
|
225
|
+
"""
|
|
226
|
+
Normalizes API responses to consistent format
|
|
227
|
+
Handles: dict, DataFrame, or None
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
@staticmethod
|
|
231
|
+
def normalize_response(response, debug=False):
|
|
232
|
+
"""
|
|
233
|
+
Convert API response to dict format
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
dict with 'data' key or None
|
|
237
|
+
"""
|
|
238
|
+
if response is None:
|
|
239
|
+
if debug:
|
|
240
|
+
print("[APIHelper] Response is None")
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
# Case 1: Dict with 'data' key
|
|
244
|
+
if isinstance(response, dict):
|
|
245
|
+
if 'data' in response:
|
|
246
|
+
if debug:
|
|
247
|
+
print(f"[APIHelper] Dict response: {len(response['data'])} records")
|
|
248
|
+
return response
|
|
249
|
+
else:
|
|
250
|
+
if debug:
|
|
251
|
+
print("[APIHelper] Dict without 'data' key")
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
# Case 2: DataFrame - convert to dict
|
|
255
|
+
if isinstance(response, pd.DataFrame):
|
|
256
|
+
if response.empty:
|
|
257
|
+
if debug:
|
|
258
|
+
print("[APIHelper] Empty DataFrame")
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
records = response.to_dict('records')
|
|
262
|
+
if debug:
|
|
263
|
+
print(f"[APIHelper] DataFrame converted: {len(records)} records")
|
|
264
|
+
return {'data': records, 'status': 'success'}
|
|
265
|
+
|
|
266
|
+
# Case 3: Unknown type
|
|
267
|
+
if debug:
|
|
268
|
+
print(f"[APIHelper] Unexpected type: {type(response)}")
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# ============================================================
|
|
273
|
+
# API MANAGER
|
|
274
|
+
# ============================================================
|
|
275
|
+
class APIManager:
|
|
276
|
+
"""Centralized API key management"""
|
|
45
277
|
_api_key = None
|
|
46
278
|
_methods = {}
|
|
47
279
|
|
|
48
280
|
@classmethod
|
|
49
281
|
def initialize(cls, api_key):
|
|
50
|
-
"""
|
|
282
|
+
"""Initialize API with key"""
|
|
51
283
|
if not api_key:
|
|
52
284
|
raise ValueError("API key cannot be empty")
|
|
53
285
|
cls._api_key = api_key
|
|
54
286
|
ivol.setLoginParams(apiKey=api_key)
|
|
55
|
-
print(f"[API] Initialized
|
|
287
|
+
print(f"[API] Initialized: {api_key[:10]}...{api_key[-5:]}")
|
|
56
288
|
|
|
57
289
|
@classmethod
|
|
58
290
|
def get_method(cls, endpoint):
|
|
59
|
-
"""
|
|
60
|
-
Get API method with automatic key injection
|
|
61
|
-
|
|
62
|
-
Args:
|
|
63
|
-
endpoint: API endpoint path (e.g. '/equities/eod/stock-opts-by-param')
|
|
64
|
-
|
|
65
|
-
Returns:
|
|
66
|
-
Callable API method
|
|
67
|
-
"""
|
|
291
|
+
"""Get API method with cached instances"""
|
|
68
292
|
if cls._api_key is None:
|
|
69
|
-
# Auto-initialize from environment if not set
|
|
70
293
|
api_key = os.getenv("API_KEY")
|
|
71
294
|
if not api_key:
|
|
72
|
-
raise ValueError(
|
|
73
|
-
"API key not initialized. Call init_api(key) first or set API_KEY environment variable"
|
|
74
|
-
)
|
|
295
|
+
raise ValueError("API key not set. Call init_api(key) first")
|
|
75
296
|
cls.initialize(api_key)
|
|
76
297
|
|
|
77
|
-
# Cache methods to avoid recreation
|
|
78
298
|
if endpoint not in cls._methods:
|
|
79
|
-
# Re-set login params before creating method
|
|
80
299
|
ivol.setLoginParams(apiKey=cls._api_key)
|
|
81
300
|
cls._methods[endpoint] = ivol.setMethod(endpoint)
|
|
82
301
|
|
|
83
302
|
return cls._methods[endpoint]
|
|
84
|
-
|
|
85
|
-
@classmethod
|
|
86
|
-
def is_initialized(cls):
|
|
87
|
-
"""Check if API is initialized"""
|
|
88
|
-
return cls._api_key is not None
|
|
89
303
|
|
|
90
304
|
|
|
91
|
-
#
|
|
305
|
+
# ============================================================
|
|
306
|
+
# PUBLIC API FUNCTIONS
|
|
307
|
+
# ============================================================
|
|
92
308
|
def init_api(api_key=None):
|
|
93
309
|
"""
|
|
94
|
-
Initialize IVolatility API
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
api_key: API key string. If None, tries to load from API_KEY env variable
|
|
310
|
+
Initialize IVolatility API
|
|
98
311
|
|
|
99
312
|
Example:
|
|
100
313
|
init_api("your-api-key")
|
|
101
314
|
# or
|
|
102
|
-
init_api() # Auto-
|
|
315
|
+
init_api() # Auto-load from API_KEY env variable
|
|
103
316
|
"""
|
|
104
317
|
if api_key is None:
|
|
105
318
|
api_key = os.getenv("API_KEY")
|
|
106
319
|
APIManager.initialize(api_key)
|
|
107
320
|
|
|
108
321
|
|
|
109
|
-
def
|
|
322
|
+
def api_call(endpoint, debug=False, **kwargs):
|
|
110
323
|
"""
|
|
111
|
-
|
|
324
|
+
Make API call with automatic response normalization
|
|
112
325
|
|
|
113
326
|
Args:
|
|
114
327
|
endpoint: API endpoint path
|
|
328
|
+
debug: Enable debug output (prints full URL with API key)
|
|
329
|
+
**kwargs: API parameters
|
|
115
330
|
|
|
116
331
|
Returns:
|
|
117
|
-
|
|
332
|
+
dict with 'data' key or None
|
|
118
333
|
|
|
119
334
|
Example:
|
|
120
|
-
|
|
121
|
-
data =
|
|
335
|
+
# Automatic handling of dict or DataFrame
|
|
336
|
+
data = api_call('/equities/eod/stock-prices',
|
|
337
|
+
symbol='AAPL',
|
|
338
|
+
from_='2024-01-01',
|
|
339
|
+
debug=True)
|
|
340
|
+
|
|
341
|
+
if data:
|
|
342
|
+
df = pd.DataFrame(data['data'])
|
|
122
343
|
"""
|
|
123
|
-
|
|
344
|
+
try:
|
|
345
|
+
# Build full URL for debugging
|
|
346
|
+
if debug and APIManager._api_key:
|
|
347
|
+
base_url = "https://restapi.ivolatility.com"
|
|
348
|
+
|
|
349
|
+
# Convert Python parameter names to API parameter names
|
|
350
|
+
# from_ -> from, to_ -> to
|
|
351
|
+
url_params = {}
|
|
352
|
+
for key, value in kwargs.items():
|
|
353
|
+
# Remove trailing underscore for reserved Python keywords
|
|
354
|
+
clean_key = key.rstrip('_') if key.endswith('_') else key
|
|
355
|
+
url_params[clean_key] = value
|
|
356
|
+
|
|
357
|
+
params_str = "&".join([f"{k}={v}" for k, v in url_params.items()])
|
|
358
|
+
full_url = f"{base_url}{endpoint}?apiKey={APIManager._api_key}&{params_str}"
|
|
359
|
+
print(f"\n[API] Full URL:")
|
|
360
|
+
print(f"[API] {full_url}\n")
|
|
361
|
+
|
|
362
|
+
method = APIManager.get_method(endpoint)
|
|
363
|
+
response = method(**kwargs)
|
|
364
|
+
|
|
365
|
+
normalized = APIHelper.normalize_response(response, debug=debug)
|
|
366
|
+
|
|
367
|
+
if normalized is None and debug:
|
|
368
|
+
print(f"[api_call] ❌ Failed to get data")
|
|
369
|
+
print(f"[api_call] Endpoint: {endpoint}")
|
|
370
|
+
print(f"[api_call] Params: {kwargs}")
|
|
371
|
+
|
|
372
|
+
return normalized
|
|
373
|
+
|
|
374
|
+
except Exception as e:
|
|
375
|
+
if debug:
|
|
376
|
+
print(f"[api_call] ❌ Exception: {e}")
|
|
377
|
+
print(f"[api_call] Endpoint: {endpoint}")
|
|
378
|
+
print(f"[api_call] Params: {kwargs}")
|
|
379
|
+
return None
|
|
124
380
|
|
|
125
381
|
|
|
382
|
+
# ============================================================
|
|
383
|
+
# BACKTEST RESULTS
|
|
384
|
+
# ============================================================
|
|
126
385
|
class BacktestResults:
|
|
127
|
-
"""
|
|
128
|
-
|
|
129
|
-
ANY strategy must return this format
|
|
130
|
-
"""
|
|
386
|
+
"""Universal container for backtest results"""
|
|
387
|
+
|
|
131
388
|
def __init__(self,
|
|
132
|
-
equity_curve,
|
|
133
|
-
equity_dates,
|
|
134
|
-
trades,
|
|
135
|
-
initial_capital,
|
|
136
|
-
config,
|
|
137
|
-
benchmark_prices=None,
|
|
138
|
-
benchmark_symbol='SPY',
|
|
139
|
-
daily_returns=None,
|
|
140
|
-
debug_info=None):
|
|
389
|
+
equity_curve,
|
|
390
|
+
equity_dates,
|
|
391
|
+
trades,
|
|
392
|
+
initial_capital,
|
|
393
|
+
config,
|
|
394
|
+
benchmark_prices=None,
|
|
395
|
+
benchmark_symbol='SPY',
|
|
396
|
+
daily_returns=None,
|
|
397
|
+
debug_info=None):
|
|
141
398
|
|
|
142
399
|
self.equity_curve = equity_curve
|
|
143
400
|
self.equity_dates = equity_dates
|
|
@@ -149,7 +406,6 @@ class BacktestResults:
|
|
|
149
406
|
self.benchmark_symbol = benchmark_symbol
|
|
150
407
|
self.debug_info = debug_info if debug_info else []
|
|
151
408
|
|
|
152
|
-
# Calculate daily returns if not provided
|
|
153
409
|
if daily_returns is None and len(equity_curve) > 1:
|
|
154
410
|
self.daily_returns = [
|
|
155
411
|
(equity_curve[i] - equity_curve[i-1]) / equity_curve[i-1]
|
|
@@ -158,7 +414,6 @@ class BacktestResults:
|
|
|
158
414
|
else:
|
|
159
415
|
self.daily_returns = daily_returns if daily_returns else []
|
|
160
416
|
|
|
161
|
-
# Calculate max drawdown
|
|
162
417
|
self.max_drawdown = self._calculate_max_drawdown()
|
|
163
418
|
|
|
164
419
|
def _calculate_max_drawdown(self):
|
|
@@ -169,11 +424,12 @@ class BacktestResults:
|
|
|
169
424
|
return abs(np.min(drawdowns))
|
|
170
425
|
|
|
171
426
|
|
|
427
|
+
# ============================================================
|
|
428
|
+
# BACKTEST ANALYZER
|
|
429
|
+
# ============================================================
|
|
172
430
|
class BacktestAnalyzer:
|
|
173
|
-
"""
|
|
174
|
-
|
|
175
|
-
Works with any BacktestResults object
|
|
176
|
-
"""
|
|
431
|
+
"""Calculate 30+ metrics from BacktestResults"""
|
|
432
|
+
|
|
177
433
|
def __init__(self, results):
|
|
178
434
|
self.results = results
|
|
179
435
|
self.metrics = {}
|
|
@@ -182,23 +438,21 @@ class BacktestAnalyzer:
|
|
|
182
438
|
"""Calculate all available metrics"""
|
|
183
439
|
r = self.results
|
|
184
440
|
|
|
185
|
-
#
|
|
441
|
+
# Profitability
|
|
186
442
|
self.metrics['total_pnl'] = r.final_capital - r.initial_capital
|
|
187
443
|
self.metrics['total_return'] = (self.metrics['total_pnl'] / r.initial_capital) * 100
|
|
188
444
|
|
|
189
|
-
# CAGR -
|
|
445
|
+
# CAGR with zero-division protection
|
|
190
446
|
if len(r.equity_dates) > 0:
|
|
191
447
|
start_date = min(r.equity_dates)
|
|
192
448
|
end_date = max(r.equity_dates)
|
|
193
449
|
days_diff = (end_date - start_date).days
|
|
194
450
|
|
|
195
|
-
# PROTECTION: если даты одинаковые или разница < 1 день
|
|
196
451
|
if days_diff <= 0:
|
|
197
452
|
self.metrics['cagr'] = 0
|
|
198
453
|
self.metrics['show_cagr'] = False
|
|
199
454
|
else:
|
|
200
455
|
years = days_diff / 365.25
|
|
201
|
-
|
|
202
456
|
if years >= 1.0:
|
|
203
457
|
self.metrics['cagr'] = ((r.final_capital / r.initial_capital) ** (1/years) - 1) * 100
|
|
204
458
|
self.metrics['show_cagr'] = True
|
|
@@ -438,8 +692,11 @@ class BacktestAnalyzer:
|
|
|
438
692
|
return min(exposure_pct, 100.0)
|
|
439
693
|
|
|
440
694
|
|
|
695
|
+
# ============================================================
|
|
696
|
+
# RESULTS REPORTER
|
|
697
|
+
# ============================================================
|
|
441
698
|
class ResultsReporter:
|
|
442
|
-
"""
|
|
699
|
+
"""Print comprehensive metrics report"""
|
|
443
700
|
|
|
444
701
|
@staticmethod
|
|
445
702
|
def print_full_report(analyzer):
|
|
@@ -451,17 +708,15 @@ class ResultsReporter:
|
|
|
451
708
|
print("="*80)
|
|
452
709
|
print()
|
|
453
710
|
|
|
454
|
-
# PRINT DEBUG INFO IF AVAILABLE
|
|
455
711
|
if hasattr(r, 'debug_info') and len(r.debug_info) > 0:
|
|
456
712
|
print("DEBUG INFORMATION")
|
|
457
713
|
print("-"*80)
|
|
458
|
-
for debug_msg in r.debug_info[:10]:
|
|
714
|
+
for debug_msg in r.debug_info[:10]:
|
|
459
715
|
print(debug_msg)
|
|
460
716
|
if len(r.debug_info) > 10:
|
|
461
|
-
print(f"... and {len(r.debug_info) - 10} more
|
|
717
|
+
print(f"... and {len(r.debug_info) - 10} more messages")
|
|
462
718
|
print()
|
|
463
719
|
|
|
464
|
-
# Profitability
|
|
465
720
|
print("PROFITABILITY METRICS")
|
|
466
721
|
print("-"*80)
|
|
467
722
|
print(f"Initial Capital: ${r.initial_capital:>15,.2f}")
|
|
@@ -475,7 +730,6 @@ class ResultsReporter:
|
|
|
475
730
|
print(f"Annualized Return: {m['cagr']:>15.2f}% (extrapolated to 1 year)")
|
|
476
731
|
print()
|
|
477
732
|
|
|
478
|
-
# Risk
|
|
479
733
|
print("RISK METRICS")
|
|
480
734
|
print("-"*80)
|
|
481
735
|
print(f"Sharpe Ratio: {m['sharpe']:>15.2f} (>1 good, >2 excellent)")
|
|
@@ -509,16 +763,15 @@ class ResultsReporter:
|
|
|
509
763
|
# Warning for unrealistic results
|
|
510
764
|
if abs(m['total_return']) > 200 or m['volatility'] > 150:
|
|
511
765
|
print()
|
|
512
|
-
print("UNREALISTIC RESULTS DETECTED:")
|
|
766
|
+
print("⚠️ UNREALISTIC RESULTS DETECTED:")
|
|
513
767
|
if abs(m['total_return']) > 200:
|
|
514
|
-
print(f" Total return {m['total_return']:.1f}% is extremely high")
|
|
768
|
+
print(f" • Total return {m['total_return']:.1f}% is extremely high")
|
|
515
769
|
if m['volatility'] > 150:
|
|
516
|
-
print(f" Volatility {m['volatility']:.1f}% is higher than leveraged ETFs")
|
|
517
|
-
print(" Review configuration before trusting results")
|
|
770
|
+
print(f" • Volatility {m['volatility']:.1f}% is higher than leveraged ETFs")
|
|
771
|
+
print(" → Review configuration before trusting results")
|
|
518
772
|
|
|
519
773
|
print()
|
|
520
774
|
|
|
521
|
-
# Efficiency
|
|
522
775
|
print("EFFICIENCY METRICS")
|
|
523
776
|
print("-"*80)
|
|
524
777
|
if m['recovery_factor'] != 0:
|
|
@@ -527,7 +780,6 @@ class ResultsReporter:
|
|
|
527
780
|
print(f"Exposure Time: {m['exposure_time']:>15.1f}% (time in market)")
|
|
528
781
|
print()
|
|
529
782
|
|
|
530
|
-
# Trading stats
|
|
531
783
|
print("TRADING STATISTICS")
|
|
532
784
|
print("-"*80)
|
|
533
785
|
print(f"Total Trades: {m['total_trades']:>15}")
|
|
@@ -548,8 +800,11 @@ class ResultsReporter:
|
|
|
548
800
|
print("="*80)
|
|
549
801
|
|
|
550
802
|
|
|
803
|
+
# ============================================================
|
|
804
|
+
# CHART GENERATOR
|
|
805
|
+
# ============================================================
|
|
551
806
|
class ChartGenerator:
|
|
552
|
-
"""
|
|
807
|
+
"""Generate 6 professional charts"""
|
|
553
808
|
|
|
554
809
|
@staticmethod
|
|
555
810
|
def create_all_charts(analyzer, filename='backtest_results.png'):
|
|
@@ -659,8 +914,11 @@ class ChartGenerator:
|
|
|
659
914
|
print(f"Chart saved: {filename}")
|
|
660
915
|
|
|
661
916
|
|
|
917
|
+
# ============================================================
|
|
918
|
+
# RESULTS EXPORTER
|
|
919
|
+
# ============================================================
|
|
662
920
|
class ResultsExporter:
|
|
663
|
-
"""
|
|
921
|
+
"""Export results to CSV files"""
|
|
664
922
|
|
|
665
923
|
@staticmethod
|
|
666
924
|
def export_all(analyzer, prefix='backtest'):
|
|
@@ -671,28 +929,27 @@ class ResultsExporter:
|
|
|
671
929
|
print("No trades to export")
|
|
672
930
|
return
|
|
673
931
|
|
|
674
|
-
#
|
|
932
|
+
# Trades
|
|
675
933
|
trades_df = pd.DataFrame(r.trades)
|
|
676
934
|
trades_df['entry_date'] = pd.to_datetime(trades_df['entry_date']).dt.strftime('%Y-%m-%d')
|
|
677
935
|
trades_df['exit_date'] = pd.to_datetime(trades_df['exit_date']).dt.strftime('%Y-%m-%d')
|
|
678
936
|
trades_df.to_csv(f'{prefix}_trades.csv', index=False)
|
|
679
|
-
print(f"
|
|
937
|
+
print(f"Exported: {prefix}_trades.csv")
|
|
680
938
|
|
|
681
|
-
#
|
|
939
|
+
# Equity
|
|
682
940
|
equity_df = pd.DataFrame({
|
|
683
941
|
'date': pd.to_datetime(r.equity_dates).strftime('%Y-%m-%d'),
|
|
684
942
|
'equity': r.equity_curve
|
|
685
943
|
})
|
|
686
944
|
equity_df.to_csv(f'{prefix}_equity.csv', index=False)
|
|
687
|
-
print(f"
|
|
945
|
+
print(f"Exported: {prefix}_equity.csv")
|
|
688
946
|
|
|
689
|
-
#
|
|
947
|
+
# Summary
|
|
690
948
|
with open(f'{prefix}_summary.txt', 'w') as f:
|
|
691
949
|
f.write("BACKTEST SUMMARY\n")
|
|
692
950
|
f.write("="*70 + "\n\n")
|
|
693
951
|
f.write(f"Strategy: {r.config.get('strategy_name', 'Unknown')}\n")
|
|
694
952
|
f.write(f"Period: {r.config.get('start_date', 'N/A')} to {r.config.get('end_date', 'N/A')}\n\n")
|
|
695
|
-
|
|
696
953
|
f.write("PERFORMANCE\n")
|
|
697
954
|
f.write("-"*70 + "\n")
|
|
698
955
|
f.write(f"Initial Capital: ${r.initial_capital:,.2f}\n")
|
|
@@ -703,7 +960,7 @@ class ResultsExporter:
|
|
|
703
960
|
f.write(f"Win Rate: {m['win_rate']:.2f}%\n")
|
|
704
961
|
f.write(f"Total Trades: {m['total_trades']}\n")
|
|
705
962
|
|
|
706
|
-
print(f"
|
|
963
|
+
print(f"Exported: {prefix}_summary.txt")
|
|
707
964
|
|
|
708
965
|
|
|
709
966
|
# ============================================================
|
|
@@ -718,32 +975,8 @@ def run_backtest(strategy_function, config,
|
|
|
718
975
|
"""
|
|
719
976
|
Run complete backtest with one command
|
|
720
977
|
|
|
721
|
-
Args:
|
|
722
|
-
strategy_function: Your strategy function that returns BacktestResults
|
|
723
|
-
config: Configuration dictionary for the strategy
|
|
724
|
-
print_report: Print full metrics report (default: True)
|
|
725
|
-
create_charts: Generate 6 charts (default: True)
|
|
726
|
-
export_results: Export to CSV files (default: True)
|
|
727
|
-
chart_filename: Name for chart file (default: 'backtest_results.png')
|
|
728
|
-
export_prefix: Prefix for exported files (default: 'backtest')
|
|
729
|
-
|
|
730
|
-
Returns:
|
|
731
|
-
BacktestAnalyzer object with all metrics
|
|
732
|
-
|
|
733
978
|
Example:
|
|
734
|
-
from ivolatility_backtesting import run_backtest
|
|
735
|
-
|
|
736
|
-
def my_strategy(config):
|
|
737
|
-
# ... your strategy logic
|
|
738
|
-
return BacktestResults(...)
|
|
739
|
-
|
|
740
|
-
CONFIG = {'initial_capital': 100000, ...}
|
|
741
|
-
|
|
742
|
-
# Run everything with one command!
|
|
743
979
|
analyzer = run_backtest(my_strategy, CONFIG)
|
|
744
|
-
|
|
745
|
-
# Access metrics
|
|
746
|
-
print(f"Sharpe: {analyzer.metrics['sharpe']:.2f}")
|
|
747
980
|
"""
|
|
748
981
|
|
|
749
982
|
print("="*80)
|
|
@@ -754,60 +987,36 @@ def run_backtest(strategy_function, config,
|
|
|
754
987
|
print(f"Capital: ${config.get('initial_capital', 0):,.0f}")
|
|
755
988
|
print("="*80 + "\n")
|
|
756
989
|
|
|
757
|
-
# Run strategy
|
|
758
990
|
results = strategy_function(config)
|
|
759
991
|
|
|
760
|
-
# Calculate metrics
|
|
761
992
|
print("\n[*] Calculating metrics...")
|
|
762
993
|
analyzer = BacktestAnalyzer(results)
|
|
763
994
|
analyzer.calculate_all_metrics()
|
|
764
995
|
|
|
765
|
-
# Print report
|
|
766
996
|
if print_report:
|
|
767
997
|
print("\n" + "="*80)
|
|
768
998
|
ResultsReporter.print_full_report(analyzer)
|
|
769
999
|
|
|
770
|
-
# Create charts
|
|
771
1000
|
if create_charts and len(results.trades) > 0:
|
|
772
1001
|
print(f"\n[*] Creating charts: {chart_filename}")
|
|
773
1002
|
try:
|
|
774
1003
|
ChartGenerator.create_all_charts(analyzer, chart_filename)
|
|
775
|
-
print(f"[OK] Charts saved: {chart_filename}")
|
|
776
1004
|
except Exception as e:
|
|
777
1005
|
print(f"[ERROR] Chart creation failed: {e}")
|
|
778
|
-
elif create_charts and len(results.trades) == 0:
|
|
779
|
-
print("\n[!] No trades - skipping charts")
|
|
780
1006
|
|
|
781
|
-
# Export results
|
|
782
1007
|
if export_results and len(results.trades) > 0:
|
|
783
|
-
print(f"\n[*] Exporting results: {export_prefix}_
|
|
1008
|
+
print(f"\n[*] Exporting results: {export_prefix}_*")
|
|
784
1009
|
try:
|
|
785
1010
|
ResultsExporter.export_all(analyzer, export_prefix)
|
|
786
|
-
print(f"[OK] Files exported:")
|
|
787
|
-
print(f" - {export_prefix}_trades.csv")
|
|
788
|
-
print(f" - {export_prefix}_equity.csv")
|
|
789
|
-
print(f" - {export_prefix}_summary.txt")
|
|
790
1011
|
except Exception as e:
|
|
791
1012
|
print(f"[ERROR] Export failed: {e}")
|
|
792
|
-
elif export_results and len(results.trades) == 0:
|
|
793
|
-
print("\n[!] No trades - skipping export")
|
|
794
|
-
|
|
795
|
-
# Final summary
|
|
796
|
-
#print("\n" + "="*80)
|
|
797
|
-
#print(" "*30 + "SUMMARY")
|
|
798
|
-
#print("="*80)
|
|
799
|
-
#print(f"Total Return: {analyzer.metrics['total_return']:>10.2f}%")
|
|
800
|
-
#print(f"Sharpe Ratio: {analyzer.metrics['sharpe']:>10.2f}")
|
|
801
|
-
#print(f"Max Drawdown: {analyzer.metrics['max_drawdown']:>10.2f}%")
|
|
802
|
-
#print(f"Win Rate: {analyzer.metrics['win_rate']:>10.1f}%")
|
|
803
|
-
#print(f"Total Trades: {analyzer.metrics['total_trades']:>10}")
|
|
804
|
-
#print(f"Profit Factor: {analyzer.metrics['profit_factor']:>10.2f}")
|
|
805
|
-
#print("="*80)
|
|
806
1013
|
|
|
807
1014
|
return analyzer
|
|
808
1015
|
|
|
809
1016
|
|
|
810
|
-
#
|
|
1017
|
+
# ============================================================
|
|
1018
|
+
# EXPORTS
|
|
1019
|
+
# ============================================================
|
|
811
1020
|
__all__ = [
|
|
812
1021
|
'BacktestResults',
|
|
813
1022
|
'BacktestAnalyzer',
|
|
@@ -816,6 +1025,11 @@ __all__ = [
|
|
|
816
1025
|
'ResultsExporter',
|
|
817
1026
|
'run_backtest',
|
|
818
1027
|
'init_api',
|
|
819
|
-
'
|
|
820
|
-
'
|
|
821
|
-
|
|
1028
|
+
'api_call',
|
|
1029
|
+
'APIHelper',
|
|
1030
|
+
'APIManager',
|
|
1031
|
+
'ResourceMonitor',
|
|
1032
|
+
'create_progress_bar',
|
|
1033
|
+
'update_progress',
|
|
1034
|
+
'format_time'
|
|
1035
|
+
]
|
{ivolatility_backtesting-1.0.1.dist-info → ivolatility_backtesting-1.2.0.dist-info}/METADATA
RENAMED
|
@@ -1,70 +1,72 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
2
|
-
Name: ivolatility_backtesting
|
|
3
|
-
Version: 1.0
|
|
4
|
-
Summary: A universal backtesting framework for financial strategies using the IVolatility API.
|
|
5
|
-
Author-email: IVolatility <support@ivolatility.com>
|
|
6
|
-
Project-URL: Homepage, https://ivolatility.com
|
|
7
|
-
Keywords: backtesting,finance,trading,ivolatility
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
-
Classifier: Operating System :: OS Independent
|
|
16
|
-
Requires-Python: >=3.8
|
|
17
|
-
Description-Content-Type: text/markdown
|
|
18
|
-
License-File: LICENSE
|
|
19
|
-
Requires-Dist: pandas>=1.5.0
|
|
20
|
-
Requires-Dist: numpy>=1.21.0
|
|
21
|
-
Requires-Dist: matplotlib>=3.5.0
|
|
22
|
-
Requires-Dist: seaborn>=0.11.0
|
|
23
|
-
Requires-Dist: ivolatility>=1.8.2
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ivolatility_backtesting
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: A universal backtesting framework for financial strategies using the IVolatility API.
|
|
5
|
+
Author-email: IVolatility <support@ivolatility.com>
|
|
6
|
+
Project-URL: Homepage, https://ivolatility.com
|
|
7
|
+
Keywords: backtesting,finance,trading,ivolatility
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.8
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: pandas>=1.5.0
|
|
20
|
+
Requires-Dist: numpy>=1.21.0
|
|
21
|
+
Requires-Dist: matplotlib>=3.5.0
|
|
22
|
+
Requires-Dist: seaborn>=0.11.0
|
|
23
|
+
Requires-Dist: ivolatility>=1.8.2
|
|
24
|
+
Requires-Dist: psutil>=7.1.0
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# IVolatility Backtesting
|
|
28
|
+
A universal backtesting framework for financial strategies using the IVolatility API.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
```bash
|
|
32
|
+
pip install ivolatility_backtesting
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
```python
|
|
37
|
+
from ivolatility_backtesting import run_backtest, init_api
|
|
38
|
+
|
|
39
|
+
# Initialize API
|
|
40
|
+
init_api("your-api-key")
|
|
41
|
+
|
|
42
|
+
# Define your strategy
|
|
43
|
+
def my_strategy(config):
|
|
44
|
+
# Strategy logic
|
|
45
|
+
return BacktestResults(
|
|
46
|
+
equity_curve=[100000, 110000],
|
|
47
|
+
equity_dates=["2023-01-01", "2023-01-02"],
|
|
48
|
+
trades=[{"pnl": 1000, "entry_date": "2023-01-01", "exit_date": "2023-01-02"}],
|
|
49
|
+
initial_capital=100000,
|
|
50
|
+
config=config
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Run backtest
|
|
54
|
+
CONFIG = {
|
|
55
|
+
"initial_capital": 100000,
|
|
56
|
+
"start_date": "2023-01-01",
|
|
57
|
+
"end_date": "2024-01-01",
|
|
58
|
+
"strategy_name": "My Strategy"
|
|
59
|
+
}
|
|
60
|
+
analyzer = run_backtest(my_strategy, CONFIG)
|
|
61
|
+
|
|
62
|
+
# Access metrics
|
|
63
|
+
print(f"Sharpe Ratio: {analyzer.metrics['sharpe']:.2f}")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Requirements
|
|
67
|
+
- Python >= 3.8
|
|
68
|
+
- pandas >= 1.5.0
|
|
69
|
+
- numpy >= 1.21.0
|
|
70
|
+
- matplotlib >= 3.5.0
|
|
71
|
+
- seaborn >= 0.11.0
|
|
72
|
+
- ivolatility >= 1.8.2
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ivolatility_backtesting/__init__.py,sha256=abZYqTZwvzgSdSs55g3_zU8mtbNKveUndoDgKU8tnIo,577
|
|
2
|
+
ivolatility_backtesting/ivolatility_backtesting.py,sha256=_lo2QrdWTf8IVpp4AIIGw7_t88GhbSeHRAT4KEcwmBw,40916
|
|
3
|
+
ivolatility_backtesting-1.2.0.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
ivolatility_backtesting-1.2.0.dist-info/METADATA,sha256=SRFqAyNI-qOs2CeX3DZF0kJwbVnsQQMbOCkPs2LNOKc,2052
|
|
5
|
+
ivolatility_backtesting-1.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
6
|
+
ivolatility_backtesting-1.2.0.dist-info/top_level.txt,sha256=Qv3irUBntr8b11WIKNN6zzCSguwaWC4nWR-ZKq8NsjY,24
|
|
7
|
+
ivolatility_backtesting-1.2.0.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
ivolatility_backtesting/__init__.py,sha256=-VS3l4sUlmlMjVDwPY2BoXOVoYa5oVGYH9cscK5NLzw,395
|
|
2
|
-
ivolatility_backtesting/ivolatility_backtesting.py,sha256=-rm9jhQeHJ2i-EDHcw3FfZ9DdM6pyFPeAjk3Nsnxp34,34012
|
|
3
|
-
ivolatility_backtesting-1.0.1.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
ivolatility_backtesting-1.0.1.dist-info/METADATA,sha256=XQwCx8mdYnQ-Pd0y4dT93A28ptcdwFSUUka4jFsXzDc,2071
|
|
5
|
-
ivolatility_backtesting-1.0.1.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
|
6
|
-
ivolatility_backtesting-1.0.1.dist-info/top_level.txt,sha256=Qv3irUBntr8b11WIKNN6zzCSguwaWC4nWR-ZKq8NsjY,24
|
|
7
|
-
ivolatility_backtesting-1.0.1.dist-info/RECORD,,
|
{ivolatility_backtesting-1.0.1.dist-info → ivolatility_backtesting-1.2.0.dist-info/licenses}/LICENSE
RENAMED
|
File without changes
|
{ivolatility_backtesting-1.0.1.dist-info → ivolatility_backtesting-1.2.0.dist-info}/top_level.txt
RENAMED
|
File without changes
|