ivolatility-backtesting 1.1.0__tar.gz → 1.2.0__tar.gz

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.

@@ -1,70 +1,72 @@
1
- Metadata-Version: 2.1
2
- Name: ivolatility_backtesting
3
- Version: 1.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
- # IVolatility Backtesting
26
- A universal backtesting framework for financial strategies using the IVolatility API.
27
-
28
- ## Installation
29
- ```bash
30
- pip install ivolatility_backtesting
31
- ```
32
-
33
- ## Usage
34
- ```python
35
- from ivolatility_backtesting import run_backtest, init_api
36
-
37
- # Initialize API
38
- init_api("your-api-key")
39
-
40
- # Define your strategy
41
- def my_strategy(config):
42
- # Strategy logic
43
- return BacktestResults(
44
- equity_curve=[100000, 110000],
45
- equity_dates=["2023-01-01", "2023-01-02"],
46
- trades=[{"pnl": 1000, "entry_date": "2023-01-01", "exit_date": "2023-01-02"}],
47
- initial_capital=100000,
48
- config=config
49
- )
50
-
51
- # Run backtest
52
- CONFIG = {
53
- "initial_capital": 100000,
54
- "start_date": "2023-01-01",
55
- "end_date": "2024-01-01",
56
- "strategy_name": "My Strategy"
57
- }
58
- analyzer = run_backtest(my_strategy, CONFIG)
59
-
60
- # Access metrics
61
- print(f"Sharpe Ratio: {analyzer.metrics['sharpe']:.2f}")
62
- ```
63
-
64
- ## Requirements
65
- - Python >= 3.8
66
- - pandas >= 1.5.0
67
- - numpy >= 1.21.0
68
- - matplotlib >= 3.5.0
69
- - seaborn >= 0.11.0
70
- - ivolatility >= 1.8.2
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
@@ -1,17 +1,23 @@
1
1
  from .ivolatility_backtesting import (
2
2
  BacktestResults, BacktestAnalyzer, ResultsReporter,
3
3
  ChartGenerator, ResultsExporter, run_backtest,
4
- init_api, get_api_method, APIManager
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
- 'get_api_method',
16
- 'APIManager'
16
+ 'api_call',
17
+ 'APIHelper',
18
+ 'APIManager',
19
+ 'ResourceMonitor',
20
+ 'create_progress_bar',
21
+ 'update_progress',
22
+ 'format_time'
17
23
  ]
@@ -1,23 +1,25 @@
1
1
  """
2
- ivolatility_backtesting.py - UPDATED VERSION
3
- Universal Backtest Framework with API Response Normalization
2
+ ivolatility_backtesting.py - UNIVERSAL BACKTEST FRAMEWORK
3
+ Version 2.0 - With DataFrame/Dict Auto-Normalization
4
4
 
5
- NEW FEATURES:
6
- - APIHelper class for automatic response normalization
7
- - Handles both dict and DataFrame responses from IVolatility API
8
- - Safe data extraction with proper error handling
9
- - Unified interface for all API calls
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()
10
11
 
11
12
  Usage:
12
13
  from ivolatility_backtesting import *
13
14
 
14
- # Initialize API once
15
+ # Initialize API
15
16
  init_api(os.getenv("API_KEY"))
16
17
 
17
- # Use API helper for normalized responses
18
+ # Use api_call for normalized responses
18
19
  data = api_call('/equities/eod/stock-prices', symbol='AAPL', from_='2024-01-01')
19
- if data: # Always returns dict or None
20
- df = pd.DataFrame(data)
20
+
21
+ # Run backtest
22
+ analyzer = run_backtest(my_strategy, CONFIG)
21
23
  """
22
24
 
23
25
  import pandas as pd
@@ -27,6 +29,8 @@ import seaborn as sns
27
29
  from datetime import datetime, timedelta
28
30
  import ivolatility as ivol
29
31
  import os
32
+ import time
33
+ import psutil
30
34
 
31
35
  # Set style
32
36
  sns.set_style('darkgrid')
@@ -34,40 +38,217 @@ plt.rcParams['figure.figsize'] = (15, 8)
34
38
 
35
39
 
36
40
  # ============================================================
