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