ivolatility-backtesting 0.1.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.

@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.1
2
+ Name: ivolatility_backtesting
3
+ Version: 0.1.0
4
+ Summary: Universal Backtest Framework with One-Command Runner for IVolatility API
5
+ Author-email: Your Name <your.email@example.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Your Name
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ Project-URL: Homepage, https://github.com/yourusername/ivolatility_backtesting
28
+ Project-URL: Bug Tracker, https://github.com/yourusername/ivolatility_backtesting/issues
29
+ Keywords: backtesting,finance,trading,ivolatility
30
+ Classifier: Programming Language :: Python :: 3
31
+ Classifier: Programming Language :: Python :: 3.8
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Operating System :: OS Independent
34
+ Requires-Python: >=3.8
35
+ Description-Content-Type: text/markdown
36
+ License-File: LICENSE
37
+ Requires-Dist: pandas>=1.5.0
38
+ Requires-Dist: numpy>=1.21.0
39
+ Requires-Dist: matplotlib>=3.5.0
40
+ Requires-Dist: seaborn>=0.11.0
41
+ Requires-Dist: ivolatility
42
+
43
+ # IVolatility Backtesting
44
+ A universal backtesting framework for financial strategies using the IVolatility API.
45
+
46
+ ## Installation
47
+ ```bash
48
+ pip install ivolatility_backtesting
49
+ ```
50
+
51
+ **Note**: The `ivolatility` package is required but may not be available on PyPI. Contact IVolatility to obtain their SDK and install it manually before using this package.
52
+
53
+ ## Usage
54
+ ```python
55
+ from ivolatility_backtesting import run_backtest, init_api
56
+
57
+ # Initialize API
58
+ init_api("your-api-key")
59
+
60
+ # Define your strategy
61
+ def my_strategy(config):
62
+ # Strategy logic
63
+ return BacktestResults(
64
+ equity_curve=[100000, 110000],
65
+ equity_dates=["2023-01-01", "2023-01-02"],
66
+ trades=[{"pnl": 1000, "entry_date": "2023-01-01", "exit_date": "2023-01-02"}],
67
+ initial_capital=100000,
68
+ config=config
69
+ )
70
+
71
+ # Run backtest
72
+ CONFIG = {
73
+ "initial_capital": 100000,
74
+ "start_date": "2023-01-01",
75
+ "end_date": "2024-01-01",
76
+ "strategy_name": "My Strategy"
77
+ }
78
+ analyzer = run_backtest(my_strategy, CONFIG)
79
+
80
+ # Access metrics
81
+ print(f"Sharpe Ratio: {analyzer.metrics['sharpe']:.2f}")
82
+ ```
83
+
84
+ ## Requirements
85
+ - Python >= 3.10
86
+ - pandas >= 1.5.0
87
+ - numpy >= 1.21.0
88
+ - matplotlib >= 3.5.0
89
+ - seaborn >= 0.11.0
90
+ - ivolatility (contact IVolatility for SDK)
91
+
92
+ ## License
93
+ MIT License
@@ -0,0 +1,51 @@
1
+ # IVolatility Backtesting
2
+ A universal backtesting framework for financial strategies using the IVolatility API.
3
+
4
+ ## Installation
5
+ ```bash
6
+ pip install ivolatility_backtesting
7
+ ```
8
+
9
+ **Note**: The `ivolatility` package is required but may not be available on PyPI. Contact IVolatility to obtain their SDK and install it manually before using this package.
10
+
11
+ ## Usage
12
+ ```python
13
+ from ivolatility_backtesting import run_backtest, init_api
14
+
15
+ # Initialize API
16
+ init_api("your-api-key")
17
+
18
+ # Define your strategy
19
+ def my_strategy(config):
20
+ # Strategy logic
21
+ return BacktestResults(
22
+ equity_curve=[100000, 110000],
23
+ equity_dates=["2023-01-01", "2023-01-02"],
24
+ trades=[{"pnl": 1000, "entry_date": "2023-01-01", "exit_date": "2023-01-02"}],
25
+ initial_capital=100000,
26
+ config=config
27
+ )
28
+
29
+ # Run backtest
30
+ CONFIG = {
31
+ "initial_capital": 100000,
32
+ "start_date": "2023-01-01",
33
+ "end_date": "2024-01-01",
34
+ "strategy_name": "My Strategy"
35
+ }
36
+ analyzer = run_backtest(my_strategy, CONFIG)
37
+
38
+ # Access metrics
39
+ print(f"Sharpe Ratio: {analyzer.metrics['sharpe']:.2f}")
40
+ ```
41
+
42
+ ## Requirements
43
+ - Python >= 3.10
44
+ - pandas >= 1.5.0
45
+ - numpy >= 1.21.0
46
+ - matplotlib >= 3.5.0
47
+ - seaborn >= 0.11.0
48
+ - ivolatility (contact IVolatility for SDK)
49
+
50
+ ## License
51
+ MIT License
@@ -0,0 +1,17 @@
1
+ from .ivolatility_backtesting import (
2
+ BacktestResults, BacktestAnalyzer, ResultsReporter,
3
+ ChartGenerator, ResultsExporter, run_backtest,
4
+ init_api, get_api_method, APIManager
5
+ )
6
+
7
+ __all__ = [
8
+ 'BacktestResults',
9
+ 'BacktestAnalyzer',
10
+ 'ResultsReporter',
11
+ 'ChartGenerator',
12
+ 'ResultsExporter',
13
+ 'run_backtest',
14
+ 'init_api',
15
+ 'get_api_method',
16
+ 'APIManager'
17
+ ]
@@ -0,0 +1,821 @@
1
+ """
2
+ ivolatility_backtesting.py
3
+ Universal Backtest Framework with One-Command Runner
4
+
5
+ Usage:
6
+ from ivolatility_backtesting import *
7
+
8
+ # Initialize API once
9
+ init_api(os.getenv("API_KEY"))
10
+
11
+ CONFIG = {...}
12
+ analyzer = run_backtest(my_strategy, CONFIG)
13
+ """
14
+
15
+ import pandas as pd
16
+ import numpy as np
17
+ import matplotlib.pyplot as plt
18
+ import seaborn as sns
19
+ from datetime import datetime, timedelta
20
+ import ivolatility as ivol
21
+ import os
22
+
23
+ # Set style
24
+ sns.set_style('darkgrid')
25
+ plt.rcParams['figure.figsize'] = (15, 8)
26
+
27
+
28
+ # ============================================================
29
+ # GLOBAL API MANAGER
30
+ # ============================================================
31
+ class APIManager:
32
+ """
33
+ Centralized API key management for IVolatility API
34
+
35
+ Usage:
36
+ from ivolatility_backtesting import init_api, get_api_method
37
+
38
+ # Initialize once at the start
39
+ init_api(os.getenv("API_KEY"))
40
+
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', ...)
44
+ """
45
+ _api_key = None
46
+ _methods = {}
47
+
48
+ @classmethod
49
+ def initialize(cls, api_key):
50
+ """Set API key globally - call this once at startup"""
51
+ if not api_key:
52
+ raise ValueError("API key cannot be empty")
53
+ cls._api_key = api_key
54
+ ivol.setLoginParams(apiKey=api_key)
55
+ print(f"[API] Initialized with key: {api_key[:10]}...{api_key[-5:]}")
56
+
57
+ @classmethod
58
+ 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
+ """
68
+ if cls._api_key is None:
69
+ # Auto-initialize from environment if not set
70
+ api_key = os.getenv("API_KEY")
71
+ if not api_key:
72
+ raise ValueError(
73
+ "API key not initialized. Call init_api(key) first or set API_KEY environment variable"
74
+ )
75
+ cls.initialize(api_key)
76
+
77
+ # Cache methods to avoid recreation
78
+ if endpoint not in cls._methods:
79
+ # Re-set login params before creating method
80
+ ivol.setLoginParams(apiKey=cls._api_key)
81
+ cls._methods[endpoint] = ivol.setMethod(endpoint)
82
+
83
+ 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
+
90
+
91
+ # Public API functions
92
+ def init_api(api_key=None):
93
+ """
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
98
+
99
+ Example:
100
+ init_api("your-api-key")
101
+ # or
102
+ init_api() # Auto-loads from environment
103
+ """
104
+ if api_key is None:
105
+ api_key = os.getenv("API_KEY")
106
+ APIManager.initialize(api_key)
107
+
108
+
109
+ def get_api_method(endpoint):
110
+ """
111
+ Get API method for specified endpoint
112
+
113
+ Args:
114
+ endpoint: API endpoint path
115
+
116
+ Returns:
117
+ Callable API method with key already configured
118
+
119
+ Example:
120
+ getOptionsData = get_api_method('/equities/eod/stock-opts-by-param')
121
+ data = getOptionsData(symbol='SPY', tradeDate='2024-01-01', cp='C')
122
+ """
123
+ return APIManager.get_method(endpoint)
124
+
125
+
126
+ class BacktestResults:
127
+ """
128
+ Universal container for backtest results
129
+ ANY strategy must return this format
130
+ """
131
+ 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
141
+
142
+ self.equity_curve = equity_curve
143
+ self.equity_dates = equity_dates
144
+ self.trades = trades
145
+ self.initial_capital = initial_capital
146
+ self.final_capital = equity_curve[-1] if len(equity_curve) > 0 else initial_capital
147
+ self.config = config
148
+ self.benchmark_prices = benchmark_prices
149
+ self.benchmark_symbol = benchmark_symbol
150
+ self.debug_info = debug_info if debug_info else []
151
+
152
+ # Calculate daily returns if not provided
153
+ if daily_returns is None and len(equity_curve) > 1:
154
+ self.daily_returns = [
155
+ (equity_curve[i] - equity_curve[i-1]) / equity_curve[i-1]
156
+ for i in range(1, len(equity_curve))
157
+ ]
158
+ else:
159
+ self.daily_returns = daily_returns if daily_returns else []
160
+
161
+ # Calculate max drawdown
162
+ self.max_drawdown = self._calculate_max_drawdown()
163
+
164
+ def _calculate_max_drawdown(self):
165
+ if len(self.equity_curve) < 2:
166
+ return 0
167
+ running_max = np.maximum.accumulate(self.equity_curve)
168
+ drawdowns = (np.array(self.equity_curve) - running_max) / running_max * 100
169
+ return abs(np.min(drawdowns))
170
+
171
+
172
+ class BacktestAnalyzer:
173
+ """
174
+ Universal metrics calculator
175
+ Works with any BacktestResults object
176
+ """
177
+ def __init__(self, results):
178
+ self.results = results
179
+ self.metrics = {}
180
+
181
+ def calculate_all_metrics(self):
182
+ """Calculate all available metrics"""
183
+ r = self.results
184
+
185
+ # Basic profitability
186
+ self.metrics['total_pnl'] = r.final_capital - r.initial_capital
187
+ self.metrics['total_return'] = (self.metrics['total_pnl'] / r.initial_capital) * 100
188
+
189
+ # CAGR - WITH PROTECTION AGAINST DIVISION BY ZERO
190
+ if len(r.equity_dates) > 0:
191
+ start_date = min(r.equity_dates)
192
+ end_date = max(r.equity_dates)
193
+ days_diff = (end_date - start_date).days
194
+
195
+ # PROTECTION: если даты одинаковые или разница < 1 день
196
+ if days_diff <= 0:
197
+ self.metrics['cagr'] = 0
198
+ self.metrics['show_cagr'] = False
199
+ else:
200
+ years = days_diff / 365.25
201
+
202
+ if years >= 1.0:
203
+ self.metrics['cagr'] = ((r.final_capital / r.initial_capital) ** (1/years) - 1) * 100
204
+ self.metrics['show_cagr'] = True
205
+ else:
206
+ self.metrics['cagr'] = self.metrics['total_return'] * (365.25 / days_diff)
207
+ self.metrics['show_cagr'] = False
208
+ else:
209
+ self.metrics['cagr'] = 0
210
+ self.metrics['show_cagr'] = False
211
+
212
+ # Risk metrics
213
+ self.metrics['sharpe'] = self._sharpe_ratio(r.daily_returns)
214
+ self.metrics['sortino'] = self._sortino_ratio(r.daily_returns)
215
+ self.metrics['max_drawdown'] = r.max_drawdown
216
+
217
+ if len(r.daily_returns) > 0:
218
+ self.metrics['volatility'] = np.std(r.daily_returns) * np.sqrt(252) * 100
219
+ else:
220
+ self.metrics['volatility'] = 0
221
+
222
+ self.metrics['calmar'] = abs(self.metrics['total_return'] / r.max_drawdown) if r.max_drawdown > 0 else 0
223
+ self.metrics['omega'] = self._omega_ratio(r.daily_returns)
224
+ self.metrics['ulcer'] = self._ulcer_index(r.equity_curve)
225
+
226
+ # VaR
227
+ self.metrics['var_95'], self.metrics['var_95_pct'] = self._calculate_var(r.daily_returns, 0.95)
228
+ self.metrics['var_99'], self.metrics['var_99_pct'] = self._calculate_var(r.daily_returns, 0.99)
229
+ self.metrics['cvar_95'], self.metrics['cvar_95_pct'] = self._calculate_cvar(r.daily_returns, 0.95)
230
+
231
+ avg_equity = np.mean(r.equity_curve) if len(r.equity_curve) > 0 else r.initial_capital
232
+ self.metrics['var_95_dollar'] = self.metrics['var_95'] * avg_equity
233
+ self.metrics['var_99_dollar'] = self.metrics['var_99'] * avg_equity
234
+ self.metrics['cvar_95_dollar'] = self.metrics['cvar_95'] * avg_equity
235
+
236
+ # Distribution
237
+ self.metrics['tail_ratio'] = self._tail_ratio(r.daily_returns)
238
+ self.metrics['skewness'], self.metrics['kurtosis'] = self._skewness_kurtosis(r.daily_returns)
239
+
240
+ # Alpha/Beta
241
+ self.metrics['alpha'], self.metrics['beta'], self.metrics['r_squared'] = self._alpha_beta(r)
242
+
243
+ # Trading stats
244
+ if len(r.trades) > 0:
245
+ trades_df = pd.DataFrame(r.trades)
246
+ winning = trades_df[trades_df['pnl'] > 0]
247
+ losing = trades_df[trades_df['pnl'] <= 0]
248
+
249
+ self.metrics['total_trades'] = len(trades_df)
250
+ self.metrics['winning_trades'] = len(winning)
251
+ self.metrics['losing_trades'] = len(losing)
252
+ self.metrics['win_rate'] = (len(winning) / len(trades_df)) * 100 if len(trades_df) > 0 else 0
253
+
254
+ wins_sum = winning['pnl'].sum() if len(winning) > 0 else 0
255
+ losses_sum = abs(losing['pnl'].sum()) if len(losing) > 0 else 0
256
+ self.metrics['profit_factor'] = wins_sum / losses_sum if losses_sum > 0 else float('inf')
257
+
258
+ self.metrics['avg_win'] = winning['pnl'].mean() if len(winning) > 0 else 0
259
+ self.metrics['avg_loss'] = losing['pnl'].mean() if len(losing) > 0 else 0
260
+ self.metrics['best_trade'] = trades_df['pnl'].max()
261
+ self.metrics['worst_trade'] = trades_df['pnl'].min()
262
+
263
+ if len(winning) > 0 and len(losing) > 0:
264
+ self.metrics['avg_win_loss_ratio'] = abs(self.metrics['avg_win'] / self.metrics['avg_loss'])
265
+ else:
266
+ self.metrics['avg_win_loss_ratio'] = 0
267
+
268
+ self.metrics['max_win_streak'], self.metrics['max_loss_streak'] = self._win_loss_streaks(r.trades)
269
+ else:
270
+ self.metrics.update({
271
+ 'total_trades': 0, 'winning_trades': 0, 'losing_trades': 0,
272
+ 'win_rate': 0, 'profit_factor': 0, 'avg_win': 0, 'avg_loss': 0,
273
+ 'best_trade': 0, 'worst_trade': 0, 'avg_win_loss_ratio': 0,
274
+ 'max_win_streak': 0, 'max_loss_streak': 0
275
+ })
276
+
277
+ # Efficiency
278
+ running_max = np.maximum.accumulate(r.equity_curve)
279
+ max_dd_dollars = np.min(np.array(r.equity_curve) - running_max)
280
+ self.metrics['recovery_factor'] = self.metrics['total_pnl'] / abs(max_dd_dollars) if max_dd_dollars != 0 else 0
281
+
282
+ # Exposure time
283
+ if len(r.trades) > 0 and 'start_date' in r.config and 'end_date' in r.config:
284
+ total_days = (pd.to_datetime(r.config['end_date']) - pd.to_datetime(r.config['start_date'])).days
285
+ self.metrics['exposure_time'] = self._exposure_time(r.trades, total_days)
286
+ else:
287
+ self.metrics['exposure_time'] = 0
288
+
289
+ return self.metrics
290
+
291
+ def _sharpe_ratio(self, returns):
292
+ if len(returns) < 2:
293
+ return 0
294
+ return np.sqrt(252) * np.mean(returns) / np.std(returns) if np.std(returns) > 0 else 0
295
+
296
+ def _sortino_ratio(self, returns):
297
+ if len(returns) < 2:
298
+ return 0
299
+ returns_array = np.array(returns)
300
+ downside = returns_array[returns_array < 0]
301
+ if len(downside) == 0 or np.std(downside) == 0:
302
+ return 0
303
+ return np.sqrt(252) * np.mean(returns_array) / np.std(downside)
304
+
305
+ def _omega_ratio(self, returns, threshold=0):
306
+ if len(returns) < 2:
307
+ return 0
308
+ returns_array = np.array(returns)
309
+ gains = np.sum(np.maximum(returns_array - threshold, 0))
310
+ losses = np.sum(np.maximum(threshold - returns_array, 0))
311
+ return gains / losses if losses > 0 else float('inf')
312
+
313
+ def _ulcer_index(self, equity_curve):
314
+ if len(equity_curve) < 2:
315
+ return 0
316
+ equity_array = np.array(equity_curve)
317
+ running_max = np.maximum.accumulate(equity_array)
318
+ drawdown = (equity_array - running_max) / running_max
319
+ return np.sqrt(np.mean(drawdown ** 2)) * 100
320
+
321
+ def _calculate_var(self, returns, confidence=0.95):
322
+ if len(returns) < 10:
323
+ return 0, 0
324
+ returns_array = np.array(returns)
325
+ returns_array = returns_array[~np.isnan(returns_array)]
326
+ if len(returns_array) < 10:
327
+ return 0, 0
328
+ var_percentile = (1 - confidence) * 100
329
+ var_return = np.percentile(returns_array, var_percentile)
330
+ return var_return, var_return * 100
331
+
332
+ def _calculate_cvar(self, returns, confidence=0.95):
333
+ if len(returns) < 10:
334
+ return 0, 0
335
+ returns_array = np.array(returns)
336
+ returns_array = returns_array[~np.isnan(returns_array)]
337
+ if len(returns_array) < 10:
338
+ return 0, 0
339
+ var_percentile = (1 - confidence) * 100
340
+ var_threshold = np.percentile(returns_array, var_percentile)
341
+ tail_losses = returns_array[returns_array <= var_threshold]
342
+ if len(tail_losses) == 0:
343
+ return 0, 0
344
+ cvar_return = np.mean(tail_losses)
345
+ return cvar_return, cvar_return * 100
346
+
347
+ def _tail_ratio(self, returns):
348
+ if len(returns) < 20:
349
+ return 0
350
+ returns_array = np.array(returns)
351
+ right = np.percentile(returns_array, 95)
352
+ left = abs(np.percentile(returns_array, 5))
353
+ return right / left if left > 0 else 0
354
+
355
+ def _skewness_kurtosis(self, returns):
356
+ if len(returns) < 10:
357
+ return 0, 0
358
+ returns_array = np.array(returns)
359
+ mean = np.mean(returns_array)
360
+ std = np.std(returns_array)
361
+ if std == 0:
362
+ return 0, 0
363
+ skew = np.mean(((returns_array - mean) / std) ** 3)
364
+ kurt = np.mean(((returns_array - mean) / std) ** 4) - 3
365
+ return skew, kurt
366
+
367
+ def _alpha_beta(self, results):
368
+ if not hasattr(results, 'benchmark_prices') or not results.benchmark_prices:
369
+ return 0, 0, 0
370
+ if len(results.equity_dates) < 10:
371
+ return 0, 0, 0
372
+
373
+ benchmark_returns = []
374
+ sorted_dates = sorted(results.equity_dates)
375
+
376
+ for i in range(1, len(sorted_dates)):
377
+ prev_date = sorted_dates[i-1]
378
+ curr_date = sorted_dates[i]
379
+
380
+ if prev_date in results.benchmark_prices and curr_date in results.benchmark_prices:
381
+ prev_price = results.benchmark_prices[prev_date]
382
+ curr_price = results.benchmark_prices[curr_date]
383
+ bench_return = (curr_price - prev_price) / prev_price
384
+ benchmark_returns.append(bench_return)
385
+ else:
386
+ benchmark_returns.append(0)
387
+
388
+ if len(benchmark_returns) != len(results.daily_returns):
389
+ return 0, 0, 0
390
+
391
+ port_ret = np.array(results.daily_returns)
392
+ bench_ret = np.array(benchmark_returns)
393
+
394
+ bench_mean = np.mean(bench_ret)
395
+ port_mean = np.mean(port_ret)
396
+
397
+ covariance = np.mean((bench_ret - bench_mean) * (port_ret - port_mean))
398
+ benchmark_variance = np.mean((bench_ret - bench_mean) ** 2)
399
+
400
+ if benchmark_variance == 0:
401
+ return 0, 0, 0
402
+
403
+ beta = covariance / benchmark_variance
404
+ alpha_daily = port_mean - beta * bench_mean
405
+ alpha_annualized = alpha_daily * 252 * 100
406
+
407
+ ss_res = np.sum((port_ret - (alpha_daily + beta * bench_ret)) ** 2)
408
+ ss_tot = np.sum((port_ret - port_mean) ** 2)
409
+ r_squared = 1 - (ss_res / ss_tot) if ss_tot > 0 else 0
410
+
411
+ return alpha_annualized, beta, r_squared
412
+
413
+ def _win_loss_streaks(self, trades):
414
+ if len(trades) == 0:
415
+ return 0, 0
416
+ max_win = max_loss = current_win = current_loss = 0
417
+ for trade in trades:
418
+ if trade['pnl'] > 0:
419
+ current_win += 1
420
+ current_loss = 0
421
+ max_win = max(max_win, current_win)
422
+ else:
423
+ current_loss += 1
424
+ current_win = 0
425
+ max_loss = max(max_loss, current_loss)
426
+ return max_win, max_loss
427
+
428
+ def _exposure_time(self, trades, total_days):
429
+ if total_days <= 0 or len(trades) == 0:
430
+ return 0
431
+ days_with_positions = set()
432
+ for trade in trades:
433
+ entry = pd.to_datetime(trade['entry_date'])
434
+ exit = pd.to_datetime(trade['exit_date'])
435
+ date_range = pd.date_range(start=entry, end=exit, freq='D')
436
+ days_with_positions.update(date_range.date)
437
+ exposure_pct = (len(days_with_positions) / total_days) * 100
438
+ return min(exposure_pct, 100.0)
439
+
440
+
441
+ class ResultsReporter:
442
+ """Universal results printer"""
443
+
444
+ @staticmethod
445
+ def print_full_report(analyzer):
446
+ m = analyzer.metrics
447
+ r = analyzer.results
448
+
449
+ print("="*80)
450
+ print(" "*25 + "BACKTEST RESULTS")
451
+ print("="*80)
452
+ print()
453
+
454
+ # PRINT DEBUG INFO IF AVAILABLE
455
+ if hasattr(r, 'debug_info') and len(r.debug_info) > 0:
456
+ print("DEBUG INFORMATION")
457
+ print("-"*80)
458
+ for debug_msg in r.debug_info[:10]: # First 10 messages
459
+ print(debug_msg)
460
+ if len(r.debug_info) > 10:
461
+ print(f"... and {len(r.debug_info) - 10} more debug messages")
462
+ print()
463
+
464
+ # Profitability
465
+ print("PROFITABILITY METRICS")
466
+ print("-"*80)
467
+ print(f"Initial Capital: ${r.initial_capital:>15,.2f}")
468
+ print(f"Final Equity: ${r.final_capital:>15,.2f}")
469
+ print(f"Total P&L: ${m['total_pnl']:>15,.2f} (absolute profit/loss)")
470
+ print(f"Total Return: {m['total_return']:>15.2f}% (% gain/loss)")
471
+ if m['cagr'] != 0:
472
+ if m['show_cagr']:
473
+ print(f"CAGR: {m['cagr']:>15.2f}% (annualized compound growth)")
474
+ else:
475
+ print(f"Annualized Return: {m['cagr']:>15.2f}% (extrapolated to 1 year)")
476
+ print()
477
+
478
+ # Risk
479
+ print("RISK METRICS")
480
+ print("-"*80)
481
+ print(f"Sharpe Ratio: {m['sharpe']:>15.2f} (>1 good, >2 excellent)")
482
+ print(f"Sortino Ratio: {m['sortino']:>15.2f} (downside risk, >2 good)")
483
+ print(f"Calmar Ratio: {m['calmar']:>15.2f} (return/drawdown, >3 good)")
484
+ if m['omega'] != 0:
485
+ omega_display = f"{m['omega']:.2f}" if m['omega'] < 999 else "inf"
486
+ print(f"Omega Ratio: {omega_display:>15s} (gains/losses, >1 good)")
487
+ print(f"Maximum Drawdown: {m['max_drawdown']:>15.2f}% (peak to trough)")
488
+ if m['ulcer'] != 0:
489
+ print(f"Ulcer Index: {m['ulcer']:>15.2f}% (pain of drawdowns, lower better)")
490
+ print(f"Volatility (ann.): {m['volatility']:>15.2f}% (annualized std dev)")
491
+
492
+ if len(r.daily_returns) >= 10:
493
+ print(f"VaR (95%, 1-day): {m['var_95_pct']:>15.2f}% (${m['var_95_dollar']:>,.0f}) (max loss 95% confidence)")
494
+ print(f"VaR (99%, 1-day): {m['var_99_pct']:>15.2f}% (${m['var_99_dollar']:>,.0f}) (max loss 99% confidence)")
495
+ print(f"CVaR (95%, 1-day): {m['cvar_95_pct']:>15.2f}% (${m['cvar_95_dollar']:>,.0f}) (avg loss in worst 5%)")
496
+
497
+ if m['tail_ratio'] != 0:
498
+ print(f"Tail Ratio (95/5): {m['tail_ratio']:>15.2f} (big wins/losses, >1 good)")
499
+
500
+ if m['skewness'] != 0 or m['kurtosis'] != 0:
501
+ print(f"Skewness: {m['skewness']:>15.2f} (>0 positive tail)")
502
+ print(f"Kurtosis (excess): {m['kurtosis']:>15.2f} (>0 fat tails)")
503
+
504
+ if m['beta'] != 0 or m['alpha'] != 0:
505
+ print(f"Alpha (vs {r.benchmark_symbol}): {m['alpha']:>15.2f}% (excess return)")
506
+ print(f"Beta (vs {r.benchmark_symbol}): {m['beta']:>15.2f} (<1 defensive, >1 aggressive)")
507
+ print(f"R^2 (vs {r.benchmark_symbol}): {m['r_squared']:>15.2f} (market correlation 0-1)")
508
+
509
+ # Warning for unrealistic results
510
+ if abs(m['total_return']) > 200 or m['volatility'] > 150:
511
+ print()
512
+ print("UNREALISTIC RESULTS DETECTED:")
513
+ if abs(m['total_return']) > 200:
514
+ print(f" Total return {m['total_return']:.1f}% is extremely high")
515
+ if m['volatility'] > 150:
516
+ print(f" Volatility {m['volatility']:.1f}% is higher than leveraged ETFs")
517
+ print(" Review configuration before trusting results")
518
+
519
+ print()
520
+
521
+ # Efficiency
522
+ print("EFFICIENCY METRICS")
523
+ print("-"*80)
524
+ if m['recovery_factor'] != 0:
525
+ print(f"Recovery Factor: {m['recovery_factor']:>15.2f} (profit/max DD, >3 good)")
526
+ if m['exposure_time'] != 0:
527
+ print(f"Exposure Time: {m['exposure_time']:>15.1f}% (time in market)")
528
+ print()
529
+
530
+ # Trading stats
531
+ print("TRADING STATISTICS")
532
+ print("-"*80)
533
+ print(f"Total Trades: {m['total_trades']:>15}")
534
+ print(f"Winning Trades: {m['winning_trades']:>15}")
535
+ print(f"Losing Trades: {m['losing_trades']:>15}")
536
+ print(f"Win Rate: {m['win_rate']:>15.2f}% (% profitable trades)")
537
+ print(f"Profit Factor: {m['profit_factor']:>15.2f} (gross profit/loss, >1.5 good)")
538
+ if m['max_win_streak'] > 0 or m['max_loss_streak'] > 0:
539
+ print(f"Max Win Streak: {m['max_win_streak']:>15} (consecutive wins)")
540
+ print(f"Max Loss Streak: {m['max_loss_streak']:>15} (consecutive losses)")
541
+ print(f"Average Win: ${m['avg_win']:>15,.2f}")
542
+ print(f"Average Loss: ${m['avg_loss']:>15,.2f}")
543
+ print(f"Best Trade: ${m['best_trade']:>15,.2f}")
544
+ print(f"Worst Trade: ${m['worst_trade']:>15,.2f}")
545
+ if m['avg_win_loss_ratio'] != 0:
546
+ print(f"Avg Win/Loss Ratio: {m['avg_win_loss_ratio']:>15.2f} (avg win / avg loss)")
547
+ print()
548
+ print("="*80)
549
+
550
+
551
+ class ChartGenerator:
552
+ """Universal chart creator"""
553
+
554
+ @staticmethod
555
+ def create_all_charts(analyzer, filename='backtest_results.png'):
556
+ r = analyzer.results
557
+ m = analyzer.metrics
558
+
559
+ if len(r.trades) == 0:
560
+ print("No trades to visualize")
561
+ return
562
+
563
+ trades_df = pd.DataFrame(r.trades)
564
+ fig, axes = plt.subplots(3, 2, figsize=(18, 14))
565
+ fig.suptitle('Backtest Results - Comprehensive Analysis',
566
+ fontsize=16, fontweight='bold', y=0.995)
567
+
568
+ dates = pd.to_datetime(r.equity_dates)
569
+ equity_array = np.array(r.equity_curve)
570
+
571
+ # Equity Curve
572
+ ax1 = axes[0, 0]
573
+ ax1.plot(dates, equity_array, linewidth=2.5, color='#2196F3')
574
+ ax1.axhline(y=r.initial_capital, color='gray', linestyle='--', alpha=0.7)
575
+ ax1.fill_between(dates, r.initial_capital, equity_array,
576
+ where=(equity_array >= r.initial_capital),
577
+ alpha=0.3, color='green', interpolate=True)
578
+ ax1.fill_between(dates, r.initial_capital, equity_array,
579
+ where=(equity_array < r.initial_capital),
580
+ alpha=0.3, color='red', interpolate=True)
581
+ ax1.set_title('Portfolio Equity Curve', fontsize=12, fontweight='bold')
582
+ ax1.set_ylabel('Equity ($)')
583
+ ax1.grid(True, alpha=0.3)
584
+ ax1.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'${x/1000:.0f}K'))
585
+
586
+ # Drawdown
587
+ ax2 = axes[0, 1]
588
+ running_max = np.maximum.accumulate(equity_array)
589
+ drawdown = (equity_array - running_max) / running_max * 100
590
+ ax2.fill_between(dates, 0, drawdown, alpha=0.6, color='#f44336')
591
+ ax2.plot(dates, drawdown, color='#d32f2f', linewidth=2)
592
+ max_dd_idx = np.argmin(drawdown)
593
+ ax2.scatter(dates[max_dd_idx], drawdown[max_dd_idx], color='darkred', s=100, zorder=5, marker='v')
594
+ ax2.set_title('Drawdown Over Time', fontsize=12, fontweight='bold')
595
+ ax2.set_ylabel('Drawdown (%)')
596
+ ax2.grid(True, alpha=0.3)
597
+
598
+ # P&L Distribution
599
+ ax3 = axes[1, 0]
600
+ pnl_values = trades_df['pnl'].values
601
+ ax3.hist(pnl_values, bins=40, color='#4CAF50', alpha=0.7, edgecolor='black')
602
+ ax3.axvline(x=0, color='red', linestyle='--', linewidth=2)
603
+ ax3.axvline(x=np.median(pnl_values), color='blue', linestyle='--', linewidth=2)
604
+ ax3.set_title('Trade P&L Distribution', fontsize=12, fontweight='bold')
605
+ ax3.set_xlabel('P&L ($)')
606
+ ax3.set_ylabel('Frequency')
607
+ ax3.grid(True, alpha=0.3, axis='y')
608
+
609
+ # Signal Performance
610
+ ax4 = axes[1, 1]
611
+ if 'signal' in trades_df.columns:
612
+ signal_pnl = trades_df.groupby('signal')['pnl'].sum()
613
+ colors = ['#4CAF50' if x > 0 else '#f44336' for x in signal_pnl.values]
614
+ bars = ax4.bar(signal_pnl.index, signal_pnl.values, color=colors, alpha=0.7, edgecolor='black')
615
+ for bar in bars:
616
+ height = bar.get_height()
617
+ ax4.text(bar.get_x() + bar.get_width()/2., height,
618
+ f'${height:,.0f}', ha='center', va='bottom' if height > 0 else 'top', fontweight='bold')
619
+ ax4.set_title('P&L by Signal Type', fontsize=12, fontweight='bold')
620
+ else:
621
+ ax4.text(0.5, 0.5, 'No signal data', ha='center', va='center', transform=ax4.transAxes)
622
+ ax4.set_ylabel('Total P&L ($)')
623
+ ax4.axhline(y=0, color='black', linestyle='-', linewidth=1)
624
+ ax4.grid(True, alpha=0.3, axis='y')
625
+
626
+ # Monthly Returns
627
+ ax5 = axes[2, 0]
628
+ trades_df['exit_date'] = pd.to_datetime(trades_df['exit_date'])
629
+ trades_df['month'] = trades_df['exit_date'].dt.to_period('M')
630
+ monthly_pnl = trades_df.groupby('month')['pnl'].sum()
631
+ colors_monthly = ['#4CAF50' if x > 0 else '#f44336' for x in monthly_pnl.values]
632
+ ax5.bar(range(len(monthly_pnl)), monthly_pnl.values, color=colors_monthly, alpha=0.7, edgecolor='black')
633
+ ax5.set_title('Monthly P&L', fontsize=12, fontweight='bold')
634
+ ax5.set_ylabel('P&L ($)')
635
+ ax5.set_xticks(range(len(monthly_pnl)))
636
+ ax5.set_xticklabels([str(m) for m in monthly_pnl.index], rotation=45, ha='right')
637
+ ax5.axhline(y=0, color='black', linestyle='-', linewidth=1)
638
+ ax5.grid(True, alpha=0.3, axis='y')
639
+
640
+ # Top Symbols
641
+ ax6 = axes[2, 1]
642
+ if 'symbol' in trades_df.columns:
643
+ symbol_pnl = trades_df.groupby('symbol')['pnl'].sum().sort_values(ascending=True).tail(10)
644
+ colors_symbols = ['#4CAF50' if x > 0 else '#f44336' for x in symbol_pnl.values]
645
+ ax6.barh(range(len(symbol_pnl)), symbol_pnl.values, color=colors_symbols, alpha=0.7, edgecolor='black')
646
+ ax6.set_yticks(range(len(symbol_pnl)))
647
+ ax6.set_yticklabels(symbol_pnl.index, fontsize=9)
648
+ ax6.set_title('Top 10 Symbols by P&L', fontsize=12, fontweight='bold')
649
+ else:
650
+ ax6.text(0.5, 0.5, 'No symbol data', ha='center', va='center', transform=ax6.transAxes)
651
+ ax6.set_xlabel('Total P&L ($)')
652
+ ax6.axvline(x=0, color='black', linestyle='-', linewidth=1)
653
+ ax6.grid(True, alpha=0.3, axis='x')
654
+
655
+ plt.tight_layout()
656
+ plt.savefig(filename, dpi=300, bbox_inches='tight')
657
+ plt.show()
658
+
659
+ print(f"Chart saved: {filename}")
660
+
661
+
662
+ class ResultsExporter:
663
+ """Universal results exporter"""
664
+
665
+ @staticmethod
666
+ def export_all(analyzer, prefix='backtest'):
667
+ r = analyzer.results
668
+ m = analyzer.metrics
669
+
670
+ if len(r.trades) == 0:
671
+ print("No trades to export")
672
+ return
673
+
674
+ # Export trades
675
+ trades_df = pd.DataFrame(r.trades)
676
+ trades_df['entry_date'] = pd.to_datetime(trades_df['entry_date']).dt.strftime('%Y-%m-%d')
677
+ trades_df['exit_date'] = pd.to_datetime(trades_df['exit_date']).dt.strftime('%Y-%m-%d')
678
+ trades_df.to_csv(f'{prefix}_trades.csv', index=False)
679
+ print(f"Trades exported: {prefix}_trades.csv")
680
+
681
+ # Export equity curve
682
+ equity_df = pd.DataFrame({
683
+ 'date': pd.to_datetime(r.equity_dates).strftime('%Y-%m-%d'),
684
+ 'equity': r.equity_curve
685
+ })
686
+ equity_df.to_csv(f'{prefix}_equity.csv', index=False)
687
+ print(f"Equity exported: {prefix}_equity.csv")
688
+
689
+ # Export summary
690
+ with open(f'{prefix}_summary.txt', 'w') as f:
691
+ f.write("BACKTEST SUMMARY\n")
692
+ f.write("="*70 + "\n\n")
693
+ f.write(f"Strategy: {r.config.get('strategy_name', 'Unknown')}\n")
694
+ f.write(f"Period: {r.config.get('start_date', 'N/A')} to {r.config.get('end_date', 'N/A')}\n\n")
695
+
696
+ f.write("PERFORMANCE\n")
697
+ f.write("-"*70 + "\n")
698
+ f.write(f"Initial Capital: ${r.initial_capital:,.2f}\n")
699
+ f.write(f"Final Equity: ${r.final_capital:,.2f}\n")
700
+ f.write(f"Total Return: {m['total_return']:.2f}%\n")
701
+ f.write(f"Sharpe Ratio: {m['sharpe']:.2f}\n")
702
+ f.write(f"Max Drawdown: {m['max_drawdown']:.2f}%\n")
703
+ f.write(f"Win Rate: {m['win_rate']:.2f}%\n")
704
+ f.write(f"Total Trades: {m['total_trades']}\n")
705
+
706
+ print(f"Summary exported: {prefix}_summary.txt")
707
+
708
+
709
+ # ============================================================
710
+ # ONE-COMMAND RUNNER
711
+ # ============================================================
712
+ def run_backtest(strategy_function, config,
713
+ print_report=True,
714
+ create_charts=True,
715
+ export_results=True,
716
+ chart_filename='backtest_results.png',
717
+ export_prefix='backtest'):
718
+ """
719
+ Run complete backtest with one command
720
+
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
+ 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
+ analyzer = run_backtest(my_strategy, CONFIG)
744
+
745
+ # Access metrics
746
+ print(f"Sharpe: {analyzer.metrics['sharpe']:.2f}")
747
+ """
748
+
749
+ print("="*80)
750
+ print(" "*25 + "STARTING BACKTEST")
751
+ print("="*80)
752
+ print(f"Strategy: {config.get('strategy_name', 'Unknown')}")
753
+ print(f"Period: {config.get('start_date', 'N/A')} to {config.get('end_date', 'N/A')}")
754
+ print(f"Capital: ${config.get('initial_capital', 0):,.0f}")
755
+ print("="*80 + "\n")
756
+
757
+ # Run strategy
758
+ results = strategy_function(config)
759
+
760
+ # Calculate metrics
761
+ print("\n[*] Calculating metrics...")
762
+ analyzer = BacktestAnalyzer(results)
763
+ analyzer.calculate_all_metrics()
764
+
765
+ # Print report
766
+ if print_report:
767
+ print("\n" + "="*80)
768
+ ResultsReporter.print_full_report(analyzer)
769
+
770
+ # Create charts
771
+ if create_charts and len(results.trades) > 0:
772
+ print(f"\n[*] Creating charts: {chart_filename}")
773
+ try:
774
+ ChartGenerator.create_all_charts(analyzer, chart_filename)
775
+ print(f"[OK] Charts saved: {chart_filename}")
776
+ except Exception as e:
777
+ print(f"[ERROR] Chart creation failed: {e}")
778
+ elif create_charts and len(results.trades) == 0:
779
+ print("\n[!] No trades - skipping charts")
780
+
781
+ # Export results
782
+ if export_results and len(results.trades) > 0:
783
+ print(f"\n[*] Exporting results: {export_prefix}_*.csv")
784
+ try:
785
+ 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
+ except Exception as e:
791
+ 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
+
807
+ return analyzer
808
+
809
+
810
+ # Export all classes and functions
811
+ __all__ = [
812
+ 'BacktestResults',
813
+ 'BacktestAnalyzer',
814
+ 'ResultsReporter',
815
+ 'ChartGenerator',
816
+ 'ResultsExporter',
817
+ 'run_backtest',
818
+ 'init_api',
819
+ 'get_api_method',
820
+ 'APIManager'
821
+ ]
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.1
2
+ Name: ivolatility_backtesting
3
+ Version: 0.1.0
4
+ Summary: Universal Backtest Framework with One-Command Runner for IVolatility API
5
+ Author-email: Your Name <your.email@example.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Your Name
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ Project-URL: Homepage, https://github.com/yourusername/ivolatility_backtesting
28
+ Project-URL: Bug Tracker, https://github.com/yourusername/ivolatility_backtesting/issues
29
+ Keywords: backtesting,finance,trading,ivolatility
30
+ Classifier: Programming Language :: Python :: 3
31
+ Classifier: Programming Language :: Python :: 3.8
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Operating System :: OS Independent
34
+ Requires-Python: >=3.8
35
+ Description-Content-Type: text/markdown
36
+ License-File: LICENSE
37
+ Requires-Dist: pandas>=1.5.0
38
+ Requires-Dist: numpy>=1.21.0
39
+ Requires-Dist: matplotlib>=3.5.0
40
+ Requires-Dist: seaborn>=0.11.0
41
+ Requires-Dist: ivolatility
42
+
43
+ # IVolatility Backtesting
44
+ A universal backtesting framework for financial strategies using the IVolatility API.
45
+
46
+ ## Installation
47
+ ```bash
48
+ pip install ivolatility_backtesting
49
+ ```
50
+
51
+ **Note**: The `ivolatility` package is required but may not be available on PyPI. Contact IVolatility to obtain their SDK and install it manually before using this package.
52
+
53
+ ## Usage
54
+ ```python
55
+ from ivolatility_backtesting import run_backtest, init_api
56
+
57
+ # Initialize API
58
+ init_api("your-api-key")
59
+
60
+ # Define your strategy
61
+ def my_strategy(config):
62
+ # Strategy logic
63
+ return BacktestResults(
64
+ equity_curve=[100000, 110000],
65
+ equity_dates=["2023-01-01", "2023-01-02"],
66
+ trades=[{"pnl": 1000, "entry_date": "2023-01-01", "exit_date": "2023-01-02"}],
67
+ initial_capital=100000,
68
+ config=config
69
+ )
70
+
71
+ # Run backtest
72
+ CONFIG = {
73
+ "initial_capital": 100000,
74
+ "start_date": "2023-01-01",
75
+ "end_date": "2024-01-01",
76
+ "strategy_name": "My Strategy"
77
+ }
78
+ analyzer = run_backtest(my_strategy, CONFIG)
79
+
80
+ # Access metrics
81
+ print(f"Sharpe Ratio: {analyzer.metrics['sharpe']:.2f}")
82
+ ```
83
+
84
+ ## Requirements
85
+ - Python >= 3.10
86
+ - pandas >= 1.5.0
87
+ - numpy >= 1.21.0
88
+ - matplotlib >= 3.5.0
89
+ - seaborn >= 0.11.0
90
+ - ivolatility (contact IVolatility for SDK)
91
+
92
+ ## License
93
+ MIT License
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ ivolatility_backtesting/___init__.py
5
+ ivolatility_backtesting/ivolatility_backtesting.py
6
+ ivolatility_backtesting.egg-info/PKG-INFO
7
+ ivolatility_backtesting.egg-info/SOURCES.txt
8
+ ivolatility_backtesting.egg-info/dependency_links.txt
9
+ ivolatility_backtesting.egg-info/requires.txt
10
+ ivolatility_backtesting.egg-info/top_level.txt
@@ -0,0 +1,5 @@
1
+ pandas>=1.5.0
2
+ numpy>=1.21.0
3
+ matplotlib>=3.5.0
4
+ seaborn>=0.11.0
5
+ ivolatility
@@ -0,0 +1 @@
1
+ ivolatility_backtesting
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ivolatility_backtesting"
7
+ version = "0.1.0"
8
+ description = "Universal Backtest Framework with One-Command Runner for IVolatility API"
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Your Name", email = "your.email@example.com" }
12
+ ]
13
+ license = { file = "LICENSE" }
14
+ requires-python = ">=3.8"
15
+ dependencies = [
16
+ "pandas>=1.5.0",
17
+ "numpy>=1.21.0",
18
+ "matplotlib>=3.5.0",
19
+ "seaborn>=0.11.0",
20
+ "ivolatility" # Note: Not available on PyPI, must be installed manually
21
+ ]
22
+ keywords = ["backtesting", "finance", "trading", "ivolatility"]
23
+ classifiers = [
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3.8",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Operating System :: OS Independent",
28
+ ]
29
+
30
+ [project.urls]
31
+ "Homepage" = "https://github.com/yourusername/ivolatility_backtesting"
32
+ "Bug Tracker" = "https://github.com/yourusername/ivolatility_backtesting/issues"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+