mirror of
https://github.com/OpenByteInc/QuantDinger.git
synced 2026-09-28 23:32:55 +08:00
129 lines
3.8 KiB
Python
129 lines
3.8 KiB
Python
from datetime import datetime
|
|
|
|
import pytest
|
|
|
|
from app.services.backtest_limits import (
|
|
BacktestRangeLimitError,
|
|
backtest_range_policy_metadata,
|
|
validate_backtest_range,
|
|
)
|
|
from app.services.strategy_v2.service import StrategyV2BacktestService
|
|
|
|
|
|
def test_forex_intraday_range_error_includes_actionable_recommendation():
|
|
err = validate_backtest_range(
|
|
market="Forex",
|
|
symbol="EURUSD",
|
|
timeframe="15m",
|
|
start_date=datetime(2024, 1, 1),
|
|
end_date=datetime(2024, 4, 1, 23, 59, 59),
|
|
)
|
|
|
|
assert err is not None
|
|
assert err["error_type"] == "BACKTEST_RANGE_LIMIT"
|
|
assert err["max_days"] == 60
|
|
assert err["recommendation_available"] is True
|
|
assert err["recommended_start"] == "2024-02-02"
|
|
assert err["recommended_end"] == "2024-02-29"
|
|
assert "Suggested fix: use 2024-02-02 to 2024-04-01" in err["msg"]
|
|
assert "set end date to 2024-02-29" in err["msg"]
|
|
|
|
|
|
def test_recommendation_accounts_for_indicator_warmup_bars():
|
|
err = validate_backtest_range(
|
|
market="Forex",
|
|
symbol="EURUSD",
|
|
timeframe="15m",
|
|
start_date=datetime(2024, 1, 1),
|
|
end_date=datetime(2024, 4, 1, 23, 59, 59),
|
|
warmup_bars=96,
|
|
)
|
|
|
|
assert err is not None
|
|
assert err["warmup_bars"] == 96
|
|
assert err["warmup_days"] == 1
|
|
assert err["fetch_start"] == "2023-12-31"
|
|
assert err["recommended_start"] == "2024-02-03"
|
|
assert err["recommended_end"] == "2024-02-28"
|
|
assert "including 96 warmup bars" in err["msg"]
|
|
|
|
|
|
def test_range_equal_to_limit_is_allowed():
|
|
err = validate_backtest_range(
|
|
market="Forex",
|
|
symbol="EURUSD",
|
|
timeframe="15m",
|
|
start_date=datetime(2024, 1, 1),
|
|
end_date=datetime(2024, 3, 1, 0, 0, 0),
|
|
)
|
|
|
|
assert err is None
|
|
|
|
|
|
def test_warmup_larger_than_policy_has_no_fake_date_recommendation():
|
|
err = validate_backtest_range(
|
|
market="USStock",
|
|
symbol="TSLA",
|
|
timeframe="1m",
|
|
start_date=datetime(2024, 1, 10),
|
|
end_date=datetime(2024, 1, 10, 23, 59, 59),
|
|
warmup_bars=60 * 24 * 10,
|
|
)
|
|
|
|
assert err is not None
|
|
assert err["max_days"] == 7
|
|
assert err["warmup_days"] == 10
|
|
assert err["recommendation_available"] is False
|
|
assert err["recommended_start"] is None
|
|
assert err["recommended_end"] is None
|
|
assert "warmup alone exceeds" in err["msg"]
|
|
|
|
|
|
def test_policy_metadata_uses_strictest_market_and_normalizes_timeframe():
|
|
policy = backtest_range_policy_metadata(
|
|
markets=["Crypto", "USStock"],
|
|
timeframe="1h",
|
|
warmup_bars=24,
|
|
)
|
|
|
|
assert policy["timeframe"] == "1H"
|
|
assert policy["market"] == "USStock"
|
|
assert policy["maxDays"] == 700
|
|
assert policy["warmupDays"] == 2
|
|
assert policy["maxSelectedDays"] == 698
|
|
|
|
|
|
def test_service_rejects_one_year_of_one_minute_data_before_fetching():
|
|
code = '''
|
|
def initialize(context):
|
|
context.set_universe(["Crypto:BTC/USDT"])
|
|
context.subscribe(frequency="1m")
|
|
|
|
def handle_data(context, data):
|
|
pass
|
|
'''
|
|
|
|
def unexpected_fetch(*_args, **_kwargs):
|
|
raise AssertionError("market data must not be fetched for an oversized request")
|
|
|
|
service = StrategyV2BacktestService(
|
|
repository=object(),
|
|
universe_service=object(),
|
|
frame_fetcher=unexpected_fetch,
|
|
snapshot_store=object(),
|
|
)
|
|
|
|
with pytest.raises(BacktestRangeLimitError) as caught:
|
|
service.run(
|
|
user_id=1,
|
|
code=code,
|
|
start_date=datetime(2025, 7, 19),
|
|
end_date=datetime(2026, 7, 19, 23, 59, 59),
|
|
initial_capital=10_000,
|
|
persist=False,
|
|
)
|
|
|
|
assert caught.value.details["error_type"] == "BACKTEST_RANGE_LIMIT"
|
|
assert caught.value.details["timeframe"] == "1m"
|
|
assert caught.value.details["max_days"] == 30
|