quantex 0.4.3__tar.gz → 0.4.5__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.
- {quantex-0.4.3 → quantex-0.4.5}/PKG-INFO +1 -1
- {quantex-0.4.3 → quantex-0.4.5}/pyproject.toml +1 -1
- {quantex-0.4.3 → quantex-0.4.5}/src/quantex/backtester/montecarlo.py +84 -6
- {quantex-0.4.3 → quantex-0.4.5}/LICENSE.md +0 -0
- {quantex-0.4.3 → quantex-0.4.5}/README.md +0 -0
- {quantex-0.4.3 → quantex-0.4.5}/src/quantex/__init__.py +0 -0
- {quantex-0.4.3 → quantex-0.4.5}/src/quantex/backtester/__init__.py +0 -0
- {quantex-0.4.3 → quantex-0.4.5}/src/quantex/backtester/backtester.py +0 -0
- {quantex-0.4.3 → quantex-0.4.5}/src/quantex/backtester/constants.py +0 -0
- {quantex-0.4.3 → quantex-0.4.5}/src/quantex/backtester/data_splits.py +0 -0
- {quantex-0.4.3 → quantex-0.4.5}/src/quantex/backtester/metrics.py +0 -0
- {quantex-0.4.3 → quantex-0.4.5}/src/quantex/backtester/parallel.py +0 -0
- {quantex-0.4.3 → quantex-0.4.5}/src/quantex/backtester/reports.py +0 -0
- {quantex-0.4.3 → quantex-0.4.5}/src/quantex/broker/__init__.py +0 -0
- {quantex-0.4.3 → quantex-0.4.5}/src/quantex/broker/broker.py +0 -0
- {quantex-0.4.3 → quantex-0.4.5}/src/quantex/broker/types.py +0 -0
- {quantex-0.4.3 → quantex-0.4.5}/src/quantex/datasource.py +0 -0
- {quantex-0.4.3 → quantex-0.4.5}/src/quantex/helpers.py +0 -0
- {quantex-0.4.3 → quantex-0.4.5}/src/quantex/indicators.py +0 -0
- {quantex-0.4.3 → quantex-0.4.5}/src/quantex/strategy.py +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Monte Carlo simulation module for quantex backtesting.
|
|
3
3
|
|
|
4
|
-
This module provides Monte Carlo simulation capabilities to test strategy
|
|
5
|
-
robustness through
|
|
6
|
-
1. Trade Order Randomization - shuffles the sequence of executed trades
|
|
7
|
-
2.
|
|
4
|
+
This module provides Monte Carlo simulation capabilities to test strategy
|
|
5
|
+
robustness through three approaches:
|
|
6
|
+
1. Trade Order Randomization - shuffles the sequence of executed trades
|
|
7
|
+
2. Trade Shuffle With Replacement - resamples trade returns with replacement
|
|
8
|
+
3. Price Path Resampling (Bootstrap) - resamples historical returns to create synthetic paths
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
import copy
|
|
@@ -33,6 +34,7 @@ class MonteCarloMode(Enum):
|
|
|
33
34
|
BOTH: Run both analyses and combine results
|
|
34
35
|
"""
|
|
35
36
|
TRADE_ORDER = "trade_order"
|
|
37
|
+
TRADE_SHUFFLE_WITH_REPLACEMENT = "trade_shuffle_with_replacement"
|
|
36
38
|
PRICE_PATH = "price_path"
|
|
37
39
|
BOTH = "both"
|
|
38
40
|
|
|
@@ -340,8 +342,10 @@ def _run_trade_order_simulation(
|
|
|
340
342
|
equity_values = np.asarray(original_equity.values, dtype=np.float64)
|
|
341
343
|
equity_returns = np.zeros_like(equity_values)
|
|
342
344
|
if len(equity_values) > 1:
|
|
343
|
-
prev = np.
|
|
344
|
-
|
|
345
|
+
prev = np.empty_like(equity_values)
|
|
346
|
+
prev[0] = original_cash
|
|
347
|
+
prev[1:] = equity_values[:-1]
|
|
348
|
+
equity_returns[1:] = np.where(prev[1:] > 0, (equity_values[1:] / prev[1:]) - 1.0, 0.0)
|
|
345
349
|
|
|
346
350
|
# Keep the starting cash anchored at index 0 and randomize the remaining
|
|
347
351
|
# returns so the path always begins from the actual initial capital.
|
|
@@ -356,6 +360,56 @@ def _run_trade_order_simulation(
|
|
|
356
360
|
return pd.Series(equity, index=index)
|
|
357
361
|
|
|
358
362
|
|
|
363
|
+
def _run_trade_shuffle_with_replacement_simulation(
|
|
364
|
+
original_orders: list[Order],
|
|
365
|
+
original_cash: float,
|
|
366
|
+
original_equity: pd.Series,
|
|
367
|
+
commission: float,
|
|
368
|
+
commission_type,
|
|
369
|
+
lot_size: int,
|
|
370
|
+
seed: int | None = None,
|
|
371
|
+
) -> pd.Series:
|
|
372
|
+
"""
|
|
373
|
+
Run a Monte Carlo simulation that samples trade outcomes with replacement.
|
|
374
|
+
|
|
375
|
+
This mode allows some trades to be repeated while others may be omitted.
|
|
376
|
+
The sampled trade outcomes are then applied as percentage returns to the
|
|
377
|
+
portfolio curve.
|
|
378
|
+
"""
|
|
379
|
+
if seed is not None:
|
|
380
|
+
random.seed(seed)
|
|
381
|
+
|
|
382
|
+
index = original_equity.index
|
|
383
|
+
equity = np.full(len(index), original_cash, dtype=np.float64)
|
|
384
|
+
|
|
385
|
+
equity_values = np.asarray(original_equity.values, dtype=np.float64)
|
|
386
|
+
equity_returns = np.zeros_like(equity_values)
|
|
387
|
+
if len(equity_values) > 1:
|
|
388
|
+
prev = np.empty_like(equity_values)
|
|
389
|
+
prev[0] = original_cash
|
|
390
|
+
prev[1:] = equity_values[:-1]
|
|
391
|
+
equity_returns[1:] = np.where(prev[1:] > 0, (equity_values[1:] / prev[1:]) - 1.0, 0.0)
|
|
392
|
+
|
|
393
|
+
trade_returns = equity_returns[1:].tolist()
|
|
394
|
+
if not trade_returns:
|
|
395
|
+
return pd.Series(equity, index=index)
|
|
396
|
+
|
|
397
|
+
# Build a replacement-sampled sequence that may include repeated trade
|
|
398
|
+
# outcomes and omit others entirely by only sampling a subset of the trade
|
|
399
|
+
# return pool on each simulation, then pad the rest with neutral returns so
|
|
400
|
+
# the full curve length is preserved.
|
|
401
|
+
sample_size = max(1, int(len(trade_returns) * 0.75))
|
|
402
|
+
sampled_returns = [random.choice(trade_returns) for _ in range(sample_size)]
|
|
403
|
+
sampled_returns.extend([0.0] * (len(trade_returns) - sample_size))
|
|
404
|
+
|
|
405
|
+
equity_returns = np.concatenate(([0.0], np.asarray(sampled_returns, dtype=np.float64)))
|
|
406
|
+
|
|
407
|
+
for i in range(1, len(equity)):
|
|
408
|
+
equity[i] = equity[i - 1] * (1.0 + equity_returns[i])
|
|
409
|
+
|
|
410
|
+
return pd.Series(equity, index=index)
|
|
411
|
+
|
|
412
|
+
|
|
359
413
|
def _run_price_path_simulation(
|
|
360
414
|
strategy: Strategy,
|
|
361
415
|
data_sources: dict[str, DataSource],
|
|
@@ -581,6 +635,21 @@ def monte_carlo(
|
|
|
581
635
|
# No trades, just return original equity
|
|
582
636
|
curve = original_equity.copy()
|
|
583
637
|
equity_curves.append(curve)
|
|
638
|
+
|
|
639
|
+
if mode == MonteCarloMode.TRADE_SHUFFLE_WITH_REPLACEMENT:
|
|
640
|
+
if len(original_orders) > 0:
|
|
641
|
+
curve = _run_trade_shuffle_with_replacement_simulation(
|
|
642
|
+
original_orders,
|
|
643
|
+
original_cash,
|
|
644
|
+
original_equity,
|
|
645
|
+
self.commission,
|
|
646
|
+
self.commission_type,
|
|
647
|
+
self.lot_size,
|
|
648
|
+
seed=iter_seed,
|
|
649
|
+
)
|
|
650
|
+
else:
|
|
651
|
+
curve = original_equity.copy()
|
|
652
|
+
equity_curves.append(curve)
|
|
584
653
|
|
|
585
654
|
if mode == MonteCarloMode.PRICE_PATH or mode == MonteCarloMode.BOTH:
|
|
586
655
|
# Price path resampling
|
|
@@ -635,6 +704,15 @@ def monte_carlo(
|
|
|
635
704
|
"trade_order": trade_result.percentile_results,
|
|
636
705
|
"price_path": price_result.percentile_results,
|
|
637
706
|
}
|
|
707
|
+
elif mode == MonteCarloMode.TRADE_SHUFFLE_WITH_REPLACEMENT:
|
|
708
|
+
result = MonteCarloResult(
|
|
709
|
+
mode=mode,
|
|
710
|
+
equity_curves=equity_curves,
|
|
711
|
+
original_equity=original_equity,
|
|
712
|
+
simulations=simulations,
|
|
713
|
+
starting_cash=original_cash,
|
|
714
|
+
)
|
|
715
|
+
result._compute_statistics()
|
|
638
716
|
else:
|
|
639
717
|
# Create result object
|
|
640
718
|
result = MonteCarloResult(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|