37
- # API HELPER - NEW!
41
+ # RESOURCE MONITOR - NEW!
42
+ # ============================================================
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
87
+
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
110
+
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()
147
+
148
+ for i in range(total):
149
+ update_progress(progress_bar, status_label, monitor, i, total, start_time)
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
38
223
  # ============================================================
39
224
  class APIHelper:
40
225
  """
41
- Helper class for normalized API responses
42
- Automatically handles both dict and DataFrame responses
226
+ Normalizes API responses to consistent format
227
+ Handles: dict, DataFrame, or None
43
228
  """
44
229
 
45
230
  @staticmethod
46
231
  def normalize_response(response, debug=False):
47
232
  """
48
- Convert API response to consistent dict format
49
-
50
- Args:
51
- response: API response (dict, DataFrame, or other)
52
- debug: Print debug information
233
+ Convert API response to dict format
53
234
 
54
235
  Returns:
55
- dict with 'data' key containing list of records, or None if invalid
236
+ dict with 'data' key or None
56
237
  """
57
238
  if response is None:
58
239
  if debug:
59
240
  print("[APIHelper] Response is None")
60
241
  return None
61
242
 
62
- # Case 1: Already a dict with 'data' key
243
+ # Case 1: Dict with 'data' key
63
244
  if isinstance(response, dict):
64
245
  if 'data' in response:
65
246
  if debug:
66
- print(f"[APIHelper] Dict response with {len(response['data'])} records")
247
+ print(f"[APIHelper] Dict response: {len(response['data'])} records")
67
248
  return response
68
249
  else:
69
250
  if debug:
70
- print("[APIHelper] Dict response without 'data' key")
251
+ print("[APIHelper] Dict without 'data' key")
71
252
  return None
72
253
 
73
254
  # Case 2: DataFrame - convert to dict
@@ -79,73 +260,39 @@ class APIHelper:
79
260
 
80
261
  records = response.to_dict('records')
81
262
  if debug:
82
- print(f"[APIHelper] Converted DataFrame to dict with {len(records)} records")
263
+ print(f"[APIHelper] DataFrame converted: {len(records)} records")
83
264
  return {'data': records, 'status': 'success'}
84
265
 
85
266
  # Case 3: Unknown type
86
267
  if debug:
87
- print(f"[APIHelper] Unexpected response type: {type(response)}")
268
+ print(f"[APIHelper] Unexpected type: {type(response)}")
88
269
  return None
89
-
90
- @staticmethod
91
- def safe_dataframe(response, debug=False):
92
- """
93
- Safely convert API response to DataFrame
94
-
95
- Args:
96
- response: API response (any type)
97
- debug: Print debug information
98
-
99
- Returns:
100
- pandas DataFrame or empty DataFrame if invalid
101
- """
102
- normalized = APIHelper.normalize_response(response, debug=debug)
103
-
104
- if normalized is None or 'data' not in normalized:
105
- if debug:
106
- print("[APIHelper] Cannot create DataFrame - no valid data")
107
- return pd.DataFrame()
108
-
109
- try:
110
- df = pd.DataFrame(normalized['data'])
111
- if debug:
112
- print(f"[APIHelper] Created DataFrame with shape {df.shape}")
113
- return df
114
- except Exception as e:
115
- if debug:
116
- print(f"[APIHelper] DataFrame creation failed: {e}")
117
- return pd.DataFrame()
118
270
 
119
271
 
120
272
  # ============================================================
121
- # GLOBAL API MANAGER (Updated)
273
+ # API MANAGER
122
274
  # ============================================================
123
275
  class APIManager:
124
- """
125
- Centralized API key management for IVolatility API
126
- Now includes response normalization
127
- """
276
+ """Centralized API key management"""
128
277
  _api_key = None
129
278
  _methods = {}
130
279
 
131
280
  @classmethod
132
281
  def initialize(cls, api_key):
133
- """Set API key globally - call this once at startup"""
282
+ """Initialize API with key"""
134
283
  if not api_key:
135
284
  raise ValueError("API key cannot be empty")
136
285
  cls._api_key = api_key
137
286
  ivol.setLoginParams(apiKey=api_key)
