/ SimMode / src / slippage.jl
slippage.jl
  1  using .Lang: @ifdebug
  2  using .Strategies: MarginStrategy
  3  using Executors: AnyBuyOrder, AnyMarketOrder, AnyLimitOrder
  4  using .Misc: toprecision
  5  import Executors: with_slippage
  6  
  7  @doc "The default slippage for the strategy."
  8  spreadopt(::Val{:spread}, date, ai) = sml.spreadat(ai, date, Val(:opcl))
  9  @doc "A raw float value (percentage) as slippage."
 10  spreadopt(n::T, args...) where {T<:Real} = n
 11  spreadopt(v, args...) = error("`base_slippage` option value not supported ($v)")
 12  
 13  @doc """ Calculate the base slippage for a given strategy, date, and asset.
 14  
 15  $(TYPEDSIGNATURES)
 16  
 17  This function uses the `spreadopt` function to calculate the base slippage. 
 18  The slippage is determined based on the strategy's attributes, the date, and the asset. 
 19  It is used in the context of trading simulations to model the cost of executing a trade.
 20  
 21  """
 22  _base_slippage
 23  function _base_slippage(s::Strategy, date::DateTime, ai)
 24      spreadopt(s.attrs[:sim_base_slippage], date, ai)
 25  end
 26  
 27  @doc """ Returns a skew factor based on the actual amount and volume.
 28  
 29  $(TYPEDSIGNATURES)
 30  
 31  This function calculates a skew factor based on the `actual_amount` and `volume`.
 32  If `volume` is 0.0, it returns 1.0.
 33  Otherwise, it returns the minimum between 1.0 and the ratio of `actual_amount` to `volume`.
 34  
 35  """
 36  _volumeskew(actual_amount, volume) =
 37      if volume == 0.0
 38          1.0
 39      else
 40          min(1.0, actual_amount / volume)
 41      end
 42  @doc """ Calculates the price skew based on low and high values at a particular date.
 43  
 44  $(TYPEDSIGNATURES)
 45  
 46  This function finds the skewness of price for a particular asset `ai` at a given `date`.
 47  The skewness is calculated as `1.0 - lowat(ai, date) / highat(ai, date)`.
 48  The function `lowat` and `highat` are used to get the low and high prices respectively.
 49  
 50  """
 51  _priceskew(ai, date) = 1.0 - lowat(ai, date) / highat(ai, date)
 52  
 53  @doc "Slippage makes price go down for buy orders."
 54  _addslippage(::AnyLimitOrder{Buy}, price, slp) = price - slp
 55  @doc "Slippage makes price go up for sell orders."
 56  _addslippage(::AnyLimitOrder{Sell}, price, slp) = price + slp
 57  @doc "Buy orders slippage is favorable when the close price is lower than the open price."
 58  _isfavorable(::AnyLimitOrder{Buy}, ai, date) = closeat(ai, date) < openat(ai, date)
 59  @doc "Sell orders slippage is favorable when the close price is higher than the open price."
 60  _isfavorable(::AnyLimitOrder{Sell}, ai, date) = closeat(ai, date) > openat(ai, date)
 61  
 62  @doc """ Apply slippage to limit orders based on various factors.
 63  
 64  $(TYPEDSIGNATURES)
 65  
 66  This function applies slippage to limit orders based on the date of creation, the favorability of the order, price volatility, and volume. 
 67  The slippage is skewed by the difference between price skew and volume skew. 
 68  If the skew rate is negative or zero, there is no slippage. 
 69  Otherwise, the base slippage is calculated and adjusted by the skew rate. 
 70  The final price is clamped within the high and low prices of the asset for the given date.
 71  
 72  """
 73  function _with_slippage(
 74      s::Strategy{<:Union{Paper,Sim}},
 75      o::AnyLimitOrder,
 76      ai,
 77      ::Val;
 78      clamp_price,
 79      actual_amount,
 80      date,
 81  )
 82      # slippage on limit orders can only happen on date of creation
 83      date == o.date || return clamp_price
 84      # buy/sell orders can have favorable slippage respectively only on red/green candles
 85      _isfavorable(o, ai, date) || return clamp_price
 86      # slippage is skewed by price volatility
 87      price_skew = _priceskew(ai, date)
 88      # less volume decreases the likelyhood of favorable slippage
 89      volume = volumeat(ai, date)
 90      volume_skew = _volumeskew(actual_amount, volume)
 91      skew_rate = price_skew - volume_skew
 92      # If skew is negative there is no slippage since limit orders
 93      # can't have unfavorable slippage
 94      if skew_rate <= 0.0
 95          clamp_price
 96      else
 97          bs = _base_slippage(s, date, ai)
 98          slp = bs * (1.0 + skew_rate)
 99          @deassert slp >= 0.0
