ivolatility-backtesting 1.0.0__tar.gz → 1.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.
- {ivolatility_backtesting-1.0.0 → ivolatility_backtesting-1.1.0}/PKG-INFO +1 -1
- {ivolatility_backtesting-1.0.0 → ivolatility_backtesting-1.1.0}/ivolatility_backtesting/ivolatility_backtesting.py +170 -124
- {ivolatility_backtesting-1.0.0 → ivolatility_backtesting-1.1.0}/ivolatility_backtesting.egg-info/PKG-INFO +1 -1
- {ivolatility_backtesting-1.0.0 → ivolatility_backtesting-1.1.0}/pyproject.toml +1 -1
- {ivolatility_backtesting-1.0.0 → ivolatility_backtesting-1.1.0}/LICENSE +0 -0
- {ivolatility_backtesting-1.0.0 → ivolatility_backtesting-1.1.0}/README.md +0 -0
- {ivolatility_backtesting-1.0.0 → ivolatility_backtesting-1.1.0}/ivolatility_backtesting/__init__.py +0 -0
- {ivolatility_backtesting-1.0.0 → ivolatility_backtesting-1.1.0}/ivolatility_backtesting.egg-info/SOURCES.txt +0 -0
- {ivolatility_backtesting-1.0.0 → ivolatility_backtesting-1.1.0}/ivolatility_backtesting.egg-info/dependency_links.txt +0 -0
- {ivolatility_backtesting-1.0.0 → ivolatility_backtesting-1.1.0}/ivolatility_backtesting.egg-info/requires.txt +0 -0
- {ivolatility_backtesting-1.0.0 → ivolatility_backtesting-1.1.0}/ivolatility_backtesting.egg-info/top_level.txt +0 -0
- {ivolatility_backtesting-1.0.0 → ivolatility_backtesting-1.1.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ivolatility_backtesting
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: A universal backtesting framework for financial strategies using the IVolatility API.
|
|
5
5
|
Author-email: IVolatility <support@ivolatility.com>
|
|
6
6
|
Project-URL: Homepage, https://ivolatility.com
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
"""
|
|
2
|
-
ivolatility_backtesting.py
|
|
3
|
-
Universal Backtest Framework with
|
|
2
|
+
ivolatility_backtesting.py - UPDATED VERSION
|
|
3
|
+
Universal Backtest Framework with API Response Normalization
|
|
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
|
|
4
10
|
|
|
5
11
|
Usage:
|
|
6
12
|
from ivolatility_backtesting import *
|
|
@@ -8,8 +14,10 @@ Usage:
|
|
|
8
14
|
# Initialize API once
|
|
9
15
|
init_api(os.getenv("API_KEY"))
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
# Use API helper for normalized responses
|
|
18
|
+
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)
|
|
13
21
|
"""
|
|
14
22
|
|
|
15
23
|
import pandas as pd
|
|
@@ -26,21 +34,96 @@ plt.rcParams['figure.figsize'] = (15, 8)
|
|
|
26
34
|
|
|
27
35
|
|
|
28
36
|
# ============================================================
|
|
29
|
-
#
|
|
37
|
+
# API HELPER - NEW!
|
|
30
38
|
# ============================================================
|
|
31
|
-
class
|
|
39
|
+
class APIHelper:
|
|
32
40
|
"""
|
|
33
|
-
|
|
41
|
+
Helper class for normalized API responses
|
|
42
|
+
Automatically handles both dict and DataFrame responses
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def normalize_response(response, debug=False):
|
|
47
|
+
"""
|
|
48
|
+
Convert API response to consistent dict format
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
response: API response (dict, DataFrame, or other)
|
|
52
|
+
debug: Print debug information
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
dict with 'data' key containing list of records, or None if invalid
|
|
56
|
+
"""
|
|
57
|
+
if response is None:
|
|
58
|
+
if debug:
|
|
59
|
+
print("[APIHelper] Response is None")
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
# Case 1: Already a dict with 'data' key
|
|
63
|
+
if isinstance(response, dict):
|
|
64
|
+
if 'data' in response:
|
|
65
|
+
if debug:
|
|
66
|
+
print(f"[APIHelper] Dict response with {len(response['data'])} records")
|
|
67
|
+
return response
|
|
68
|
+
else:
|
|
69
|
+
if debug:
|
|
70
|
+
print("[APIHelper] Dict response without 'data' key")
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
# Case 2: DataFrame - convert to dict
|
|
74
|
+
if isinstance(response, pd.DataFrame):
|
|
75
|
+
if response.empty:
|
|
76
|
+
if debug:
|
|
77
|
+
print("[APIHelper] Empty DataFrame")
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
records = response.to_dict('records')
|
|
81
|
+
if debug:
|
|
82
|
+
print(f"[APIHelper] Converted DataFrame to dict with {len(records)} records")
|
|
83
|
+
return {'data': records, 'status': 'success'}
|
|
84
|
+
|
|
85
|
+
# Case 3: Unknown type
|
|
86
|
+
if debug:
|
|
87
|
+
print(f"[APIHelper] Unexpected response type: {type(response)}")
|
|
88
|
+
return None
|
|
34
89
|
|
|
35
|
-
|
|
36
|
-
|
|
90
|
+
@staticmethod
|
|
91
|
+
def safe_dataframe(response, debug=False):
|
|
92
|
+
"""
|
|
93
|
+
Safely convert API response to DataFrame
|
|
37
94
|
|
|
38
|
-
|
|
39
|
-
|
|
95
|
+
Args:
|
|
96
|
+
response: API response (any type)
|
|
97
|
+
debug: Print debug information
|
|
40
98
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
|
|
119
|
+
|
|
120
|
+
# ============================================================
|
|
121
|
+
# GLOBAL API MANAGER (Updated)
|
|
122
|
+
# ============================================================
|
|
123
|
+
class APIManager:
|
|
124
|
+
"""
|
|
125
|
+
Centralized API key management for IVolatility API
|
|
126
|
+
Now includes response normalization
|
|
44
127
|
"""
|
|
45
128
|
_api_key = None
|
|
46
129
|
_methods = {}
|
|
@@ -56,17 +139,8 @@ class APIManager:
|
|
|
56
139
|
|
|
57
140
|
@classmethod
|
|
58
141
|
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
|
-
"""
|
|
142
|
+
"""Get API method with automatic key injection"""
|
|
68
143
|
if cls._api_key is None:
|
|
69
|
-
# Auto-initialize from environment if not set
|
|
70
144
|
api_key = os.getenv("API_KEY")
|
|
71
145
|
if not api_key:
|
|
72
146
|
raise ValueError(
|
|
@@ -74,9 +148,7 @@ class APIManager:
|
|
|
74
148
|
)
|
|
75
149
|
cls.initialize(api_key)
|
|
76
150
|
|
|
77
|
-
# Cache methods to avoid recreation
|
|
78
151
|
if endpoint not in cls._methods:
|
|
79
|
-
# Re-set login params before creating method
|
|
80
152
|
ivol.setLoginParams(apiKey=cls._api_key)
|
|
81
153
|
cls._methods[endpoint] = ivol.setMethod(endpoint)
|
|
82
154
|
|
|
@@ -88,56 +160,80 @@ class APIManager:
|
|
|
88
160
|
return cls._api_key is not None
|
|
89
161
|
|
|
90
162
|
|
|
91
|
-
# Public API functions
|
|
163
|
+
# Public API functions (Updated)
|
|
92
164
|
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
|
-
"""
|
|
165
|
+
"""Initialize IVolatility API with key"""
|
|
104
166
|
if api_key is None:
|
|
105
167
|
api_key = os.getenv("API_KEY")
|
|
106
168
|
APIManager.initialize(api_key)
|
|
107
169
|
|
|
108
170
|
|
|
109
171
|
def get_api_method(endpoint):
|
|
172
|
+
"""Get API method for specified endpoint"""
|
|
173
|
+
return APIManager.get_method(endpoint)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def api_call(endpoint, debug=False, **kwargs):
|
|
110
177
|
"""
|
|
111
|
-
|
|
178
|
+
Make API call with automatic response normalization
|
|
112
179
|
|
|
113
180
|
Args:
|
|
114
181
|
endpoint: API endpoint path
|
|
182
|
+
debug: Enable debug output
|
|
183
|
+
**kwargs: API parameters
|
|
115
184
|
|
|
116
185
|
Returns:
|
|
117
|
-
|
|
186
|
+
dict with 'data' key (normalized format) or None if error
|
|
118
187
|
|
|
119
188
|
Example:
|
|
120
|
-
|
|
121
|
-
|
|
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'])
|
|
196
|
+
|
|
197
|
+
# New way (automatic):
|
|
198
|
+
data = api_call('/equities/eod/stock-prices', symbol='AAPL', from_='2024-01-01')
|
|
199
|
+
if data:
|
|
200
|
+
df = pd.DataFrame(data['data'])
|
|
122
201
|
"""
|
|
123
|
-
|
|
202
|
+
try:
|
|
203
|
+
method = get_api_method(endpoint)
|
|
204
|
+
response = method(**kwargs)
|
|
205
|
+
|
|
206
|
+
normalized = APIHelper.normalize_response(response, debug=debug)
|
|
207
|
+
|
|
208
|
+
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}")
|
|
211
|
+
|
|
212
|
+
return normalized
|
|
213
|
+
|
|
214
|
+
except Exception as e:
|
|
215
|
+
if debug:
|
|
216
|
+
print(f"[api_call] Exception: {e}")
|
|
217
|
+
print(f"[api_call] Endpoint: {endpoint}")
|
|
218
|
+
print(f"[api_call] Parameters: {kwargs}")
|
|
219
|
+
return None
|
|
124
220
|
|
|
125
221
|
|
|
222
|
+
# ============================================================
|
|
223
|
+
# BACKTEST RESULTS (Unchanged)
|
|
224
|
+
# ============================================================
|
|
126
225
|
class BacktestResults:
|
|
127
|
-
"""
|
|
128
|
-
Universal container for backtest results
|
|
129
|
-
ANY strategy must return this format
|
|
130
|
-
"""
|
|
226
|
+
"""Universal container for backtest results"""
|
|
131
227
|
def __init__(self,
|
|
132
|
-
equity_curve,
|
|
133
|
-
equity_dates,
|
|
134
|
-
trades,
|
|
135
|
-
initial_capital,
|
|
136
|
-
config,
|
|
137
|
-
benchmark_prices=None,
|
|
138
|
-
benchmark_symbol='SPY',
|
|
139
|
-
daily_returns=None,
|
|
140
|
-
debug_info=None):
|
|
228
|
+
equity_curve,
|
|
229
|
+
equity_dates,
|
|
230
|
+
trades,
|
|
231
|
+
initial_capital,
|
|
232
|
+
config,
|
|
233
|
+
benchmark_prices=None,
|
|
234
|
+
benchmark_symbol='SPY',
|
|
235
|
+
daily_returns=None,
|
|
236
|
+
debug_info=None):
|
|
141
237
|
|
|
142
238
|
self.equity_curve = equity_curve
|
|
143
239
|
self.equity_dates = equity_dates
|
|
@@ -149,7 +245,6 @@ class BacktestResults:
|
|
|
149
245
|
self.benchmark_symbol = benchmark_symbol
|
|
150
246
|
self.debug_info = debug_info if debug_info else []
|
|
151
247
|
|
|
152
|
-
# Calculate daily returns if not provided
|
|
153
248
|
if daily_returns is None and len(equity_curve) > 1:
|
|
154
249
|
self.daily_returns = [
|
|
155
250
|
(equity_curve[i] - equity_curve[i-1]) / equity_curve[i-1]
|
|
@@ -158,7 +253,6 @@ class BacktestResults:
|
|
|
158
253
|
else:
|
|
159
254
|
self.daily_returns = daily_returns if daily_returns else []
|
|
160
255
|
|
|
161
|
-
# Calculate max drawdown
|
|
162
256
|
self.max_drawdown = self._calculate_max_drawdown()
|
|
163
257
|
|
|
164
258
|
def _calculate_max_drawdown(self):
|
|
@@ -169,11 +263,11 @@ class BacktestResults:
|
|
|
169
263
|
return abs(np.min(drawdowns))
|
|
170
264
|
|
|
171
265
|
|
|
266
|
+
# ============================================================
|
|
267
|
+
# BACKTEST ANALYZER (Unchanged - same as before)
|
|
268
|
+
# ============================================================
|
|
172
269
|
class BacktestAnalyzer:
|
|
173
|
-
"""
|
|
174
|
-
Universal metrics calculator
|
|
175
|
-
Works with any BacktestResults object
|
|
176
|
-
"""
|
|
270
|
+
"""Universal metrics calculator"""
|
|
177
271
|
def __init__(self, results):
|
|
178
272
|
self.results = results
|
|
179
273
|
self.metrics = {}
|
|
@@ -186,13 +280,12 @@ class BacktestAnalyzer:
|
|
|
186
280
|
self.metrics['total_pnl'] = r.final_capital - r.initial_capital
|
|
187
281
|
self.metrics['total_return'] = (self.metrics['total_pnl'] / r.initial_capital) * 100
|
|
188
282
|
|
|
189
|
-
# CAGR
|
|
283
|
+
# CAGR with protection
|
|
190
284
|
if len(r.equity_dates) > 0:
|
|
191
285
|
start_date = min(r.equity_dates)
|
|
192
286
|
end_date = max(r.equity_dates)
|
|
193
287
|
days_diff = (end_date - start_date).days
|
|
194
288
|
|
|
195
|
-
# PROTECTION: если даты одинаковые или разница < 1 день
|
|
196
289
|
if days_diff <= 0:
|
|
197
290
|
self.metrics['cagr'] = 0
|
|
198
291
|
self.metrics['show_cagr'] = False
|
|
@@ -438,6 +531,10 @@ class BacktestAnalyzer:
|
|
|
438
531
|
return min(exposure_pct, 100.0)
|
|
439
532
|
|
|
440
533
|
|
|
534
|
+
# ============================================================
|
|
535
|
+
# RESULTS REPORTER, CHART GENERATOR, RESULTS EXPORTER
|
|
536
|
+
# (All unchanged - same as before)
|
|
537
|
+
# ============================================================
|
|
441
538
|
class ResultsReporter:
|
|
442
539
|
"""Universal results printer"""
|
|
443
540
|
|
|
@@ -451,17 +548,15 @@ class ResultsReporter:
|
|
|
451
548
|
print("="*80)
|
|
452
549
|
print()
|
|
453
550
|
|
|
454
|
-
# PRINT DEBUG INFO IF AVAILABLE
|
|
455
551
|
if hasattr(r, 'debug_info') and len(r.debug_info) > 0:
|
|
456
552
|
print("DEBUG INFORMATION")
|
|
457
553
|
print("-"*80)
|
|
458
|
-
for debug_msg in r.debug_info[:10]:
|
|
554
|
+
for debug_msg in r.debug_info[:10]:
|
|
459
555
|
print(debug_msg)
|
|
460
556
|
if len(r.debug_info) > 10:
|
|
461
557
|
print(f"... and {len(r.debug_info) - 10} more debug messages")
|
|
462
558
|
print()
|
|
463
559
|
|
|
464
|
-
# Profitability
|
|
465
560
|
print("PROFITABILITY METRICS")
|
|
466
561
|
print("-"*80)
|
|
467
562
|
print(f"Initial Capital: ${r.initial_capital:>15,.2f}")
|
|
@@ -475,7 +570,6 @@ class ResultsReporter:
|
|
|
475
570
|
print(f"Annualized Return: {m['cagr']:>15.2f}% (extrapolated to 1 year)")
|
|
476
571
|
print()
|
|
477
572
|
|
|
478
|
-
# Risk
|
|
479
573
|
print("RISK METRICS")
|
|
480
574
|
print("-"*80)
|
|
481
575
|
print(f"Sharpe Ratio: {m['sharpe']:>15.2f} (>1 good, >2 excellent)")
|
|
@@ -506,7 +600,6 @@ class ResultsReporter:
|
|
|
506
600
|
print(f"Beta (vs {r.benchmark_symbol}): {m['beta']:>15.2f} (<1 defensive, >1 aggressive)")
|
|
507
601
|
print(f"R^2 (vs {r.benchmark_symbol}): {m['r_squared']:>15.2f} (market correlation 0-1)")
|
|
508
602
|
|
|
509
|
-
# Warning for unrealistic results
|
|
510
603
|
if abs(m['total_return']) > 200 or m['volatility'] > 150:
|
|
511
604
|
print()
|
|
512
605
|
print("UNREALISTIC RESULTS DETECTED:")
|
|
@@ -518,7 +611,6 @@ class ResultsReporter:
|
|
|
518
611
|
|
|
519
612
|
print()
|
|
520
613
|
|
|
521
|
-
# Efficiency
|
|
522
614
|
print("EFFICIENCY METRICS")
|
|
523
615
|
print("-"*80)
|
|
524
616
|
if m['recovery_factor'] != 0:
|
|
@@ -527,7 +619,6 @@ class ResultsReporter:
|
|
|
527
619
|
print(f"Exposure Time: {m['exposure_time']:>15.1f}% (time in market)")
|
|
528
620
|
print()
|
|
529
621
|
|
|
530
|
-
# Trading stats
|
|
531
622
|
print("TRADING STATISTICS")
|
|
532
623
|
print("-"*80)
|
|
533
624
|
print(f"Total Trades: {m['total_trades']:>15}")
|
|
@@ -671,14 +762,12 @@ class ResultsExporter:
|
|
|
671
762
|
print("No trades to export")
|
|
672
763
|
return
|
|
673
764
|
|
|
674
|
-
# Export trades
|
|
675
765
|
trades_df = pd.DataFrame(r.trades)
|
|
676
766
|
trades_df['entry_date'] = pd.to_datetime(trades_df['entry_date']).dt.strftime('%Y-%m-%d')
|
|
677
767
|
trades_df['exit_date'] = pd.to_datetime(trades_df['exit_date']).dt.strftime('%Y-%m-%d')
|
|
678
768
|
trades_df.to_csv(f'{prefix}_trades.csv', index=False)
|
|
679
769
|
print(f"Trades exported: {prefix}_trades.csv")
|
|
680
770
|
|
|
681
|
-
# Export equity curve
|
|
682
771
|
equity_df = pd.DataFrame({
|
|
683
772
|
'date': pd.to_datetime(r.equity_dates).strftime('%Y-%m-%d'),
|
|
684
773
|
'equity': r.equity_curve
|
|
@@ -686,7 +775,6 @@ class ResultsExporter:
|
|
|
686
775
|
equity_df.to_csv(f'{prefix}_equity.csv', index=False)
|
|
687
776
|
print(f"Equity exported: {prefix}_equity.csv")
|
|
688
777
|
|
|
689
|
-
# Export summary
|
|
690
778
|
with open(f'{prefix}_summary.txt', 'w') as f:
|
|
691
779
|
f.write("BACKTEST SUMMARY\n")
|
|
692
780
|
f.write("="*70 + "\n\n")
|
|
@@ -707,7 +795,7 @@ class ResultsExporter:
|
|
|
707
795
|
|
|
708
796
|
|
|
709
797
|
# ============================================================
|
|
710
|
-
# ONE-COMMAND RUNNER
|
|
798
|
+
# ONE-COMMAND RUNNER (Unchanged)
|
|
711
799
|
# ============================================================
|
|
712
800
|
def run_backtest(strategy_function, config,
|
|
713
801
|
print_report=True,
|
|
@@ -715,36 +803,7 @@ def run_backtest(strategy_function, config,
|
|
|
715
803
|
export_results=True,
|
|
716
804
|
chart_filename='backtest_results.png',
|
|
717
805
|
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
|
-
"""
|
|
806
|
+
"""Run complete backtest with one command"""
|
|
748
807
|
|
|
749
808
|
print("="*80)
|
|
750
809
|
print(" "*25 + "STARTING BACKTEST")
|
|
@@ -754,20 +813,16 @@ def run_backtest(strategy_function, config,
|
|
|
754
813
|
print(f"Capital: ${config.get('initial_capital', 0):,.0f}")
|
|
755
814
|
print("="*80 + "\n")
|
|
756
815
|
|
|
757
|
-
# Run strategy
|
|
758
816
|
results = strategy_function(config)
|
|
759
817
|
|
|
760
|
-
# Calculate metrics
|
|
761
818
|
print("\n[*] Calculating metrics...")
|
|
762
819
|
analyzer = BacktestAnalyzer(results)
|
|
763
820
|
analyzer.calculate_all_metrics()
|
|
764
821
|
|
|
765
|
-
# Print report
|
|
766
822
|
if print_report:
|
|
767
823
|
print("\n" + "="*80)
|
|
768
824
|
ResultsReporter.print_full_report(analyzer)
|
|
769
825
|
|
|
770
|
-
# Create charts
|
|
771
826
|
if create_charts and len(results.trades) > 0:
|
|
772
827
|
print(f"\n[*] Creating charts: {chart_filename}")
|
|
773
828
|
try:
|
|
@@ -778,7 +833,6 @@ def run_backtest(strategy_function, config,
|
|
|
778
833
|
elif create_charts and len(results.trades) == 0:
|
|
779
834
|
print("\n[!] No trades - skipping charts")
|
|
780
835
|
|
|
781
|
-
# Export results
|
|
782
836
|
if export_results and len(results.trades) > 0:
|
|
783
837
|
print(f"\n[*] Exporting results: {export_prefix}_*.csv")
|
|
784
838
|
try:
|
|
@@ -792,22 +846,12 @@ def run_backtest(strategy_function, config,
|
|
|
792
846
|
elif export_results and len(results.trades) == 0:
|
|
793
847
|
print("\n[!] No trades - skipping export")
|
|
794
848
|
|
|
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
849
|
return analyzer
|
|
808
850
|
|
|
809
851
|
|
|
810
|
-
#
|
|
852
|
+
# ============================================================
|
|
853
|
+
# EXPORTS
|
|
854
|
+
# ============================================================
|
|
811
855
|
__all__ = [
|
|
812
856
|
'BacktestResults',
|
|
813
857
|
'BacktestAnalyzer',
|
|
@@ -817,5 +861,7 @@ __all__ = [
|
|
|
817
861
|
'run_backtest',
|
|
818
862
|
'init_api',
|
|
819
863
|
'get_api_method',
|
|
864
|
+
'api_call', # NEW!
|
|
865
|
+
'APIHelper', # NEW!
|
|
820
866
|
'APIManager'
|
|
821
|
-
]
|
|
867
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ivolatility_backtesting
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: A universal backtesting framework for financial strategies using the IVolatility API.
|
|
5
5
|
Author-email: IVolatility <support@ivolatility.com>
|
|
6
6
|
Project-URL: Homepage, https://ivolatility.com
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ivolatility_backtesting"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.1.0"
|
|
8
8
|
description = "A universal backtesting framework for financial strategies using the IVolatility API."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
File without changes
|
|
File without changes
|
{ivolatility_backtesting-1.0.0 → ivolatility_backtesting-1.1.0}/ivolatility_backtesting/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|