quantex 0.4.4__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.4 → quantex-0.4.5}/PKG-INFO +1 -1
- {quantex-0.4.4 → quantex-0.4.5}/pyproject.toml +1 -1
- {quantex-0.4.4 → quantex-0.4.5}/src/quantex/backtester/montecarlo.py +80 -4
- {quantex-0.4.4 → quantex-0.4.5}/LICENSE.md +0 -0
- {quantex-0.4.4 → quantex-0.4.5}/README.md +0 -0
- {quantex-0.4.4 → quantex-0.4.5}/src/quantex/__init__.py +0 -0
- {quantex-0.4.4 → quantex-0.4.5}/src/quantex/backtester/__init__.py +0 -0
- {quantex-0.4.4 → quantex-0.4.5}/src/quantex/backtester/backtester.py +0 -0
- {quantex-0.4.4 → quantex-0.4.5}/src/quantex/backtester/constants.py +0 -0
- {quantex-0.4.4 → quantex-0.4.5}/src/quantex/backtester/data_splits.py +0 -0
- {quantex-0.4.4 → quantex-0.4.5}/src/quantex/backtester/metrics.py +0 -0
- {quantex-0.4.4 → quantex-0.4.5}/src/quantex/backtester/parallel.py +0 -0
- {quantex-0.4.4 → quantex-0.4.5}/src/quantex/backtester/reports.py +0 -0
- {quantex-0.4.4 → quantex-0.4.5}/src/quantex/broker/__init__.py +0 -0
- {quantex-0.4.4 → quantex-0.4.5}/src/quantex/broker/broker.py +0 -0
- {quantex-0.4.4 → quantex-0.4.5}/src/quantex/broker/types.py +0 -0
- {quantex-0.4.4 → quantex-0.4.5}/src/quantex/datasource.py +0 -0
- {quantex-0.4.4 → quantex-0.4.5}/src/quantex/helpers.py +0 -0
- {quantex-0.4.4 → quantex-0.4.5}/src/quantex/indicators.py +0 -0
- {quantex-0.4.4 → 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
|
|
|
@@ -358,6 +360,56 @@ def _run_trade_order_simulation(
|
|
|
358
360
|
return pd.Series(equity, index=index)
|
|
359
361
|
|
|
360
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
|
+
|
|
361
413
|
def _run_price_path_simulation(
|
|
362
414
|
strategy: Strategy,
|
|
363
415
|
data_sources: dict[str, DataSource],
|
|
@@ -583,6 +635,21 @@ def monte_carlo(
|
|
|
583
635
|
# No trades, just return original equity
|
|
584
636
|
curve = original_equity.copy()
|
|
585
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)
|
|
586
653
|
|
|
587
654
|
if mode == MonteCarloMode.PRICE_PATH or mode == MonteCarloMode.BOTH:
|
|
588
655
|
# Price path resampling
|
|
@@ -637,6 +704,15 @@ def monte_carlo(
|
|
|
637
704
|
"trade_order": trade_result.percentile_results,
|
|
638
705
|
"price_path": price_result.percentile_results,
|
|
639
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()
|
|
640
716
|
else:
|
|
641
717
|
# Create result object
|
|
642
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
|