ivolatility-backtesting 2.144__tar.gz → 2.145__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.
- {ivolatility_backtesting-2.144 → ivolatility_backtesting-2.145}/PKG-INFO +1 -1
- {ivolatility_backtesting-2.144 → ivolatility_backtesting-2.145}/ivolatility_backtesting/ivolatility_backtesting.py +50 -27
- {ivolatility_backtesting-2.144 → ivolatility_backtesting-2.145}/ivolatility_backtesting.egg-info/PKG-INFO +1 -1
- {ivolatility_backtesting-2.144 → ivolatility_backtesting-2.145}/pyproject.toml +1 -1
- {ivolatility_backtesting-2.144 → ivolatility_backtesting-2.145}/README.md +0 -0
- {ivolatility_backtesting-2.144 → ivolatility_backtesting-2.145}/ivolatility_backtesting/__init__.py +0 -0
- {ivolatility_backtesting-2.144 → ivolatility_backtesting-2.145}/ivolatility_backtesting.egg-info/SOURCES.txt +0 -0
- {ivolatility_backtesting-2.144 → ivolatility_backtesting-2.145}/ivolatility_backtesting.egg-info/dependency_links.txt +0 -0
- {ivolatility_backtesting-2.144 → ivolatility_backtesting-2.145}/ivolatility_backtesting.egg-info/requires.txt +0 -0
- {ivolatility_backtesting-2.144 → ivolatility_backtesting-2.145}/ivolatility_backtesting.egg-info/top_level.txt +0 -0
- {ivolatility_backtesting-2.144 → ivolatility_backtesting-2.145}/setup.cfg +0 -0
- {ivolatility_backtesting-2.144 → ivolatility_backtesting-2.145}/tests/test_2142_fixes.py +0 -0
- {ivolatility_backtesting-2.144 → ivolatility_backtesting-2.145}/tests/test_2144_duckdb_dedup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ivolatility_backtesting
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.145
|
|
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
|
|
@@ -179,8 +179,29 @@ _API_LOG_FILE = None # Track API log file path for summary printing
|
|
|
179
179
|
# ============================================================
|
|
180
180
|
from typing import Optional, Dict, Any, Tuple
|
|
181
181
|
|
|
182
|
+
_NUMERIC_COERCE_WARNED = False
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _as_data_row(source):
|
|
186
|
+
"""Normalize a stock/data row to a single record.
|
|
187
|
+
|
|
188
|
+
Callers legitimately pass either a row (``df.iloc[0]``) or a one-row slice
|
|
189
|
+
(``df[df['date'] == d]``). Only a 2-D object needs ``.iloc[0]``: applying it
|
|
190
|
+
to a Series would collapse it to its first cell.
|
|
191
|
+
"""
|
|
192
|
+
if source is None:
|
|
193
|
+
return None
|
|
194
|
+
try:
|
|
195
|
+
if getattr(source, 'ndim', 1) > 1:
|
|
196
|
+
return source.iloc[0] if len(source) > 0 else None
|
|
197
|
+
except Exception:
|
|
198
|
+
return source
|
|
199
|
+
return source
|
|
200
|
+
|
|
201
|
+
|
|
182
202
|
def _first_valid_numeric_value(source, columns) -> Optional[float]:
|
|
183
203
|
"""Return the first usable numeric value from source[column] in priority order."""
|
|
204
|
+
source = _as_data_row(source)
|
|
184
205
|
if source is None:
|
|
185
206
|
return None
|
|
186
207
|
for col in columns:
|
|
@@ -196,7 +217,16 @@ def _first_valid_numeric_value(source, columns) -> Optional[float]:
|
|
|
196
217
|
if value is None or pd.isna(value):
|
|
197
218
|
continue
|
|
198
219
|
return float(value)
|
|
199
|
-
except Exception:
|
|
220
|
+
except Exception as exc:
|
|
221
|
+
# Shape errors here used to vanish silently and surface later as a
|
|
222
|
+
# wrong number (fallback instead of the real price). Warn once per
|
|
223
|
+
# run so the cause is visible without flooding the notebook output.
|
|
224
|
+
global _NUMERIC_COERCE_WARNED
|
|
225
|
+
if not _NUMERIC_COERCE_WARNED:
|
|
226
|
+
_NUMERIC_COERCE_WARNED = True
|
|
227
|
+
print(f"\u26a0\ufe0f Could not read numeric field '{col}' from the data row "
|
|
228
|
+
f"({type(exc).__name__}: {exc}). Falling back to defaults — "
|
|
229
|
+
f"check that stock_row is a row or a one-row slice.")
|
|
200
230
|
continue
|
|
201
231
|
return None
|
|
202
232
|
|
|
@@ -266,14 +296,7 @@ def _resolve_runtime_underlying_price(stock_price=None, stock_row=None, options_
|
|
|
266
296
|
Resolve the best option-aligned underlying price for runtime P&L / SL logic.
|
|
267
297
|
Priority: stock_row runtime fields -> options underlying_price -> passed scalar.
|
|
268
298
|
"""
|
|
269
|
-
row_obj =
|
|
270
|
-
try:
|
|
271
|
-
if stock_row is not None and hasattr(stock_row, 'iloc') and len(stock_row) > 0:
|
|
272
|
-
row_obj = stock_row.iloc[0]
|
|
273
|
-
elif stock_row is not None:
|
|
274
|
-
row_obj = stock_row
|
|
275
|
-
except Exception:
|
|
276
|
-
row_obj = stock_row
|
|
299
|
+
row_obj = _as_data_row(stock_row)
|
|
277
300
|
|
|
278
301
|
value = _first_valid_numeric_value(
|
|
279
302
|
row_obj,
|
|
@@ -297,14 +320,7 @@ def _resolve_runtime_underlying_price(stock_price=None, stock_row=None, options_
|
|
|
297
320
|
|
|
298
321
|
def _resolve_runtime_stock_range(stock_row=None, fallback_price=None) -> Tuple[Optional[float], Optional[float]]:
|
|
299
322
|
"""Resolve runtime high/low in the same coordinate system as runtime price."""
|
|
300
|
-
row_obj =
|
|
301
|
-
try:
|
|
302
|
-
if stock_row is not None and hasattr(stock_row, 'iloc') and len(stock_row) > 0:
|
|
303
|
-
row_obj = stock_row.iloc[0]
|
|
304
|
-
elif stock_row is not None:
|
|
305
|
-
row_obj = stock_row
|
|
306
|
-
except Exception:
|
|
307
|
-
row_obj = stock_row
|
|
323
|
+
row_obj = _as_data_row(stock_row)
|
|
308
324
|
|
|
309
325
|
high = _first_valid_numeric_value(row_obj, ('runtime_high', 'high'))
|
|
310
326
|
low = _first_valid_numeric_value(row_obj, ('runtime_low', 'low'))
|
|
@@ -1515,6 +1531,7 @@ def report_results_folder(
|
|
|
1515
1531
|
return {"folder": folder, "strategies": strategies}
|
|
1516
1532
|
|
|
1517
1533
|
|
|
1534
|
+
|
|
1518
1535
|
def run_backtest_notebook(
|
|
1519
1536
|
base_config: dict,
|
|
1520
1537
|
strategy_fn,
|
|
@@ -13715,15 +13732,12 @@ class PositionManager:
|
|
|
13715
13732
|
if stock_row is not None:
|
|
13716
13733
|
# runtime_open = split-aligned open (REFERENCE §4a) — the
|
|
13717
13734
|
# AM-settlement reference must not use the adjusted series
|
|
13718
|
-
|
|
13719
|
-
|
|
13720
|
-
|
|
13721
|
-
|
|
13722
|
-
|
|
13723
|
-
|
|
13724
|
-
if _ov is not None and not pd.isna(_ov) and float(_ov) > 0:
|
|
13725
|
-
_stock_open = float(_ov)
|
|
13726
|
-
break
|
|
13735
|
+
# stock_row may arrive as a single-row slice (stock_df[mask]) —
|
|
13736
|
+
# take the row itself, otherwise .get() yields a Series and any
|
|
13737
|
+
# boolean/float use of it raises ValueError.
|
|
13738
|
+
_stock_open = _first_valid_numeric_value(stock_row, ('runtime_open', 'open'))
|
|
13739
|
+
if _stock_open is not None and _stock_open <= 0:
|
|
13740
|
+
_stock_open = None
|
|
13727
13741
|
price_data[pos_id].update({
|
|
13728
13742
|
'underlying_high': stock_high,
|
|
13729
13743
|
'underlying_low': stock_low,
|
|
@@ -15708,6 +15722,14 @@ class ResultsReporter:
|
|
|
15708
15722
|
print("TRADING STATISTICS")
|
|
15709
15723
|
print("-"*80)
|
|
15710
15724
|
print(f"Total Trades: {m['total_trades']:>15}")
|
|
15725
|
+
if not m.get('total_trades'):
|
|
15726
|
+
# 0 trades is almost never a real result — it usually means the entry
|
|
15727
|
+
# signal never fired (unread config key, indicators not computed).
|
|
15728
|
+
print("\u26a0\ufe0f 0 trades: the entry signal never fired — this is not a "
|
|
15729
|
+
"result, it is a run that never entered. Check that indicators were "
|
|
15730
|
+
"computed (look for 'Processing N cached indicators' — 0 means the "
|
|
15731
|
+
"signal could not be evaluated) and that the data loaded (a locked "
|
|
15732
|
+
"DuckDB cache or a failed fetch also ends in 0 trades).")
|
|
15711
15733
|
print(f"Winning Trades: {m['winning_trades']:>15}")
|
|
15712
15734
|
print(f"Losing Trades: {m['losing_trades']:>15}")
|
|
15713
15735
|
print(f"Win Rate: {m['win_rate']:>15.2f}% (% profitable trades)")
|
|
@@ -25550,7 +25572,7 @@ def print_signals_table(config, indicator_cache, stock_df, save_path=None, silen
|
|
|
25550
25572
|
expected_ivx_key = ('iv_lean_zscore_ivx', (_cfg_dte, int(_cfg_lb))) if _cfg_lb is not None else None
|
|
25551
25573
|
|
|
25552
25574
|
if expected_raw_key is not None and expected_raw_key not in indicator_cache and should_print:
|
|
25553
|
-
present_lbs = sorted({k[1][0] for k in indicator_cache if k[0] == 'iv_lean_zscore' and isinstance(k[1], tuple)})
|
|
25575
|
+
present_lbs = sorted({k[1][0] for k in indicator_cache if k[0] == 'iv_lean_zscore' and isinstance(k[1], tuple) and k[1]})
|
|
25554
25576
|
print(f"⚠️ print_signals_table: no iv_lean_zscore cache for lookback={_cfg_lb} "
|
|
25555
25577
|
f"(cache has: {present_lbs}). Table will be empty for active config.")
|
|
25556
25578
|
|
|
@@ -26803,6 +26825,7 @@ def run_optimization(base_config, param_grid, strategy_function,
|
|
|
26803
26825
|
(combined_results_df, baselines_dict, results_folder)
|
|
26804
26826
|
baselines_dict: {symbol: metrics_dict}
|
|
26805
26827
|
"""
|
|
26828
|
+
|
|
26806
26829
|
import copy as _copy
|
|
26807
26830
|
|
|
26808
26831
|
if symbols is None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ivolatility_backtesting
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.145
|
|
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 = "2.
|
|
7
|
+
version = "2.145"
|
|
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
|
{ivolatility_backtesting-2.144 → ivolatility_backtesting-2.145}/ivolatility_backtesting/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ivolatility_backtesting-2.144 → ivolatility_backtesting-2.145}/tests/test_2144_duckdb_dedup.py
RENAMED
|
File without changes
|