feat: add 5-day reversal strategy

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Yuxuan Yan
2026-06-07 09:26:35 +08:00
parent fd86190e9a
commit 085e51abf1
3 changed files with 52 additions and 4 deletions
+4 -4
View File
@@ -1,9 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""End-to-end smoke test: download data -> backtest SMA crossover -> print results.""" """End-to-end smoke test: download data -> backtest 5-day reversal -> print results."""
import logging import logging
from backtest.config import BacktestConfig from backtest.config import BacktestConfig
from backtest.runner import BacktestRunner from backtest.runner import BacktestRunner
from strategies.base import SmaCross from strategies.reversal import FiveDayReversal
from analysis.report import print_results from analysis.report import print_results
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
@@ -11,14 +11,14 @@ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(me
def main(): def main():
config = BacktestConfig( config = BacktestConfig(
symbols=["sh600000"], # 浦发银行 symbols=["sh600000", "sz000001", "sh600519"], # 浦发银行, 平安银行, 贵州茅台
start_date="2023-01-01", start_date="2023-01-01",
end_date="2024-12-31", end_date="2024-12-31",
initial_cash=1_000_000, initial_cash=1_000_000,
) )
runner = BacktestRunner(config) runner = BacktestRunner(config)
results = runner.run(SmaCross) results = runner.run(FiveDayReversal)
print_results(results, config.initial_cash) print_results(results, config.initial_cash)
+27
View File
@@ -0,0 +1,27 @@
import backtrader as bt
class FiveDayReversal(bt.Strategy):
"""Buy on 5-day oversold signal, sell on bounce or time stop."""
params = (
("lookback", 5),
("entry_threshold", -0.05), # buy when 5-day return < -5%
("exit_threshold", 0.0), # sell when 1-day return > 0%
("max_hold", 3), # max hold days
)
def __init__(self):
self.roc5 = bt.indicators.RateOfChange(self.data.close, period=self.params.lookback)
self.hold_counter = 0
def next(self):
if not self.position:
if self.roc5[0] < self.params.entry_threshold: # RateOfChange returns a fraction, not %
self.buy()
self.hold_counter = 0
else:
self.hold_counter += 1
roc1 = (self.data.close[0] / self.data.close[-1] - 1) * 100
if roc1 > self.params.exit_threshold * 100 or self.hold_counter >= self.params.max_hold:
self.close()
+21
View File
@@ -0,0 +1,21 @@
import pytest
from backtest.config import BacktestConfig
from backtest.runner import BacktestRunner
from strategies.reversal import FiveDayReversal
def test_reversal_smoke():
"""Smoke test: run a minimal reversal backtest and check results exist."""
config = BacktestConfig(
symbols=["sh600000"],
start_date="2024-01-01",
end_date="2024-03-31",
initial_cash=100_000,
)
runner = BacktestRunner(config)
results = runner.run(FiveDayReversal)
assert results is not None
assert len(results) == 1
# Check analyzers exist
sharpe = results[0].analyzers.sharpe.get_analysis()
assert "sharperatio" in sharpe