100          slp_price = _addslippage(o, clamp_price, slp)
101          @deassert volume > 0
102          volume > 0.0 ? clamp(slp_price, lowat(ai, date), highat(ai, date)) : slp_price
103      end
104  end
105  
106  @doc """ Apply slippage to market orders based on average price.
107  
108  $(TYPEDSIGNATURES)
109  
110  This function calculates the slippage for market orders based on the average price. 
111  The slippage is calculated as the average of the absolute differences between the open price and the close prices at the previous and next timeframes. 
112  The final price is then adjusted by the calculated slippage.
113  
114  """
115  function _with_slippage(
116      s::Strategy{<:Union{Paper,Sim}}, o::AnyMarketOrder, ai, ::Val{:avg}; date, kwargs...
117  )
118      m = openat(ai, date)
119      diff1 = abs(closeat(ai, date - s.timeframe) - openat(ai, date))
120      diff2 = abs(closeat(ai, date) - openat(ai, date + s.timeframe))
121      slp = (diff1 + diff2) / 2.0
122      _addslippage(o, m, slp)
123  end
124  
125  @doc "Market buy orders price is increased by slippage."
126  _addslippage(::AnyMarketOrder{Buy}, price, slp) = price + slp
127  @doc "Market sell orders price is decreased by slippage."
128  _addslippage(::AnyMarketOrder{Sell}, price, slp) = price - slp
129  @doc """ Apply slippage to market orders based on skew.
130  
131  $(TYPEDSIGNATURES)
132  
133  This function calculates the slippage for market orders based on the skew. 
134  The skew is calculated as the sum of the volume skew and the price skew. 
135  The base slippage is then adjusted by the skew rate. 
136  The final price is clamped within the high and low prices of the asset for the given date, unless the volume skew is very small.
137  
138  """
139  function _with_slippage(
140      s::Strategy{<:Union{Paper,Sim}},
141      o::AnyMarketOrder,
142      ai,
143      ::Val{:skew};
144      clamp_price,
145      actual_amount,
146      date,
147  )
148      @deassert o.price == priceat(s, o, ai, date) ||
149          o isa Union{LiquidationOrder,ReduceOnlyOrder}
150      volume = volumeat(ai, date)
151      volume_skew = _volumeskew(actual_amount, volume)
152      price_skew = _priceskew(ai, date)
153      # neg skew makes the price _increase_ while pos skew makes it decrease
154      skew_rate = volume_skew + price_skew
155      bs = _base_slippage(s, o.date, ai)
156      slp = if skew_rate <= 0.0
157          bs
158      else
159          bs_skew = clamp_price * skew_rate
160          muladd(bs, bs_skew > 10.0 ? log10(bs_skew) : bs_skew / 10.0, bs)
161      end
162      @assert !isnan(slp)
163      @deassert slp >= 0.0
164      slp_price = _addslippage(o, clamp_price, slp)
165      # We only go outside candle high/low boundaries if the candle
166      # has very little volume, otherwise assume that liquidity is deep enough
167      if o isa AnyBuyOrder
168          @assert slp_price >= clamp_price (slp_price, clamp_price)
169      else
170          @assert slp_price <= clamp_price (slp_price, clamp_price)
171      end
172      if volume_skew < 1e-3 && !(o isa LiquidationOrder)
173          clamp(slp_price, lowat(ai, date), highat(ai, date))
174      else
175          slp_price
176      end
177  end
178  
179  @doc """ Clamp the price within the high and low prices of the asset for the given date.
180  
181  $(TYPEDSIGNATURES)
182  
183  This function clamps the price of a limit order within the high and low prices of the asset for the given date.
184  
185  """
186  function _doclamp(::Order{<:LimitOrderType}, price, ai, date)
187      clamp(price, lowat(ai, date), highat(ai, date))
188  end
189  @doc "Market order price is never clamped."
190  _doclamp(::Order{<:MarketOrderType}, price, args...) = price
191  @doc """ Apply slippage to the given price with respect to a specific order, date, and amount.
192  
193  $(TYPEDSIGNATURES)
194  
195  This function first clamps the price within the high and low prices of the asset for the given date. 
196  Then, it applies slippage to the clamped price based on the market slippage attribute of the strategy.
197  
198  """
199  function _do_slippage(s, o, ai; date, price, actual_amount)
200      clamp_price = _doclamp(o, price, ai, date)
201      @deassert clamp_price > 0.0
202      _with_slippage(
203          s, o, ai, s.attrs[:sim_market_slippage]; clamp_price, actual_amount, date
204      )
205  end
206  
207  @doc """ Apply slippage to given `price` with respect to a specific order, date, and amount.
208  
209  $(TYPEDSIGNATURES)
210  
211  This function applies slippage to the given price with respect to a specific order, date, and amount. 
212  It first clamps the price within the high and low prices of the asset for the given date, then applies slippage to the clamped price.
213  
214  """
215  function Executors.with_slippage(s::Strategy{<:Union{Paper,Sim}}, o, ai; date, price, actual_amount)
216      _do_slippage(s, o, ai; date, price, actual_amount)
217  end