/ Executors / src / orders / state.jl
state.jl
  1  using .Lang: @deassert, @lget!, Option, @ifdebug
  2  using .OrderTypes: ExchangeID
  3  import .OrderTypes: commit!, positionside, LiquidationType, ReduceOnlyOrder, trades
  4  using Strategies: Strategies as st, NoMarginStrategy, MarginStrategy, IsolatedStrategy
  5  using .Instances: notional, pnl, Instances
  6  import .Instances: committed
  7  using .Misc: Short, DFT, toprecision
  8  using .Instruments
  9  using .Instruments: @importcash!, AbstractAsset
 10  import .Checks: cost
 11  @importcash!
 12  import Base: fill!
 13  import .Misc: reset!, attr
 14  
 15  ##  committed::DFT # committed is `cost + fees` for buying or `amount` for selling
 16  const _BasicOrderState{T} = NamedTuple{
 17      (:take, :stop, :committed, :unfilled, :trades),
 18      Tuple{Option{T},Option{T},Ref{T},Ref{T},Vector{Trade}},
 19  }
 20  
 21  @doc """Constructs a basic order state with given parameters.
 22  
 23  $(TYPEDSIGNATURES)
 24  
 25  """
 26  function basic_order_state(
 27      # FIXME: should `trades` be a `SortedArray`?
 28      take,
 29      stop,
 30      committed::Ref{T},
 31      unfilled::Ref{T},
 32      trades=Trade[],
 33  ) where {T<:Real}
 34      _BasicOrderState{T}((take, stop, committed, unfilled, trades))
 35  end
 36  
 37  @doc """Constructs an `Order` for a given `OrderType` `type` and inputs.
 38  
 39  $(TYPEDSIGNATURES)
 40  
 41  """
 42  function basicorder(
 43      ai::AssetInstance,
 44      price,
 45      amount,
 46      committed,
 47      ::SanitizeOff;
 48      type::Type{<:Order},
 49      date,
 50      loss=nothing,
 51      profit=nothing,
 52      id="",
 53      tag="",
 54  )
 55      if !ismonotonic(loss, price, profit)
 56          @debug "basic order: prices not monotonic" ai = raw(ai) loss price profit type
 57          return nothing
 58      end
 59      # don't check cost for market orders since they can go lower
 60      ignore_cost = isnocost(type)
 61      # Allow reduce only orders below minimum cost
 62      if !ignore_cost && !iscost(ai, amount, loss, price, profit)
 63          @debug "basic order: invalid cost" ai = raw(ai) amount loss price profit type
 64          return nothing
 65      end
 66      if !ignore_cost
 67          @deassert if type <: IncreaseOrder
 68              committed[] * leverage(ai, positionside(type)) >= ai.limits.cost.min
 69          else
 70              abs(committed[]) >= ai.limits.amount.min || ignore_cost
 71          end "Order committment too low\n$(committed[]), $(ai.asset) $date"
 72      end
 73      unfilled = Ref(unfillment(type, amount))
 74      @deassert type <: AnyBuyOrder ? unfilled[] < 0.0 : unfilled[] > 0.0
 75      OrderTypes.Order(
 76          ai,
 77          type;
 78          date,
 79          price,
 80          amount,
 81          id,
 82          tag,
 83          attrs=basic_order_state(profit, loss, committed, unfilled),
 84      )
 85  end
 86  
 87  @doc """Removes a single order from the order queue.
 88  
 89  $(TYPEDSIGNATURES)
 90  
 91  """
 92  function Base.delete!(s::Strategy, ai, o::IncreaseOrder)
 93      @deassert committed(o) |> approxzero o
 94      delete!(orders(s, ai, orderside(o)), pricetime(o))
 95      @deassert pricetime(o) ∉ keys(orders(s, ai, orderside(o)))
 96      # If we don't have cash for this asset, it should be released from holdings
 97      release!(s, ai)
 98  end
 99  
