ivolatility-backtesting 1.0.1__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.0.1
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,14 +1,24 @@
1
1
  """
2
- ivolatility_backtesting.py
3
- Universal Backtest Framework with One-Command Runner
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 once
15
+ # Initialize API
9
16
  init_api(os.getenv("API_KEY"))
10
17
 
11
- CONFIG = {...}
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
- # GLOBAL API MANAGER
41
+ # RESOURCE MONITOR - NEW!
30
42
  # ============================================================
31
- class APIManager:
32
- """
33
- Centralized API key management for IVolatility API
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
- Usage:
36
- from ivolatility_backtesting import init_api, get_api_method
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
- # Initialize once at the start
39
- init_api(os.getenv("API_KEY"))
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
- # Use anywhere in your code
42
- getOptionsData = get_api_method('/equities/eod/stock-opts-by-param')
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
- """Set API key globally - call this once at startup"""
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 with key: {api_key[:10]}...{api_key[-5:]}")
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
- # Public API functions
305
+ # ============================================================
306
+ # PUBLIC API FUNCTIONS
307
+ # ============================================================
92
308
  def init_api(api_key=None):
93
309
  """
94
- Initialize IVolatility API with key
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-loads from environment
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 get_api_method(endpoint):
322
+ def api_call(endpoint, debug=False, **kwargs):
110
323
  """
111
- Get API method for specified endpoint
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
- Callable API method with key already configured
332
+ dict with 'data' key or None
118
333
 
119
334
  Example:
120
- getOptionsData = get_api_method('/equities/eod/stock-opts-by-param')
121
- data = getOptionsData(symbol='SPY', tradeDate='2024-01-01', cp='C')
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
- return APIManager.get_method(endpoint)
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
- Universal container for backtest results
129
- ANY strategy must return this format
130
- """
386
+ """Universal container for backtest results"""
387
+
131
388
  def __init__(self,
132
- equity_curve, # List of equity values
133
- equity_dates, # List of dates (matching equity_curve)
134
- trades, # List of dicts with trade info
135
- initial_capital, # Starting capital
136
- config, # Strategy config dict
137
- benchmark_prices=None, # Optional: dict {date: price}
138
- benchmark_symbol='SPY', # Optional: benchmark ticker
139
- daily_returns=None, # Optional: if not provided, calculated
140
- debug_info=None): # Optional: debug information
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
- Universal metrics calculator
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
- # Basic profitability
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 - WITH PROTECTION AGAINST DIVISION BY ZERO
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
- """Universal results printer"""
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]: # First 10 messages
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 debug messages")
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
- """Universal chart creator"""
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
- """Universal results exporter"""
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
- # Export trades
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"Trades exported: {prefix}_trades.csv")
937
+ print(f"Exported: {prefix}_trades.csv")
680
938
 
681
- # Export equity curve
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"Equity exported: {prefix}_equity.csv")
945
+ print(f"Exported: {prefix}_equity.csv")
688
946
 
689
- # Export summary
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"Summary exported: {prefix}_summary.txt")
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}_*.csv")
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
- # Export all classes and functions
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
- 'get_api_method',
820
- 'APIManager'
821
- ]
1028
+ 'api_call',
1029
+ 'APIHelper',
1030
+ 'APIManager',
1031
+ 'ResourceMonitor',
1032
+ 'create_progress_bar',
1033
+ 'update_progress',
1034
+ 'format_time'
1035
+ ]
@@ -1,70 +1,72 @@
1
- Metadata-Version: 2.1
2
- Name: ivolatility_backtesting
3
- Version: 1.0.1
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.0.1"
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
+