138
- print(f"[API] Initialized with key: {api_key[:10]}...{api_key[-5:]}")
287
+ print(f"[API] Initialized: {api_key[:10]}...{api_key[-5:]}")
139
288
 
140
289
  @classmethod
141
290
  def get_method(cls, endpoint):
142
- """Get API method with automatic key injection"""
291
+ """Get API method with cached instances"""
143
292
  if cls._api_key is None:
144
293
  api_key = os.getenv("API_KEY")
145
294
  if not api_key:
146
- raise ValueError(
147
- "API key not initialized. Call init_api(key) first or set API_KEY environment variable"
148
- )
295
+ raise ValueError("API key not set. Call init_api(key) first")
149
296
  cls.initialize(api_key)
150
297
 
151
298
  if endpoint not in cls._methods:
@@ -153,77 +300,91 @@ class APIManager:
153
300
  cls._methods[endpoint] = ivol.setMethod(endpoint)
154
301
 
155
302
  return cls._methods[endpoint]
156
-
157
- @classmethod
158
- def is_initialized(cls):
159
- """Check if API is initialized"""
160
- return cls._api_key is not None
161
303
 
162
304
 
163
- # Public API functions (Updated)
305
+ # ============================================================
306
+ # PUBLIC API FUNCTIONS
307
+ # ============================================================
164
308
  def init_api(api_key=None):
165
- """Initialize IVolatility API with key"""
309
+ """
310
+ Initialize IVolatility API
311
+
312
+ Example:
313
+ init_api("your-api-key")
314
+ # or
315
+ init_api() # Auto-load from API_KEY env variable
316
+ """
166
317
  if api_key is None:
167
318
  api_key = os.getenv("API_KEY")
168
319
  APIManager.initialize(api_key)
169
320
 
170
321
 
171
- def get_api_method(endpoint):
172
- """Get API method for specified endpoint"""
173
- return APIManager.get_method(endpoint)
174
-
175
-
176
322
  def api_call(endpoint, debug=False, **kwargs):
177
323
  """
178
324
  Make API call with automatic response normalization
179
325
 
180
326
  Args:
181
327
  endpoint: API endpoint path
182
- debug: Enable debug output
328
+ debug: Enable debug output (prints full URL with API key)
183
329
  **kwargs: API parameters
184
330
 
185
331
  Returns:
186
- dict with 'data' key (normalized format) or None if error
332
+ dict with 'data' key or None
187
333
 
188
334
  Example:
189
- # Old way (manual handling):
190
- method = get_api_method('/equities/eod/stock-prices')
191
- response = method(symbol='AAPL', from_='2024-01-01')
192
- if isinstance(response, pd.DataFrame):
193
- df = response
194
- elif isinstance(response, dict):
195
- df = pd.DataFrame(response['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)
196
340
 
197
- # New way (automatic):
198
- data = api_call('/equities/eod/stock-prices', symbol='AAPL', from_='2024-01-01')
199
341
  if data:
200
342
  df = pd.DataFrame(data['data'])
201
343
  """
202
344
  try:
203
- method = get_api_method(endpoint)
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)
204
363
  response = method(**kwargs)
205
364
 
206
365
  normalized = APIHelper.normalize_response(response, debug=debug)
207
366
 
208
367
  if normalized is None and debug:
209
- print(f"[api_call] Failed to get valid data from {endpoint}")
210
- print(f"[api_call] Parameters: {kwargs}")
368
+ print(f"[api_call] Failed to get data")
369
+ print(f"[api_call] Endpoint: {endpoint}")
370
+ print(f"[api_call] Params: {kwargs}")
211
371
 
212
372
  return normalized
213
373
 
214
374
  except Exception as e:
215
375
  if debug:
216
- print(f"[api_call] Exception: {e}")
376
+ print(f"[api_call] Exception: {e}")
217
377
  print(f"[api_call] Endpoint: {endpoint}")
218
- print(f"[api_call] Parameters: {kwargs}")
378
+ print(f"[api_call] Params: {kwargs}")
219
379
  return None
220
380
 
221
381
 
222
382
  # ============================================================