100  @doc """Removes a single sell order from the order queue.
101  
102  $(TYPEDSIGNATURES)
103  
104  """
105  function Base.delete!(s::Strategy, ai, o::SellOrder)
106      @deassert committed(o) |> approxzero o
107      delete!(orders(s, ai, orderside(o)), pricetime(o))
108      # If we don't have cash for this asset, it should be released from holdings
109      release!(s, ai)
110  end
111  
112  @doc """Removes a single short buy order from the order queue.
113  
114  $(TYPEDSIGNATURES)
115  
116  """
117  function Base.delete!(s::Strategy, ai, o::ShortBuyOrder)
118      # Short buy orders have negative committment
119      @deassert committed(o) |> approxzero o
120      delete!(orders(s, ai, Buy), pricetime(o))
121      # If we don't have cash for this asset, it should be released from holdings
122      release!(s, ai)
123  end
124  
125  @doc """Removes all buy/sell orders for an asset instance.
126  
127  $(TYPEDSIGNATURES)
128  
129  """
130  function Base.delete!(s::Strategy, ai, t::Type{<:Union{Buy,Sell}})
131      delete!.(s, ai, values(orders(s, ai, t)))
132  end
133  
134  @doc """Removes all buy and sell orders for an asset instance.
135  
136  $(TYPEDSIGNATURES)
137  
138  """
139  Base.delete!(s::Strategy, ai, ::Type{BuyOrSell}) = begin
140      delete!(s, ai, Buy)
141      delete!(s, ai, Sell)
142  end
143  
144  @doc """Removes all orders for an asset instance.
145  
146  $(TYPEDSIGNATURES)
147  
148  """
149  Base.delete!(s::Strategy, ai) = delete!(s, ai, BuyOrSell)
150  
151  @doc """Inserts an order into the order dict of the asset instance. Orders should be identifiable by a unique (price, date) tuple.
152  
153  $(TYPEDSIGNATURES)
154  
155  """
156  function Base.push!(s::Strategy, ai, o::Order{<:OrderType{S}}) where {S<:OrderSide}
157      k = pricetime(o)
158      d = orders(s, ai, S)
159      # stok = searchsortedfirst(d, k)
160      @ifdebug if k ∈ keys(d)
161          @debug "Duplicate order key" o.id d[k].id o.price o.date
162      end
163      @assert k ∉ keys(d)
164      d[k] = o
165  end
166  @doc """Checks if an order is already added to the queue.
167  
168  $(TYPEDSIGNATURES)
169  
170  """
171  function isqueued(o::Order{<:OrderType{S}}, s::Strategy, ai) where {S<:OrderSide}
172      let k = pricetime(o), d = orders(s, ai, S)
173          k in keys(d)
174      end
175  end
176  
177  @doc """Checks order committment to be within expected values.
178  
179  $(TYPEDSIGNATURES)
180  
181  """
182  function _check_committment(o)
183      @deassert attr(o, :committed)[] |> gtxzero ||
184          ordertype(o) <: MarketOrderType ||
185          o isa IncreaseLimitOrder o
186  end
187  
188  @doc """Checks if the unfilled amount for a limit sell order is positive.
189  
190  $(TYPEDSIGNATURES)
191  
192  """
193  _check_unfillment(o::AnyLimitOrder{Sell}) = attr(o, :unfilled)[] > 0.0
194  
195  @doc """Checks if the unfilled amount for a limit buy order is negative.
196  
197  $(TYPEDSIGNATURES)
198  
199  """
200  _check_unfillment(o::AnyLimitOrder{Buy}) = attr(o, :unfilled)[] < 0.0
201  
202  @doc """Checks if the unfilled amount for a market buy order is negative.
203  
204  $(TYPEDSIGNATURES)
205  
206  """
207  _check_unfillment(o::AnyMarketOrder{Buy}) = attr(o, :unfilled)[] < 0.0
208  
209  @doc """Checks if the unfilled amount for a market sell order is positive.
210  
211  $(TYPEDSIGNATURES)
212  
213  """
214  _check_unfillment(o::AnyMarketOrder{Sell}) = attr(o, :unfilled)[] > 0.0
215  
216  @doc """Checks if the unfilled amount for a long order is positive.
217  
218  $(TYPEDSIGNATURES)
219  
220  """
221  _check_unfillment(o::LongOrder) = attr(o, :unfilled)[] > 0.0
222  
223  @doc """Checks if the unfilled amount for a short order is negative.
224  
225  $(TYPEDSIGNATURES)
226  
227  """
228  _check_unfillment(o::ShortOrder) = attr(o, :unfilled)[] < 0.0
229  @doc """Fills a buy order for a no-margin strategy.
230  
231  $(TYPEDSIGNATURES)
232  
233  """
234  function fill!(
235      ::Strategy{<:Union{Sim,Paper}}, ai::NoMarginInstance, o::BuyOrder, t::BuyTrade
236  )
237      @deassert o isa IncreaseOrder && _check_unfillment(o) unfilled(o), typeof(o)
238      @deassert committed(o) == o.attrs.committed[] && committed(o) >= 0.0
239      # from neg to 0 (buy amount is pos)
240      attr(o, :unfilled)[] += t.amount
241      @deassert attr(o, :unfilled)[] |> ltxzero (o, t.amount)
242      # from pos to 0 (buy size is neg)
243      attr(o, :committed)[] -= committment(ai, t)
244      @deassert gtxzero(ai, committed(o), Val(:price)) || o isa MarketOrder o,
245      committment(ai, t)
246  end
247  
248  @doc """Fills a sell order.
249  
250  $(TYPEDSIGNATURES)
251  
252  """
253  function fill!(
254      ::Strategy{<:Union{Sim,Paper}}, ai::AssetInstance, o::SellOrder, t::SellTrade
255  )
256      @deassert o isa SellOrder && _check_unfillment(o)
257      @deassert committed(o) == o.attrs.committed[] && committed(o) |> gtxzero
258      # from pos to 0 (sell amount is neg)
259      attr(o, :unfilled)[] += t.amount
260      @deassert attr(o, :unfilled)[] |> gtxzero
261      # from pos to 0 (sell amount is neg)
262      attr(o, :committed)[] += t.amount
263      @deassert committed(o) |> gtxzero
264  end
265  
266  @doc """Fills a short buy order.
267  
268  $(TYPEDSIGNATURES)
269  
270  """
271  function fill!(
272      ::Strategy{<:Union{Sim,Paper}}, ai::AssetInstance, o::ShortBuyOrder, t::ShortBuyTrade
273  )
274      @deassert o isa ShortBuyOrder && _check_unfillment(o) o
275      @deassert committed(o) == o.attrs.committed[] && committed(o) |> ltxzero
276      @deassert attr(o, :unfilled)[] < 0.0
277      attr(o, :unfilled)[] += t.amount # from neg to 0 (buy amount is pos)
278      @deassert attr(o, :unfilled)[] |> ltxzero
279      # NOTE: committment is always positive except for short buy orders
280      # where that's committed is shorted (negative) asset cash
281      @deassert t.amount > 0.0 && committed(o) < 0.0
282      attr(o, :committed)[] += t.amount # from neg to 0 (buy amount is pos)
283      @deassert committed(o) |> ltxzero
284  end
285  @doc """Fills an increase order for a margin strategy.
286  
287  $(TYPEDSIGNATURES)
288  
289  """
290  function fill!(
291      ::MarginStrategy{<:Union{Sim,Paper}},
292      ai::MarginInstance,
293      o::IncreaseOrder,
294      t::IncreaseTrade,
295  )
296      @deassert o isa IncreaseOrder && _check_unfillment(o) o
297      @deassert committed(o) == o.attrs.committed[] && committed(o) > 0.0 t
298      attr(o, :unfilled)[] += t.amount
299      @deassert attr(o, :unfilled)[] |> ltxzero || o isa ShortSellOrder
300      @deassert t.value > 0.0
301      attr(o, :committed)[] -= committment(ai, t)
302      # Market order spending can exceed the estimated committment
303      # ShortSell limit orders can spend more than committed because of slippage
304      @deassert committed(o) |> gtxzero || o isa AnyMarketOrder || o isa IncreaseLimitOrder
305  end
306  
307  @doc """Checks if an order is open.
308  
309  $(TYPEDSIGNATURES)
310  
311  """
312  Base.isopen(ai::AssetInstance, o::Order) = !isfilled(ai, o)
313  
314  @doc """Checks if the order amount left to fill is below minimum qty.
315  
316  $(TYPEDSIGNATURES)
317  
318  """
319  Base.iszero(ai::AssetInstance, o::Order) = iszero(ai, unfilled(o))
320  
321  @doc """Checks if the order committed value is below minimum quantity.
322  
323  $(TYPEDSIGNATURES)
324  
325  """
326  function Instances.isdust(ai::AssetInstance, o::Order)
327      unf = abs(unfilled(o))
328      unf < ai.limits.amount.min ||
329          unf * o.price < ai.limits.cost.min ||
330          unf < ai.limits.amount.min * ai.fees.min
331  end
332  
333  function Instances.isdust(ai::AssetInstance, o::ReduceOnlyOrder)
334      false
335  end
336  
337  @doc """Checks if an order is filled.
338  
339  $(TYPEDSIGNATURES)
340  
341  """
342  isfilled(ai::AssetInstance, o::Order) =
343      isdust(ai, o) || begin
344          ot = trades(o)
345          if length(ot) > 0
346              abs(sum(t.amount for t in ot)) >= abs(o.amount)
347          else
348              false
349          end
350      end
351  
352  @doc """Updates the strategy's cash after a buy trade.
353  
354  $(TYPEDSIGNATURES)
355  
356  """
357  function strategycash!(s::NoMarginStrategy, ai, t::BuyTrade)
358      @deassert t.size < 0.0
359      add!(s.cash, t.size)
360      sub!(s.cash_committed, committment(ai, t))
361      @deassert gtxzero(ai, s.cash_committed, Val(:price))
362  end
363  
364  @doc """Updates the strategy's cash after a sell trade.
365  
366  $(TYPEDSIGNATURES)
367  
368  """
369  function strategycash!(s::NoMarginStrategy, _, t::SellTrade)
370      @deassert t.size > 0.0
371      add!(s.cash, t.size)
372      @deassert s.cash |> gtxzero
373  end
374  
375  @doc """Updates the strategy's cash after an increase trade.
376  
377  $(TYPEDSIGNATURES)
378  
379  """
380  function strategycash!(s::IsolatedStrategy, ai, t::IncreaseTrade)
381      @deassert t.size < 0.0
382      # t.amount can be negative for short sells
383      margin = t.value / t.leverage
384      # subtract realized fees, and added margin
385      @deassert t.fees > 0.0 || maxfees(ai) < 0.0
386      spent = t.fees + margin
387      @deassert spent > 0.0
388      sub!(s.cash, spent)
389      @ifdebug if committment(ai, t) > committed(s)
390          @error "cash: trade committment can't be higher that total comm" trade = committment(
391              ai, t
392          ) total = committed(s) t
393      end
394      subzero!(s.cash_committed, committment(ai, t); atol=ai.limits.cost.min, dothrow=false)
395      @deassert s.cash_committed |> gtxzero s.cash, s.cash_committed.value, orderscount(s)
396  end
397  
398  function _showliq(s, unrealized_pnl, gained, po, t)
399      get(s.attrs, :verbose, false) || return nothing
400      if ordertype(t) <: LiquidationType
401          @show positionside(t) s.cash margin(po) t.fees t.leverage t.size price(po) t.order.price t.price liqprice(
402              po
403          ) unrealized_pnl gained ""
404      end
405  end
406  
407  _checktrade(t::SellTrade) = @deassert t.amount < 0.0
408  _checktrade(t::ShortBuyTrade) = @deassert t.amount > 0.0
409  
410  @doc """Updates the strategy's cash after a reduce trade.
411  
412  $(TYPEDSIGNATURES)
413  
414  """
415  function strategycash!(s::IsolatedStrategy, ai, t::ReduceTrade)
416      @deassert t.size > 0.0
417      @deassert abs(cash(ai, positionside(t)())) >= abs(t.amount) (
418          cash(ai), t.amount, t.order
419      )
420      @ifdebug _checktrade(t)
421      po = position(ai, positionside(t))
422      # The notional tracks current value, but the margin
423      # refers to the notional from the (avg) entry price
424      # of the position
425      margin = abs(t.entryprice * t.amount) / t.leverage
426      unrealized_pnl = pnl(po, t.price, t.amount)
427      @deassert t.fees > 0.0 || maxfees(ai) < 0.0
428      gained = margin + unrealized_pnl - t.fees # minus fees
429      @ifdebug _showliq(s, unrealized_pnl, gained, po, t)
430      @debug "strategycash reduce trade:" gained t.value margin unrealized_pnl t.fees po.entryprice[] cash(
431          po
432      ) t.price t.leverage t.amount posside(t) orderside(t)
433      add!(s.cash, gained)
434      @deassert s.cash |> gtxzero || (hasorders(s) || hascash(s)) (;
435          s.cash, s.cash_committed, t.price, t.amount, unrealized_pnl, t.fees, margin
436      )
437  end
438  
439  @doc """Updates the strategy's and asset instance's cash after a trade.
440  
441  $(TYPEDSIGNATURES)
442  
443  """
444  function cash!(s::Strategy, ai, t::Trade)
445      @ifdebug _check_trade(t, ai)
446      strategycash!(s, ai, t)
447      cash!(ai, t)
448      @ifdebug _check_cash(ai, positionside(t)())
449  end
450  
451  @doc """Returns the attribute of an order.
452  
453  $(TYPEDSIGNATURES)
454  
455  """
456  attr(o::Order, sym) = getfield(getfield(o, :attrs), sym)
457  
458  @doc """Returns the absolute value of the unfilled amount of an order.
459  
460  $(TYPEDSIGNATURES)
461  
462  """
463  unfilled(o::Order) = abs(attr(o, :unfilled)[])
464  
465  @doc """Returns the filled amount of an order.
466  
467  $(TYPEDSIGNATURES)
468  
469  """
470  filled_amount(o) = abs(o.amount) - unfilled(o)
471  @doc """Commits an increase order to a strategy.
472  
473  $(TYPEDSIGNATURES)
474  
475  """
476  function commit!(s::Strategy, o::IncreaseOrder, _)
477      @deassert committed(o) |> gtxzero
478      add!(s.cash_committed, committed(o))
479      @debug "order commit" s.cash_committed.value committed(o)
480  end
481  
482  @doc """Commits a reduce order to an asset instance.
483  
484  $(TYPEDSIGNATURES)
485  
486  """
487  function commit!(::Strategy, o::ReduceOrder, ai)
488      @deassert committed(o) |> ltxzero || positionside(o) == Long
489      add!(committed(ai, positionside(o)()), committed(o))
490  end
491  
492  @doc """Decommits an increase order from a strategy.
493  
494  $(TYPEDSIGNATURES)
495  
496  """
497  function decommit!(s::Strategy, o::IncreaseOrder, ai, canceled=false)
498      @ifdebug _check_committment(o)
499      # NOTE: ignore negative values caused by slippage
500      @deassert canceled || isdust(ai, o) o
501      # NOTE: committed can be negative in case the predicted commit is below the executed size
502      subzero!(s.cash_committed, abs(committed(o)))
503      @deassert gtxzero(ai, s.cash_committed, Val(:price)) s.cash_committed.value,
504      s.cash.precision,
505      o
506      attr(o, :committed)[] = 0.0
507  end
508  
509  # FIXME: order committment for reduce orders could also be negative IF the exchange
510  # does fee exclusive trading (rare, never seen) and the fee is paid in base currency (so and so).
511  # for long sells fee is usually paid in quote cur, for short buys it is possible
512  # it could be paid in base cur.
513  # Should we check the sign of `committed(o)` for long sells and short buys?
514  
515  @doc """Decommits a sell order from an asset instance.
516  
517  $(TYPEDSIGNATURES)
518  
519  """
520  function decommit!(s::Strategy, o::SellOrder, ai, args...)
521      # NOTE: ignore negative values caused by slippage
522      sub!(committed(ai, Long()), max(0.0, committed(o)))
523      @deassert gtxzero(ai, committed(ai, Long()), Val(:amount))
524      attr(o, :committed)[] = 0.0
525  end
526  
527  @doc """Decommits a short buy order from an asset instance.
528  
529  $(TYPEDSIGNATURES)
530  
531  """
532  function decommit!(s::Strategy, o::ShortBuyOrder, ai, args...)
533      @deassert committed(o) |> ltxzero
534      sub!(committed(ai, Short()), committed(o))
535      attr(o, :committed)[] = 0.0
536  end
537  @doc """Checks if an increase order can be committed to a strategy.
538  
539  $(TYPEDSIGNATURES)
540  
541  """
542  function iscommittable(s::Strategy, o::IncreaseOrder, ai)
543      @deassert committed(o) > 0.0
544      c = st.freecash(s)
545      comm = committed(o)
546      c >= comm || isapprox(c, comm)
547  end
548  
549  @doc """Checks if a sell order can be committed to an asset instance.
550  
551  $(TYPEDSIGNATURES)
552  
553  """
554  function iscommittable(::Strategy, o::SellOrder, ai)
555      @deassert committed(o) > 0.0
556      c = Instances.freecash(ai, Long())
557      comm = committed(o)
558      c >= comm || isapprox(c, comm)
559  end
560  
561  @doc """Checks if a short buy order can be committed to an asset instance.
562  
563  $(TYPEDSIGNATURES)
564  
565  """
566  function iscommittable(::Strategy, o::ShortBuyOrder, ai)
567      @deassert committed(o) < 0.0
568      c = Instances.freecash(ai, Short())
569      comm = committed(o)
570      c <= comm || isapprox(c, comm)
571  end
572  
573  @doc """When an increase order is added to a strategy, the asset is added to the holdings.
574  
575  $(TYPEDSIGNATURES)
576  
577  """
578  function hold!(s::Strategy, ai, o::IncreaseOrder)
579      @deassert hasorders(s, ai, orderside(o)) || !iszero(ai) o
580      push!(s.holdings, ai)
581  end
582  
583  @doc """Reduce orders can never switch an asset from not held to held.
584  
585  $(TYPEDSIGNATURES)
586  
587  """
588  hold!(::Strategy, _, ::ReduceOrder) = nothing
589  
590  @doc """An asset is released when there are no orders for it and its balance is zero.
591  
592  $(TYPEDSIGNATURES)
593  
594  """
595  function release!(s::Strategy, ai)
596      if iszero(ai) && !hasorders(s, ai)
597          delete!(s.holdings, ai)
598      end
599  end
600  
601  @doc """Cancels an order with given error.
602  
603  $(TYPEDSIGNATURES)
604  
605  """
606  function cancel!(s::Strategy, o::Order, ai; err::OrderError)::Bool
607      @debug "order cancel" o.id ai = raw(ai) err s.cash_committed.value committed(o)
608      if isqueued(o, s, ai)
609          decommit!(s, o, ai, true)
610          @debug "order cancel" s.cash_committed.value
611          delete!(s, ai, o)
612          st.call!(s, o, err, ai)
613      end
614      true
615  end
616  
617  
618  @doc """Returns the amount of an order.
619  
620  $(TYPEDSIGNATURES)
621  
622  """
623  amount(o::Order) = getfield(o, :amount)
624  
625  @doc """Returns the trades of an order.
626  
627  $(TYPEDSIGNATURES)
628  
629  """
630  trades(o::Order) = attr(o, :trades)
631  
632  @doc """Returns the committed amount of a short buy order.
633  
634  $(TYPEDSIGNATURES)
635  
636  """
637  function committed(o::ShortBuyOrder{<:AbstractAsset,<:ExchangeID})
638      @deassert attr(o, :committed)[] |> ltxzero o
639      attr(o, :committed)[]
640  end
641  
642  @doc """Returns the committed amount of an order.
643  
644  $(TYPEDSIGNATURES)
645  
646  """
647  function committed(o::Order)
648      @ifdebug _check_committment(o)
649      attr(o, :committed)[]
650  end
651  
652  @doc """Returns the cost of an order.
653  
654  $(TYPEDSIGNATURES)
655  
656  """
657  cost(o::Order) = o.price * abs(o.amount)
658  
659  @doc """Resets an order committment and unfilled amount.
660  
661  $(TYPEDSIGNATURES)
662  
663  """
664  function reset!(o::Order, ai)
665      empty!(trades(o))
666      attr(o, :committed)[] = committment(ai, o)
667      attr(o, :unfilled)[] = unfillment(o)
668  end
669  
670  queue!(s::Strategy, o::Order, ai; skipcommit=false) = nothing
671  
672  function _increase_order_comm(s::Strategy, ai::AssetInstance, oside::BySide)
673      all_comm = sum((committed(o) for o in values(s, ai, oside)); init=0.0)
674      s_comm = committed(s)
675      all_comm, s_comm
676  end
677  
678  function _check_committment(s::Strategy, ai::AssetInstance, ::BySide{Buy}, ::ByPos{Long})
679      o_comm, ai_comm = _increase_order_comm(s, ai, Buy)
680      abs(ai_comm) >= abs(o_comm)
681  end
682  
683  function _check_committment(s::Strategy, ai::AssetInstance, ::BySide{Sell}, ::ByPos{Short})
684      o_comm, s_comm = _increase_order_comm(s, ai, Sell)
685      abs(s_comm) >= abs(o_comm)
686  end
687  
688  function _reduce_order_comm(
689      s::Strategy, ai::AssetInstance, oside::BySide, pside::ByPos=posside(ai)
690  )
691      o_comm = sum((committed(o) for o in values(s, ai, oside) if ispos(pside, o)); init=0.0)
692      s_comm = committed(ai, pside)
693      o_comm, s_comm
694  end
695  
696  function _check_committment(s::Strategy, ai::AssetInstance, ::BySide{Sell}, ::ByPos{Long})
697      o_comm, ai_comm = _reduce_order_comm(s, ai, Sell, Long)
698      isapprox(ai, abs(o_comm), abs(ai_comm), Val(:amount))
699  end
700  
701  function _check_committment(s::Strategy, ai::AssetInstance, ::BySide{Buy}, ::ByPos{Short})
702      o_comm, ai_comm = _reduce_order_comm(s, ai, Buy, Short)
703      isapprox(ai, abs(o_comm), abs(ai_comm), Val(:amount))
704  end