ivolatility-backtesting 1.14.0__tar.gz → 1.18.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.14.0 → ivolatility_backtesting-1.18.0}/PKG-INFO +1 -1
- {ivolatility_backtesting-1.14.0 → ivolatility_backtesting-1.18.0}/ivolatility_backtesting/ivolatility_backtesting.py +215 -79
- {ivolatility_backtesting-1.14.0 → ivolatility_backtesting-1.18.0}/ivolatility_backtesting.egg-info/PKG-INFO +1 -1
- {ivolatility_backtesting-1.14.0 → ivolatility_backtesting-1.18.0}/pyproject.toml +1 -1
- {ivolatility_backtesting-1.14.0 → ivolatility_backtesting-1.18.0}/LICENSE +0 -0
- {ivolatility_backtesting-1.14.0 → ivolatility_backtesting-1.18.0}/README.md +0 -0
- {ivolatility_backtesting-1.14.0 → ivolatility_backtesting-1.18.0}/ivolatility_backtesting/__init__.py +0 -0
- {ivolatility_backtesting-1.14.0 → ivolatility_backtesting-1.18.0}/ivolatility_backtesting.egg-info/SOURCES.txt +0 -0
- {ivolatility_backtesting-1.14.0 → ivolatility_backtesting-1.18.0}/ivolatility_backtesting.egg-info/dependency_links.txt +0 -0
- {ivolatility_backtesting-1.14.0 → ivolatility_backtesting-1.18.0}/ivolatility_backtesting.egg-info/requires.txt +0 -0
- {ivolatility_backtesting-1.14.0 → ivolatility_backtesting-1.18.0}/ivolatility_backtesting.egg-info/top_level.txt +0 -0
- {ivolatility_backtesting-1.14.0 → ivolatility_backtesting-1.18.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ivolatility_backtesting
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.18.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
|
|
@@ -2234,6 +2234,10 @@ class StopLossConfig:
|
|
|
2234
2234
|
|
|
2235
2235
|
NEW METHOD:
|
|
2236
2236
|
- combined(): Requires BOTH pl_loss AND directional conditions
|
|
2237
|
+
|
|
2238
|
+
IMPORTANT:
|
|
2239
|
+
- directional(): Creates EOD directional stop (checked once per day)
|
|
2240
|
+
- For INTRADAY directional stops, use INTRADAY_STOPS_CONFIG (separate system)
|
|
2237
2241
|
"""
|
|
2238
2242
|
|
|
2239
2243
|
@staticmethod
|
|
@@ -2333,7 +2337,7 @@ class StopLossConfig:
|
|
|
2333
2337
|
|
|
2334
2338
|
@staticmethod
|
|
2335
2339
|
def directional(pct):
|
|
2336
|
-
"""
|
|
2340
|
+
"""EOD directional stop based on underlying movement (checked once per day)"""
|
|
2337
2341
|
decimal = StopLossConfig._normalize_pct(pct)
|
|
2338
2342
|
display = StopLossConfig._format_pct(pct)
|
|
2339
2343
|
|
|
@@ -2341,8 +2345,8 @@ class StopLossConfig:
|
|
|
2341
2345
|
'enabled': True,
|
|
2342
2346
|
'type': 'directional',
|
|
2343
2347
|
'value': decimal,
|
|
2344
|
-
'name': f'Directional {display}',
|
|
2345
|
-
'description': f'Stop when underlying moves {display}'
|
|
2348
|
+
'name': f'EOD Directional {display}',
|
|
2349
|
+
'description': f'Stop when underlying moves {display} (checked at EOD)'
|
|
2346
2350
|
}
|
|
2347
2351
|
|
|
2348
2352
|
# ========================================================
|
|
@@ -3288,29 +3292,19 @@ def optimize_parameters(base_config, param_grid, strategy_function,
|
|
|
3288
3292
|
test_config['_preloaded_options_cache'] = preloaded_options_df
|
|
3289
3293
|
# Otherwise, data is already in base_config from preload_data_universal
|
|
3290
3294
|
|
|
3291
|
-
#
|
|
3292
|
-
if has_widgets:
|
|
3293
|
-
# Use update_progress for full display with ETA, CPU, RAM
|
|
3294
|
-
update_progress(
|
|
3295
|
-
progress_bar, status_label, monitor,
|
|
3296
|
-
current=idx,
|
|
3297
|
-
total=total_combinations,
|
|
3298
|
-
start_time=start_time,
|
|
3299
|
-
message=f"Testing: {param_str}"
|
|
3300
|
-
)
|
|
3301
|
-
else:
|
|
3302
|
-
if idx % max(1, total_combinations // 10) == 0:
|
|
3303
|
-
print(f"[{idx}/{total_combinations}] {param_str}")
|
|
3304
|
-
|
|
3305
|
-
# ═══ MODIFY run_backtest CALL (lines ~2240-2248) ═══
|
|
3295
|
+
# ═══ CREATE COMPACT PARAMETER STRING EARLY (for progress display) ═══
|
|
3306
3296
|
try:
|
|
3307
|
-
# Create compact parameter string (e.g., Z1.0_E0.
|
|
3297
|
+
# Create compact parameter string (e.g., Z1.0_E0.1_L60_DT45)
|
|
3308
3298
|
param_parts = []
|
|
3309
3299
|
for name, value in zip(param_names, param_combo):
|
|
3310
3300
|
if 'z_score_entry' in name:
|
|
3311
3301
|
param_parts.append(f"Z{value}")
|
|
3312
3302
|
elif 'z_score_exit' in name:
|
|
3313
3303
|
param_parts.append(f"E{value}")
|
|
3304
|
+
elif 'lookback' in name:
|
|
3305
|
+
param_parts.append(f"L{value}")
|
|
3306
|
+
elif 'dte' in name:
|
|
3307
|
+
param_parts.append(f"DT{value}")
|
|
3314
3308
|
elif 'profit_target' in name:
|
|
3315
3309
|
if value is None:
|
|
3316
3310
|
param_parts.append("PTNo")
|
|
@@ -3325,13 +3319,70 @@ def optimize_parameters(base_config, param_grid, strategy_function,
|
|
|
3325
3319
|
|
|
3326
3320
|
compact_params = "_".join(param_parts)
|
|
3327
3321
|
|
|
3328
|
-
# Create
|
|
3329
|
-
|
|
3330
|
-
|
|
3322
|
+
# Create friendly string for display/logging
|
|
3323
|
+
param_display = ", ".join([f"{name}={value}" for name, value in zip(param_names, param_combo)])
|
|
3324
|
+
|
|
3325
|
+
# Add SL prefix if provided (from notebook loop)
|
|
3326
|
+
sl_prefix = test_config.get('_sl_prefix', '')
|
|
3327
|
+
if sl_prefix:
|
|
3328
|
+
# Format: SL3_cb1_Z1_E0.1_L60_DT45
|
|
3329
|
+
combo_name = f"{sl_prefix}_cb{idx}_{compact_params}"
|
|
3330
|
+
display_name = f"{sl_prefix}_{compact_params}"
|
|
3331
|
+
else:
|
|
3332
|
+
# Fallback: Add stop-loss to filename if enabled
|
|
3333
|
+
if test_config.get('stop_loss_enabled') and 'stop_loss_config' in test_config:
|
|
3334
|
+
sl_value = test_config['stop_loss_config'].get('value', 0)
|
|
3335
|
+
combo_name = f"cb{idx}_{compact_params}_SL{int(sl_value*100)}"
|
|
3336
|
+
display_name = f"{compact_params}_SL{int(sl_value*100)}"
|
|
3337
|
+
else:
|
|
3338
|
+
combo_name = f"cb{idx}_{compact_params}"
|
|
3339
|
+
display_name = compact_params
|
|
3331
3340
|
|
|
3332
|
-
#
|
|
3333
|
-
|
|
3341
|
+
# -----------------------------
|
|
3342
|
+
# Print combo header BEFORE running backtest (so user sees params)
|
|
3343
|
+
# -----------------------------
|
|
3344
|
+
print("\n" + "="*80)
|
|
3345
|
+
print(f"[{idx}/{total_combinations}] {combo_name}")
|
|
3346
|
+
print("="*80)
|
|
3347
|
+
print(f"• Parameters : {param_display}")
|
|
3348
|
+
if test_config.get('stop_loss_enabled') and 'stop_loss_config' in test_config:
|
|
3349
|
+
sl_cfg = test_config['stop_loss_config']
|
|
3350
|
+
sl_type = sl_cfg.get('type', 'unknown')
|
|
3351
|
+
sl_value = sl_cfg.get('value')
|
|
3352
|
+
if isinstance(sl_value, (int, float)):
|
|
3353
|
+
sl_value_display = f"{sl_value*100:.2f}%" if sl_type in ('pl_loss', 'fixed_pct', 'trailing', 'directional') else sl_value
|
|
3354
|
+
else:
|
|
3355
|
+
sl_value_display = sl_value
|
|
3356
|
+
print(f"• Stop-loss : {sl_type} -> {sl_value_display}")
|
|
3357
|
+
else:
|
|
3358
|
+
print("• Stop-loss : disabled")
|
|
3359
|
+
|
|
3360
|
+
intraday_cfg = test_config.get('intraday_stops', {})
|
|
3361
|
+
if intraday_cfg.get('enabled', False):
|
|
3362
|
+
intraday_pct = intraday_cfg.get('stop_pct')
|
|
3363
|
+
pct_text = f"{intraday_pct*100:.2f}%" if isinstance(intraday_pct, (int, float)) else intraday_pct
|
|
3364
|
+
print(f"• Intraday SL: enabled ({pct_text}, min_days={intraday_cfg.get('min_days_before_intraday', 'n/a')})")
|
|
3365
|
+
else:
|
|
3366
|
+
print("• Intraday SL: disabled")
|
|
3367
|
+
print("-"*80)
|
|
3368
|
+
|
|
3369
|
+
# Update progress with compact name (after printing parameters)
|
|
3370
|
+
if has_widgets:
|
|
3371
|
+
update_progress(
|
|
3372
|
+
progress_bar, status_label, monitor,
|
|
3373
|
+
current=idx,
|
|
3374
|
+
total=total_combinations,
|
|
3375
|
+
start_time=start_time,
|
|
3376
|
+
message=f"Testing: {display_name}"
|
|
3377
|
+
)
|
|
3378
|
+
|
|
3379
|
+
# Create combo folder: SL3_c01_Z1.0_E0.1_PT20
|
|
3380
|
+
combo_folder = os.path.join(results_folder, combo_name)
|
|
3381
|
+
os.makedirs(combo_folder, exist_ok=True)
|
|
3334
3382
|
|
|
3383
|
+
# File prefix: SL3_c01_Z1.0_E0.1_PT20
|
|
3384
|
+
combo_prefix = combo_name
|
|
3385
|
+
|
|
3335
3386
|
# Run backtest WITH EXPORT AND CHARTS (saved but not displayed)
|
|
3336
3387
|
analyzer = run_backtest(
|
|
3337
3388
|
strategy_function,
|
|
@@ -3360,10 +3411,6 @@ def optimize_parameters(base_config, param_grid, strategy_function,
|
|
|
3360
3411
|
status_symbol = "✓" if is_valid else "✗"
|
|
3361
3412
|
status_color = "#00cc00" if is_valid else "#ff6666"
|
|
3362
3413
|
|
|
3363
|
-
# Print combination header
|
|
3364
|
-
print(f"[{idx}/{total_combinations}] {param_str}")
|
|
3365
|
-
print("-" * 100)
|
|
3366
|
-
|
|
3367
3414
|
# Print chart file if created
|
|
3368
3415
|
if hasattr(analyzer, 'chart_file') and analyzer.chart_file:
|
|
3369
3416
|
print(f"Chart saved: {analyzer.chart_file}")
|
|
@@ -3400,7 +3447,7 @@ def optimize_parameters(base_config, param_grid, strategy_function,
|
|
|
3400
3447
|
resource_text = f"CPU: {cpu_pct:.0f}% | RAM: {ram_mb:.0f}MB"
|
|
3401
3448
|
|
|
3402
3449
|
status_label.value = (
|
|
3403
|
-
f"<b style='color:{status_color}'>[{idx}/{total_combinations}] {
|
|
3450
|
+
f"<b style='color:{status_color}'>[{idx}/{total_combinations}] {combo_name}</b><br>"
|
|
3404
3451
|
f"<span style='color:#666'>{result_text}</span><br>"
|
|
3405
3452
|
f"<span style='color:#999;font-size:10px'>{resource_text}</span>"
|
|
3406
3453
|
)
|
|
@@ -3422,12 +3469,28 @@ def optimize_parameters(base_config, param_grid, strategy_function,
|
|
|
3422
3469
|
'avg_win': analyzer.metrics['avg_win'],
|
|
3423
3470
|
'avg_loss': analyzer.metrics['avg_loss'],
|
|
3424
3471
|
'volatility': analyzer.metrics['volatility'],
|
|
3472
|
+
'combo_name': combo_name,
|
|
3473
|
+
'combo_folder': combo_folder if export_each_combo else "",
|
|
3425
3474
|
}
|
|
3426
3475
|
|
|
3427
3476
|
results.append(result)
|
|
3428
3477
|
|
|
3429
3478
|
# ═══ MEMORY CLEANUP AFTER EACH TEST ═══
|
|
3430
3479
|
# Delete large objects to free RAM for next iteration
|
|
3480
|
+
|
|
3481
|
+
# Clear references to preloaded data (prevents memory leaks)
|
|
3482
|
+
if use_legacy_preload:
|
|
3483
|
+
# Legacy preload method
|
|
3484
|
+
if '_preloaded_lean_df' in test_config:
|
|
3485
|
+
del test_config['_preloaded_lean_df']
|
|
3486
|
+
if '_preloaded_options_cache' in test_config:
|
|
3487
|
+
del test_config['_preloaded_options_cache']
|
|
3488
|
+
else:
|
|
3489
|
+
# Universal preloader - clear all preloaded keys
|
|
3490
|
+
for key in list(test_config.keys()):
|
|
3491
|
+
if key.startswith('_preloaded_'):
|
|
3492
|
+
del test_config[key]
|
|
3493
|
+
|
|
3431
3494
|
del analyzer, test_config
|
|
3432
3495
|
gc.collect()
|
|
3433
3496
|
|
|
@@ -3623,59 +3686,132 @@ def optimize_parameters(base_config, param_grid, strategy_function,
|
|
|
3623
3686
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
3624
3687
|
# NEW! FULL BACKTEST OF BEST COMBINATION WITH ALL CHARTS
|
|
3625
3688
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
3626
|
-
print("\n" + "="*80)
|
|
3627
|
-
print(" "*15 + "RUNNING FULL BACKTEST FOR BEST COMBINATION")
|
|
3628
|
-
print("="*80)
|
|
3629
|
-
print("\n📊 Creating detailed report for best combination...")
|
|
3630
|
-
print(f"Parameters: {', '.join([f'{k}={v}' for k, v in best_params.items()])}\n")
|
|
3631
|
-
|
|
3632
|
-
# Create config for best combination
|
|
3633
|
-
best_config = base_config.copy()
|
|
3634
|
-
best_config.update(best_params)
|
|
3635
|
-
if use_legacy_preload:
|
|
3636
|
-
best_config['_preloaded_lean_df'] = preloaded_lean_df
|
|
3637
|
-
best_config['_preloaded_options_cache'] = preloaded_options_df
|
|
3638
|
-
|
|
3639
|
-
# Create folder for best combination
|
|
3640
|
-
best_combo_folder = os.path.join(results_folder, 'best_combination')
|
|
3641
|
-
os.makedirs(best_combo_folder, exist_ok=True)
|
|
3642
|
-
|
|
3643
|
-
# Run FULL backtest with ALL charts and exports
|
|
3644
|
-
# Note: progress_context=None, so plt.show() will be called but fail due to renderer
|
|
3645
|
-
# We'll display charts explicitly afterwards using IPython.display.Image
|
|
3646
|
-
best_analyzer = run_backtest(
|
|
3647
|
-
strategy_function,
|
|
3648
|
-
best_config,
|
|
3649
|
-
print_report=True, # ← SHOW FULL REPORT
|
|
3650
|
-
create_charts=True, # ← CREATE ALL CHARTS
|
|
3651
|
-
export_results=True, # ← EXPORT ALL FILES
|
|
3652
|
-
progress_context=None, # ← Normal mode
|
|
3653
|
-
chart_filename=os.path.join(best_combo_folder, 'equity_curve.png'),
|
|
3654
|
-
export_prefix=os.path.join(best_combo_folder, 'best')
|
|
3655
|
-
)
|
|
3656
|
-
|
|
3657
|
-
# Save detailed metrics to optimization_metrics.csv
|
|
3658
|
-
metrics_data = {
|
|
3659
|
-
'metric': list(best_analyzer.metrics.keys()),
|
|
3660
|
-
'value': list(best_analyzer.metrics.values())
|
|
3661
|
-
}
|
|
3662
|
-
metrics_df = pd.DataFrame(metrics_data)
|
|
3663
|
-
metrics_path = os.path.join(results_folder, 'optimization_metrics.csv')
|
|
3664
|
-
metrics_df.to_csv(metrics_path, index=False)
|
|
3665
3689
|
|
|
3666
|
-
|
|
3667
|
-
|
|
3690
|
+
# Create compact parameter string for best combination
|
|
3691
|
+
param_parts = []
|
|
3692
|
+
for name, value in best_params.items():
|
|
3693
|
+
if 'z_score_entry' in name:
|
|
3694
|
+
param_parts.append(f"Z{value}")
|
|
3695
|
+
elif 'z_score_exit' in name:
|
|
3696
|
+
param_parts.append(f"E{value}")
|
|
3697
|
+
elif 'lookback' in name:
|
|
3698
|
+
param_parts.append(f"L{value}")
|
|
3699
|
+
elif 'dte' in name:
|
|
3700
|
+
param_parts.append(f"DT{value}")
|
|
3701
|
+
elif 'stop_loss' in name:
|
|
3702
|
+
param_parts.append(f"SL{int(value*100)}")
|
|
3703
|
+
|
|
3704
|
+
best_params_str = "_".join(param_parts) if param_parts else "best"
|
|
3705
|
+
|
|
3706
|
+
# Add SL prefix if provided (from notebook loop)
|
|
3707
|
+
sl_prefix = base_config.get('_sl_prefix', '')
|
|
3708
|
+
if sl_prefix:
|
|
3709
|
+
best_params_str_with_prefix = f"{sl_prefix}_{best_params_str}"
|
|
3710
|
+
else:
|
|
3711
|
+
best_params_str_with_prefix = best_params_str
|
|
3668
3712
|
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3713
|
+
metrics_path = os.path.join(results_folder, 'optimization_metrics.csv')
|
|
3714
|
+
|
|
3715
|
+
best_combo_name = best_result.get('combo_name', '')
|
|
3716
|
+
best_combo_folder_recorded = best_result.get('combo_folder', '')
|
|
3717
|
+
|
|
3718
|
+
if export_each_combo and best_combo_name and best_combo_folder_recorded and os.path.exists(best_combo_folder_recorded):
|
|
3719
|
+
print("\n" + "="*80)
|
|
3720
|
+
print(" "*15 + "USING EXISTING ARTIFACTS FOR BEST COMBINATION")
|
|
3721
|
+
print("="*80)
|
|
3722
|
+
print("\n⚡ export_each_combo=True → пропускаем повторный запуск")
|
|
3723
|
+
print("⚡ Используем ранее сохранённые файлы комбинации")
|
|
3724
|
+
print(f"Комбинация: {best_combo_name}")
|
|
3725
|
+
print(f"Путь: {best_combo_folder_recorded}")
|
|
3726
|
+
|
|
3727
|
+
metrics_dict = None
|
|
3728
|
+
metrics_json_path = os.path.join(best_combo_folder_recorded, f"{best_combo_name}_metrics.json")
|
|
3729
|
+
if os.path.exists(metrics_json_path):
|
|
3730
|
+
try:
|
|
3731
|
+
import json
|
|
3732
|
+
with open(metrics_json_path, 'r') as mj:
|
|
3733
|
+
metrics_dict = json.load(mj)
|
|
3734
|
+
except Exception as e:
|
|
3735
|
+
print(f"⚠ Не удалось загрузить {metrics_json_path}: {e}")
|
|
3736
|
+
|
|
3737
|
+
if metrics_dict is None:
|
|
3738
|
+
metrics_dict = {
|
|
3739
|
+
'total_return': best_result['total_return'],
|
|
3740
|
+
'sharpe': best_result['sharpe'],
|
|
3741
|
+
'sortino': best_result.get('sortino'),
|
|
3742
|
+
'calmar': best_result.get('calmar'),
|
|
3743
|
+
'max_drawdown': best_result['max_drawdown'],
|
|
3744
|
+
'win_rate': best_result['win_rate'],
|
|
3745
|
+
'profit_factor': best_result['profit_factor'],
|
|
3746
|
+
'total_trades': best_result['total_trades'],
|
|
3747
|
+
'avg_win': best_result['avg_win'],
|
|
3748
|
+
'avg_loss': best_result['avg_loss'],
|
|
3749
|
+
'volatility': best_result['volatility'],
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
metrics_df = pd.DataFrame({
|
|
3753
|
+
'metric': list(metrics_dict.keys()),
|
|
3754
|
+
'value': list(metrics_dict.values())
|
|
3755
|
+
})
|
|
3756
|
+
metrics_df.to_csv(metrics_path, index=False)
|
|
3757
|
+
print(f"\n✓ Метрики сохранены: {metrics_path} (использованы существующие данные)")
|
|
3758
|
+
print(f"✓ Артефакты лучшей комбинации: {best_combo_folder_recorded}/")
|
|
3759
|
+
|
|
3760
|
+
chart_file = os.path.join(best_combo_folder_recorded, 'equity_curve.png')
|
|
3675
3761
|
if os.path.exists(chart_file):
|
|
3676
|
-
print(f"
|
|
3677
|
-
|
|
3678
|
-
|
|
3762
|
+
print(f"📈 График equity: {chart_file}")
|
|
3763
|
+
summary_file = os.path.join(best_combo_folder_recorded, f"{best_combo_name}_summary.txt")
|
|
3764
|
+
if os.path.exists(summary_file):
|
|
3765
|
+
print(f"📄 Summary: {summary_file}")
|
|
3766
|
+
|
|
3767
|
+
else:
|
|
3768
|
+
print("\n" + "="*80)
|
|
3769
|
+
print(" "*15 + "RUNNING FULL BACKTEST FOR BEST COMBINATION")
|
|
3770
|
+
print("="*80)
|
|
3771
|
+
print("\n📊 Creating detailed report for best combination...")
|
|
3772
|
+
print(f"Parameters: {', '.join([f'{k}={v}' for k, v in best_params.items()])}")
|
|
3773
|
+
print(f"Files will be saved with prefix: BST_{best_params_str_with_prefix}_*\n")
|
|
3774
|
+
|
|
3775
|
+
# Create config for best combination
|
|
3776
|
+
best_config = base_config.copy()
|
|
3777
|
+
best_config.update(best_params)
|
|
3778
|
+
if use_legacy_preload:
|
|
3779
|
+
best_config['_preloaded_lean_df'] = preloaded_lean_df
|
|
3780
|
+
best_config['_preloaded_options_cache'] = preloaded_options_df
|
|
3781
|
+
|
|
3782
|
+
# Create folder for best combination with parameters in name
|
|
3783
|
+
best_combo_folder = os.path.join(results_folder, f'best_{best_params_str_with_prefix}')
|
|
3784
|
+
os.makedirs(best_combo_folder, exist_ok=True)
|
|
3785
|
+
|
|
3786
|
+
# Run FULL backtest with ALL charts and exports
|
|
3787
|
+
best_analyzer = run_backtest(
|
|
3788
|
+
strategy_function,
|
|
3789
|
+
best_config,
|
|
3790
|
+
print_report=True,
|
|
3791
|
+
create_charts=True,
|
|
3792
|
+
export_results=True,
|
|
3793
|
+
progress_context=None,
|
|
3794
|
+
chart_filename=os.path.join(best_combo_folder, f'BST_{best_params_str_with_prefix}_chart.png'),
|
|
3795
|
+
export_prefix=os.path.join(best_combo_folder, f'BST_{best_params_str_with_prefix}')
|
|
3796
|
+
)
|
|
3797
|
+
|
|
3798
|
+
metrics_data = {
|
|
3799
|
+
'metric': list(best_analyzer.metrics.keys()),
|
|
3800
|
+
'value': list(best_analyzer.metrics.values())
|
|
3801
|
+
}
|
|
3802
|
+
metrics_df = pd.DataFrame(metrics_data)
|
|
3803
|
+
metrics_df.to_csv(metrics_path, index=False)
|
|
3804
|
+
|
|
3805
|
+
print(f"\n✓ Detailed metrics saved: {metrics_path}")
|
|
3806
|
+
print(f"✓ Best combination results saved to: {best_combo_folder}/")
|
|
3807
|
+
print(f" Files: BST_{best_params_str_with_prefix}_*.csv, BST_{best_params_str_with_prefix}_chart.png")
|
|
3808
|
+
|
|
3809
|
+
try:
|
|
3810
|
+
chart_file = os.path.join(best_combo_folder, f'BST_{best_params_str_with_prefix}_chart.png')
|
|
3811
|
+
if os.path.exists(chart_file):
|
|
3812
|
+
print(f"\n📈 Best combination charts saved to: {chart_file}")
|
|
3813
|
+
except Exception as e:
|
|
3814
|
+
print(f"\n⚠ Could not display charts (saved to {best_combo_folder}/): {e}")
|
|
3679
3815
|
|
|
3680
3816
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
3681
3817
|
# CREATE OPTIMIZATION COMPARISON CHARTS (save only, display in notebook manually)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ivolatility_backtesting
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.18.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.18.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|