223
- # BACKTEST RESULTS (Unchanged)
383
+ # BACKTEST RESULTS
224
384
  # ============================================================
225
385
  class BacktestResults:
226
386
  """Universal container for backtest results"""
387
+
227
388
  def __init__(self,
228
389
  equity_curve,
229
390
  equity_dates,
@@ -264,10 +425,11 @@ class BacktestResults:
264
425
 
265
426
 
266
427
  # ============================================================
267
- # BACKTEST ANALYZER (Unchanged - same as before)
428
+ # BACKTEST ANALYZER
268
429
  # ============================================================
269
430
  class BacktestAnalyzer:
270
- """Universal metrics calculator"""
431
+ """Calculate 30+ metrics from BacktestResults"""
432
+
271
433
  def __init__(self, results):
272
434
  self.results = results
273
435
  self.metrics = {}
@@ -276,11 +438,11 @@ class BacktestAnalyzer:
276
438
  """Calculate all available metrics"""
277
439
  r = self.results
278
440
 
279
- # Basic profitability
441
+ # Profitability
280
442
  self.metrics['total_pnl'] = r.final_capital - r.initial_capital
281
443
  self.metrics['total_return'] = (self.metrics['total_pnl'] / r.initial_capital) * 100
282
444
 
283
- # CAGR with protection
445
+ # CAGR with zero-division protection
284
446
  if len(r.equity_dates) > 0:
285
447
  start_date = min(r.equity_dates)
286
448
  end_date = max(r.equity_dates)
@@ -291,7 +453,6 @@ class BacktestAnalyzer:
291
453
  self.metrics['show_cagr'] = False
292
454
  else:
293
455
  years = days_diff / 365.25
294
-
295
456
  if years >= 1.0:
296
457
  self.metrics['cagr'] = ((r.final_capital / r.initial_capital) ** (1/years) - 1) * 100
297
458
  self.metrics['show_cagr'] = True
@@ -532,11 +693,10 @@ class BacktestAnalyzer:
532
693
 
533
694
 
534
695
  # ============================================================
535
- # RESULTS REPORTER, CHART GENERATOR, RESULTS EXPORTER
536
- # (All unchanged - same as before)
696
+ # RESULTS REPORTER
537
697
  # ============================================================
538
698
  class ResultsReporter:
539
- """Universal results printer"""
699
+ """Print comprehensive metrics report"""
540
700
 
541
701
  @staticmethod
542
702
  def print_full_report(analyzer):
@@ -554,7 +714,7 @@ class ResultsReporter:
554
714
  for debug_msg in r.debug_info[:10]:
555
715
  print(debug_msg)
556
716
  if len(r.debug_info) > 10:
557
- print(f"... and {len(r.debug_info) - 10} more debug messages")
717
+ print(f"... and {len(r.debug_info) - 10} more messages")
558
718
  print()
559
719
 
560
720
  print("PROFITABILITY METRICS")
@@ -600,14 +760,15 @@ class ResultsReporter:
600
760
  print(f"Beta (vs {r.benchmark_symbol}): {m['beta']:>15.2f} (<1 defensive, >1 aggressive)")
601
761
  print(f"R^2 (vs {r.benchmark_symbol}): {m['r_squared']:>15.2f} (market correlation 0-1)")
602
762
 
763
+ # Warning for unrealistic results
603
764
  if abs(m['total_return']) > 200 or m['volatility'] > 150:
604
765
  print()
605
- print("UNREALISTIC RESULTS DETECTED:")
766
+ print("⚠️ UNREALISTIC RESULTS DETECTED:")
606
767
  if abs(m['total_return']) > 200:
607
- print(f" Total return {m['total_return']:.1f}% is extremely high")
768
+ print(f" Total return {m['total_return']:.1f}% is extremely high")
608
769
  if m['volatility'] > 150:
609
- print(f" Volatility {m['volatility']:.1f}% is higher than leveraged ETFs")
610
- 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")
611
772
 
612
773
  print()
613
774
 
@@ -639,8 +800,11 @@ class ResultsReporter:
639
800
  print("="*80)
640
801
 
641
802
 
803
+ # ============================================================
804
+ # CHART GENERATOR
805
+ # ============================================================
642
806
  class ChartGenerator:
643
- """Universal chart creator"""
807
+ """Generate 6 professional charts"""
644
808
 
645
809
  @staticmethod
646
810
  def create_all_charts(analyzer, filename='backtest_results.png'):
@@ -750,8 +914,11 @@ class ChartGenerator:
750
914
  print(f"Chart saved: {filename}")
751
915
 
752
916
 
917
+ # ============================================================
918
+ # RESULTS EXPORTER
919
+ # ============================================================
753
920
  class ResultsExporter:
754
- """Universal results exporter"""
921
+ """Export results to CSV files"""
755
922
 
756
923
  @staticmethod
757
924
  def export_all(analyzer, prefix='backtest'):
@@ -762,25 +929,27 @@ class ResultsExporter:
762
929
  print("No trades to export")
763
930
  return
764
931
 
932
+ # Trades
765
933
  trades_df = pd.DataFrame(r.trades)
766
934
  trades_df['entry_date'] = pd.to_datetime(trades_df['entry_date']).dt.strftime('%Y-%m-%d')
767
935
  trades_df['exit_date'] = pd.to_datetime(trades_df['exit_date']).dt.strftime('%Y-%m-%d')
768
936
  trades_df.to_csv(f'{prefix}_trades.csv', index=False)
769
- print(f"Trades exported: {prefix}_trades.csv")
937
+ print(f"Exported: {prefix}_trades.csv")
770
938
 
939
+ # Equity
771
940
  equity_df = pd.DataFrame({
772
941
  'date': pd.to_datetime(r.equity_dates).strftime('%Y-%m-%d'),
773
942
  'equity': r.equity_curve
774
943
  })
775
944
  equity_df.to_csv(f'{prefix}_equity.csv', index=False)
776
- print(f"Equity exported: {prefix}_equity.csv")
945
+ print(f"Exported: {prefix}_equity.csv")
777
946
 
947
+ # Summary
778
948
  with open(f'{prefix}_summary.txt', 'w') as f:
779
949
  f.write("BACKTEST SUMMARY\n")
780
950
  f.write("="*70 + "\n\n")
781
951
  f.write(f"Strategy: {r.config.get('strategy_name', 'Unknown')}\n")
782
952
  f.write(f"Period: {r.config.get('start_date', 'N/A')} to {r.config.get('end_date', 'N/A')}\n\n")
783
-
784
953
  f.write("PERFORMANCE\n")
785
954
  f.write("-"*70 + "\n")
786
955
  f.write(f"Initial Capital: ${r.initial_capital:,.2f}\n")
@@ -791,11 +960,11 @@ class ResultsExporter:
791
960
  f.write(f"Win Rate: {m['win_rate']:.2f}%\n")
792
961
  f.write(f"Total Trades: {m['total_trades']}\n")
793
962
 
794
- print(f"Summary exported: {prefix}_summary.txt")
963
+ print(f"Exported: {prefix}_summary.txt")
795
964
 
796
965
 
797
966
  # ============================================================
798
- # ONE-COMMAND RUNNER (Unchanged)
967
+ # ONE-COMMAND RUNNER
799
968
  # ============================================================
800
969
  def run_backtest(strategy_function, config,
801
970
  print_report=True,
@@ -803,7 +972,12 @@ def run_backtest(strategy_function, config,
803
972
  export_results=True,
804
973
  chart_filename='backtest_results.png',
805
974
  export_prefix='backtest'):
806
- """Run complete backtest with one command"""
975
+ """
976
+ Run complete backtest with one command
977
+
978
+ Example:
979
+ analyzer = run_backtest(my_strategy, CONFIG)
980
+ """
807
981
 
808
982
  print("="*80)
809
983
  print(" "*25 + "STARTING BACKTEST")
@@ -827,24 +1001,15 @@ def run_backtest(strategy_function, config,
827
1001
  print(f"\n[*] Creating charts: {chart_filename}")
828
1002
  try:
829
1003
  ChartGenerator.create_all_charts(analyzer, chart_filename)
830
- print(f"[OK] Charts saved: {chart_filename}")
831
1004
  except Exception as e:
832
1005
  print(f"[ERROR] Chart creation failed: {e}")
833
- elif create_charts and len(results.trades) == 0:
834
- print("\n[!] No trades - skipping charts")
835
1006
 
836
1007
  if export_results and len(results.trades) > 0:
837
- print(f"\n[*] Exporting results: {export_prefix}_*.csv")
1008
+ print(f"\n[*] Exporting results: {export_prefix}_*")
838
1009
  try:
839
1010
  ResultsExporter.export_all(analyzer, export_prefix)
840
- print(f"[OK] Files exported:")
841
- print(f" - {export_prefix}_trades.csv")
842
- print(f" - {export_prefix}_equity.csv")
843
- print(f" - {export_prefix}_summary.txt")
844
1011
  except Exception as e:
845
1012
  print(f"[ERROR] Export failed: {e}")
846
- elif export_results and len(results.trades) == 0:
847
- print("\n[!] No trades - skipping export")
848
1013
 
849
1014
  return analyzer
850
1015
 
@@ -860,8 +1025,11 @@ __all__ = [
860
1025
  'ResultsExporter',
861
1026
  'run_backtest',
862
1027
  'init_api',
863
- 'get_api_method',
864
- 'api_call', # NEW!
865
- 'APIHelper', # NEW!
866
- 'APIManager'
1028
+ 'api_call',
1029
+ 'APIHelper',
1030
+ 'APIManager',
1031
+ 'ResourceMonitor',
1032
+ 'create_progress_bar',
1033
+ 'update_progress',
1034
+ 'format_time'
867
1035
  ]
@@ -1,70 +1,72 @@
1
- Metadata-Version: 2.1
2
- Name: ivolatility_backtesting
3
- Version: 1.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
- # IVolatility Backtesting
26
- A universal backtesting framework for financial strategies using the IVolatility API.
27
-
28
- ## Installation
29
- ```bash
30
- pip install ivolatility_backtesting
31
- ```
32
-
33
- ## Usage
34
- ```python
35
- from ivolatility_backtesting import run_backtest, init_api
36
-
37
- # Initialize API
38
- init_api("your-api-key")
39
-
40
- # Define your strategy
41
- def my_strategy(config):
42
- # Strategy logic
43
- return BacktestResults(
44
- equity_curve=[100000, 110000],
45
- equity_dates=["2023-01-01", "2023-01-02"],
46
- trades=[{"pnl": 1000, "entry_date": "2023-01-01", "exit_date": "2023-01-02"}],
47
- initial_capital=100000,
48
- config=config
49
- )
50
-
51
- # Run backtest
52
- CONFIG = {
53
- "initial_capital": 100000,
54
- "start_date": "2023-01-01",
55
- "end_date": "2024-01-01",
56
- "strategy_name": "My Strategy"
57
- }
58
- analyzer = run_backtest(my_strategy, CONFIG)
59
-
60
- # Access metrics
61
- print(f"Sharpe Ratio: {analyzer.metrics['sharpe']:.2f}")
62
- ```
63
-
64
- ## Requirements
65
- - Python >= 3.8
66
- - pandas >= 1.5.0
67
- - numpy >= 1.21.0
68
- - matplotlib >= 3.5.0
69
- - seaborn >= 0.11.0
70
- - ivolatility >= 1.8.2
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
@@ -3,3 +3,4 @@ numpy>=1.21.0
3
3
  matplotlib>=3.5.0
4
4
  seaborn>=0.11.0
5
5
  ivolatility>=1.8.2
6
+ psutil>=7.1.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ivolatility_backtesting"
7
- version = "1.1.0"
7
+ version = "1.2.0"
8
8
  description = "A universal backtesting framework for financial strategies using the IVolatility API."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -17,7 +17,8 @@ dependencies = [
17
17
  "numpy>=1.21.0",
18
18
  "matplotlib>=3.5.0",
19
19
  "seaborn>=0.11.0",
20
- "ivolatility>=1.8.2"
20
+ "ivolatility>=1.8.2",
21
+ "psutil>=7.1.0",
21
22
  ]
22
23
  keywords = ["backtesting", "finance", "trading", "ivolatility"]
23
24
  classifiers = [
@@ -1,4 +1,4 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+