/ PlanarDev / test / test_backtest.jl
test_backtest.jl
  1  using PlanarDev.Stubs
  2  using Test
  3  using .Planar.Engine.Simulations.Random
  4  using PlanarDev.Planar.Engine.Lang: @m_str
  5  
  6  openval(s, a) = s.universe[a].ohlcv.open[begin]
  7  closeval(s, a) = s.universe[a].ohlcv.close[end]
  8  test_synth(s) = begin
  9      @test openval(s, m"sol") == 101.0
 10      @test closeval(s, m"sol") == 1753.0
 11      @test openval(s, m"eth") == 99.0
 12      @test closeval(s, m"eth") == 574.0
 13      @test openval(s, m"btc") == 97.0
 14      @test closeval(s, m"btc") == 123.0
 15  end
 16  
 17  _ai_trades(s) = s[m"eth"].history
 18  eq1(a, b) = isapprox(a, b; atol=1e-1)
 19  test_nomargin_market(s) = begin
 20      @test egn.marginmode(s) isa egn.NoMargin
 21      s.attrs[:overrides] = (; ordertype=:market)
 22      egn.start!(s)
 23      @test first(_ai_trades(s)).order isa egn.MarketOrder
 24      @info "TEST: " s.cash.value
 25      @test eq1(Cash(:USDT, 9.39228334), s.cash.value)
 26      @test eq1(Cash(:USDT, 0.0), s.cash_committed)
 27      @test st.trades_count(s) == 4657
 28      mmh = st.minmax_holdings(s)
 29      @test mmh.count == 0
 30      @test mmh.min[1] == :USDT
 31      @test mmh.min[2] ≈ Inf
 32      @test mmh.max[1] == :USDT
 33      @test mmh.max[2] ≈ 0.0 atol = 1e-4
 34  end
 35  
 36  test_nomargin_gtc(s) = begin
 37      @test marginmode(s) isa egn.NoMargin
 38      s.attrs[:overrides] = (; ordertype=:gtc)
 39      egn.start!(s)
 40      @test first(_ai_trades(s)).order isa egn.GTCOrder
 41      @info "TEST: " s.cash.value
 42      @test eq1(Cash(:USDT, 7615.8), s.cash.value)
 43      @test eq1(Cash(:USDT, 0.0), s.cash_committed)
 44      @test st.trades_count(s) == 10105
 45      mmh = st.minmax_holdings(s)
 46      @test mmh.count == 0
 47      @test mmh.min[1] == :USDT
 48      @test mmh.min[2] ≈ Inf atol = 1e3
 49      @test mmh.max[1] == :USDT
 50      @test mmh.max[2] ≈ 0 atol = 1e3
 51  end
 52  
 53  test_nomargin_ioc(s) = begin
 54      @test marginmode(s) isa egn.NoMargin
 55      s.attrs[:overrides] = (; ordertype=:ioc)
 56      egn.start!(s)
 57      @test first(_ai_trades(s)).order isa egn.IOCOrder
 58      @info "TEST: " s.cash.value
 59      @test Cash(:USDT, 694.909e3) ≈ s.cash atol = 1
 60      @info "TEST: " s.cash_committed.value
 61      @test Cash(:USDT, -0.4e-7) ≈ s.cash_committed atol = 1e-6
 62      @test st.trades_count(s) == 10244
 63      mmh = st.minmax_holdings(s)
 64      @test mmh.count == 0
 65      @test mmh.min[1] == :USDT
 66      @test mmh.min[2] ≈ Inf atol = 1e-1
 67      @test mmh.max[1] == :USDT
 68      @test mmh.max[2] ≈ 0.0 atol = 1e-1
 69  end
 70  
 71  test_nomargin_fok(s) = begin
 72      @test marginmode(s) isa egn.NoMargin
 73      s.attrs[:overrides] = (; ordertype=:fok)
 74      # increase cash to trigger order kills
 75      s.config.initial_cash = 1e6
 76      s.config.min_size = 1e3
 77      egn.start!(s)
 78      @test first(_ai_trades(s)).order isa egn.FOKOrder
 79      @test Cash(:USDT, 999.547) ≈ s.cash atol = 1e-1
 80      @test Cash(:USDT, 0.0) ≈ s.cash_committed atol = 1e-7
 81      @test st.trades_count(s) == 2051
 82      mmh = st.minmax_holdings(s)
 83      reset!(s, true)
 84      @test mmh.count == 1
 85      @test mmh.min[1] == :BTC
 86      @test mmh.min[2] ≈ 4.068469375875e6 atol = 1e2
 87      @test mmh.max[1] == :BTC
 88      @test mmh.max[2] ≈ 4.068469375875e6 atol = 1e2
 89  end
 90  
 91  function margin_overrides(ot=:market)
 92      (;
 93          ordertype=ot,
 94          def_lev=10.0,
 95          longdiff=1.02,
 96          buydiff=1.01,
 97          selldiff=1.012,
 98          long_k=0.02,
 99          short_k=0.02,
100          per_order_leverage=false,
101          verbose=false,
102      )
103  end
104  
105  test_margin_market(s) = begin
106      s[:per_order_leverage] = false
107      @test marginmode(s) isa egn.Isolated
108      s.attrs[:overrides] = margin_overrides(:market)
109      egn.start!(s)
110      @test first(_ai_trades(s)).order isa ect.AnyMarketOrder
111      @test Cash(:USDT, -0.056) ≈ s.cash atol = 1e-3
112      @test Cash(:USDT, 0.0) ≈ s.cash_committed atol = 1e-1
113      @test ect.tradescount(s) == st.trades_count(s) == 480
114      mmh = st.minmax_holdings(s)
115      @test mmh.count == 0
116      @test mmh.min[1] == :USDT
117      @test mmh.min[2] ≈ Inf atol = 1e-3
118      @test mmh.max[1] == :USDT
119      @test mmh.max[2] ≈ 0.0 atol = 1e-3
120  end
121  
122  test_margin_gtc(s) = begin
123      @test marginmode(s) isa egn.Isolated
124      s.attrs[:overrides] = margin_overrides(:gtc)
125      egn.start!(s)
126      @test first(_ai_trades(s)).order isa ect.AnyGTCOrder
127      @test Cash(:USDT, -0.105) ≈ s.cash atol = 1e-3
128      @test Cash(:USDT, 0.0) ≈ s.cash_committed atol = 1e-1
129      @test st.trades_count(s) == 541
130      mmh = st.minmax_holdings(s)
131      reset!(s, true)
132      @test mmh.count == 0
133      @test mmh.min[1] == :USDT
134      @test mmh.min[2] ≈ Inf atol = 1e-3
135      @test mmh.max[1] == :USDT
136      @test mmh.max[2] ≈ 0.0 atol = 1e-3
137  end
138  
139  test_margin_fok(s) = begin
140      @test marginmode(s) isa egn.Isolated
141      s.attrs[:overrides] = margin_overrides(:fok)
142      # increase cash to trigger order kills
143      s.config.initial_cash = 1e6
144      s.config.min_size = 1e3
145      egn.start!(s)
146      @test first(_ai_trades(s)).order isa ect.AnyFOKOrder
147      @test Cash(:USDT, -0.036) ≈ s.cash atol = 1e1
148      @test Cash(:USDT, 0.0) ≈ s.cash_committed atol = 1e1
149      @test st.trades_count(s) == 2352
150      mmh = st.minmax_holdings(s)
151      reset!(s, true)
152      @test mmh.count == 0
153      @test mmh.min[1] == :USDT
154      @test mmh.min[2] ≈ Inf atol = 1e-3
155      @test mmh.max[1] == :USDT
156      @test mmh.max[2] ≈ 0.0 atol = 1e-3
157  end
158  
159  test_margin_ioc(s) = begin
160      @test marginmode(s) isa egn.Isolated
161      s.attrs[:overrides] = margin_overrides(:ioc)
162      # increase cash to trigger order kills
163      s.config.initial_cash = 1e6
164      s.config.min_size = 1e3
165      egn.start!(s)
166      @test first(_ai_trades(s)).order isa ect.AnyIOCOrder
167      @test Cash(:USDT, -0.048) ≈ s.cash atol = 1e1
168      @test Cash(:USDT, 0.0) ≈ s.cash_committed atol = 1e-1
169      @test st.trades_count(s) == 2354
170      mmh = st.minmax_holdings(s)
171      reset!(s, true)
172      @test mmh.count == 0
173      @test mmh.min[1] == :USDT
174      @test mmh.min[2] ≈ Inf atol = 1e-3
175      @test mmh.max[1] == :USDT
176      @test mmh.max[2] ≈ 0.0 atol = 1e-3
177  end
178  
179  _nomargin_backtest_tests(s) = begin
180      @testset test_synth(s)
181      @testset test_nomargin_market(s)
182      @testset test_nomargin_gtc(s)
183      @testset test_nomargin_ioc(s)
184      @testset test_nomargin_fok(s)
185  end
186  
187  _margin_backtest_tests(s) = begin
188      @testset test_margin_market(s)
189      @testset test_margin_gtc(s)
190      @testset test_margin_ioc(s)
191      @testset test_margin_fok(s)
192  end
193  
194  test_backtest() = begin
195      @eval begin
196          using PlanarDev.Planar.Engine: Engine as egn
197          using .egn.Instruments: Cash
198          Planar.@environment!
199          using .Planar.Engine.Strategies: reset!
200          if isnothing(Base.find_package("BlackBoxOptim")) && @__MODULE__() == Main
201              import Pkg
202              Pkg.add("BlackBoxOptim")
203          end
204      end
205      # NOTE: Don't override exchange of these tests, since they rely on
206      # specific assets precision/limits
207      @testset failfast = FAILFAST "backtest" begin
208          s = backtest_strat(:Example)
209          @info "TEST: Example strat" exc = nameof(exchange(s))
210          invokelatest(_nomargin_backtest_tests, s)
211  
212          s = backtest_strat(:ExampleMargin)
213          @info "TEST: ExampleMargin strat" exc = nameof(exchange(s))
214          invokelatest(_margin_backtest_tests, s)
215      end
216  end