38e1d64437
CI / lint-test-build (push) Successful in 2m31s
- Introduced backtesting page and fragment in the dashboard for running backtests and viewing recent reports. - Implemented backtest run logic with configuration options including event path, starting balances, trade capital, and fee profiles. - Added recent backtest reports storage and retrieval. - Created a new strategy module for statistical arbitrage experiments with validation on configuration parameters. - Updated settings to include parameters for the statistical arbitrage strategy. - Enhanced dashboard controls to support the new strategy mode. - Added unit tests for backtesting functionality and strategy validation. - Updated templates for backtesting UI integration.
67 lines
2.0 KiB
Python
67 lines
2.0 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime, timedelta
|
|
|
|
from arbitrade.strategy.stat_arb import StatArbExperiment, StatArbExperimentConfig
|
|
|
|
|
|
def test_stat_arb_experiment_warmup_then_entry_and_exit() -> None:
|
|
started_at = datetime(2026, 6, 2, 12, 0, tzinfo=UTC)
|
|
experiment = StatArbExperiment(
|
|
StatArbExperimentConfig(
|
|
pair_a="BTC/USD",
|
|
pair_b="ETH/USD",
|
|
lookback_window=5,
|
|
entry_zscore=1.5,
|
|
exit_zscore=0.2,
|
|
max_holding_seconds=0.5,
|
|
)
|
|
)
|
|
|
|
# Warmup with nearly stationary spread around 0.
|
|
for idx in range(5):
|
|
signal = experiment.observe(
|
|
price_a=100.0 + (0.02 * idx),
|
|
price_b=100.0,
|
|
observed_at=started_at + timedelta(seconds=idx),
|
|
)
|
|
|
|
assert signal.action in {"warmup", "hold"}
|
|
|
|
# Large positive spread should trigger short-spread entry.
|
|
entry = experiment.observe(
|
|
price_a=104.0,
|
|
price_b=100.0,
|
|
observed_at=started_at + timedelta(seconds=10),
|
|
)
|
|
assert entry.action == "enter_short_spread"
|
|
assert entry.position == "short"
|
|
assert entry.zscore is not None
|
|
|
|
# Mean reversion toward center should trigger exit.
|
|
exit_signal = experiment.observe(
|
|
price_a=100.05,
|
|
price_b=100.0,
|
|
observed_at=started_at + timedelta(seconds=11),
|
|
)
|
|
assert exit_signal.action == "exit_position"
|
|
assert exit_signal.position == "flat"
|
|
|
|
|
|
def test_stat_arb_experiment_rejects_invalid_prices() -> None:
|
|
experiment = StatArbExperiment(
|
|
StatArbExperimentConfig(
|
|
pair_a="BTC/USD",
|
|
pair_b="ETH/USD",
|
|
lookback_window=5,
|
|
)
|
|
)
|
|
|
|
at = datetime(2026, 6, 2, 12, 0, tzinfo=UTC)
|
|
try:
|
|
experiment.observe(price_a=0.0, price_b=100.0, observed_at=at)
|
|
except ValueError as exc:
|
|
assert "prices must be > 0" in str(exc)
|
|
else:
|
|
raise AssertionError("Expected ValueError for non-positive price")
|