| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- from okx_codex_trader.backtest import run_backtest
- from okx_codex_trader.models import Candle
- from okx_codex_trader.strategy import simple_moving_average
- def build_crossing_series() -> list[Candle]:
- closes = [
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 140.0,
- 50.0,
- 50.0,
- 50.0,
- 50.0,
- 50.0,
- 50.0,
- 50.0,
- 50.0,
- 50.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- 60.0,
- ]
- opens = list(closes)
- opens[21] = 100.0
- opens[30] = 90.0
- opens[40] = 80.0
- candles = []
- for index, (open_price, close_price) in enumerate(zip(opens, closes)):
- high = max(open_price, close_price)
- low = min(open_price, close_price)
- candles.append(
- Candle(
- symbol="BTC-USDT-SWAP",
- ts=index,
- open=open_price,
- high=high,
- low=low,
- close=close_price,
- volume=1_000.0,
- )
- )
- return candles
- def build_open_position_series() -> list[Candle]:
- candles = build_crossing_series()[:29]
- return candles
- def build_drawdown_series() -> list[Candle]:
- closes = [60.0] * 20 + [120.0, 75.0, 100.0] + [75.0] * 14
- opens = list(closes)
- opens[21] = 100.0
- opens[36] = 100.0
- candles = []
- for index, (open_price, close_price) in enumerate(zip(opens, closes)):
- high = max(open_price, close_price)
- low = min(open_price, close_price)
- candles.append(
- Candle(
- symbol="BTC-USDT-SWAP",
- ts=index,
- open=open_price,
- high=high,
- low=low,
- close=close_price,
- volume=1_000.0,
- )
- )
- return candles
- def test_simple_moving_average_requires_full_window():
- candles = [
- Candle(symbol="BTC-USDT-SWAP", ts=index, open=close, high=close, low=close, close=close, volume=1_000.0)
- for index, close in enumerate([10.0, 20.0, 30.0, 40.0])
- ]
- assert simple_moving_average(candles, 3) == [None, None, 20.0, 30.0]
- def test_backtest_rejects_invalid_leverage():
- candles = build_crossing_series()
- for leverage in (0, 4):
- try:
- run_backtest(candles=candles, leverage=leverage)
- except ValueError as exc:
- assert str(exc) == "leverage is invalid"
- else:
- raise AssertionError("expected ValueError")
- def test_backtest_runs_fixed_sma_crossover_series():
- candles = build_crossing_series()
- result = run_backtest(candles=candles, leverage=2)
- assert result.initial_equity == 10_000
- assert result.trade_count == 2
- assert result.trades[0].entry_price == candles[21].open
- assert result.trades[0].exit_price == candles[30].open
- assert result.trades[0].margin_used == 10_000
- assert result.trades[1].margin_used == result.trades[0].ending_equity
- assert result.ending_equity == 4_888.888888888889
- assert result.total_return == -0.5111111111111112
- assert "total_return" in result.to_dict()
- assert "max_drawdown" in result.to_dict()
- assert result.win_rate == 0.5
- def test_backtest_does_not_force_close_open_position_at_series_end():
- candles = build_open_position_series()
- result = run_backtest(candles=candles, leverage=2)
- assert result.trade_count == 0
- assert result.trades == []
- assert result.ending_equity == 0.0
- assert result.total_return == -1.0
- def test_backtest_tracks_open_trade_drawdown_from_candle_close():
- candles = build_drawdown_series()
- result = run_backtest(candles=candles, leverage=2)
- assert result.trade_count == 1
- assert result.trades[0].entry_price == candles[21].open
- assert result.trades[0].exit_price == candles[36].open
- assert result.max_drawdown == 0.5
|