/ Executors / src / orders / utils.jl
utils.jl
  1  using .Checks: sanitize_price, sanitize_amount
  2  using .Checks: iscost, ismonotonic, SanitizeOff, cost, withfees
  3  using .Strategies: PriceTime, universe
  4  using .Instances:
  5      MarginInstance, NoMarginInstance, AssetInstance, @rprice, @ramount, amount_with_fees
  6  using .OrderTypes:
  7      IncreaseOrder, ShortBuyOrder, LimitOrderType, MarketOrderType, PostOnlyOrderType
  8  using .OrderTypes: ExchangeID, ByPos, ordertype
  9  using .Instruments: AbstractAsset
 10  using Base: negate, beginsym
 11  using .Lang: @lget!, @deassert, @caller
 12  using .Misc: Long, Short, PositionSide
 13  
 14  @doc """ Type alias for any limit order """
 15  const AnyLimitOrder{S<:OrderSide,P<:PositionSide} = Order{
 16      <:LimitOrderType{S},<:AbstractAsset,<:ExchangeID,P
 17  }
 18  
 19  @doc """ Type alias for any GTC order """
 20  const AnyGTCOrder = Union{GTCOrder,ShortGTCOrder}
 21  
 22  @doc """ Type alias for any FOK order """
 23  const AnyFOKOrder = Union{FOKOrder,ShortFOKOrder}
 24  
 25  @doc """ Type alias for any IOC order """
 26  const AnyIOCOrder = Union{IOCOrder,ShortIOCOrder}
 27  
 28  @doc """ Type alias for any market order """
 29  const AnyMarketOrder{S<:OrderSide,P<:PositionSide} = Order{
 30      <:MarketOrderType{S},<:AbstractAsset,<:ExchangeID,P
 31  }
 32  
 33  @doc """ Type alias for any post only order """
 34  const AnyPostOnlyOrder{S<:OrderSide,P<:PositionSide} = Order{
 35      <:PostOnlyOrderType{S},<:AbstractAsset,<:ExchangeID,P
 36  }
 37  
 38  @doc """
 39  Clamps the given values within the correct boundaries.
 40  
 41  $(TYPEDSIGNATURES)
 42  """
 43  function _doclamp(clamper, ai, whats...)
 44      ai = esc(ai)
 45      clamper = esc(clamper)
 46      expr = quote end
 47      for w in whats
 48          w = esc(w)
 49          push!(expr.args, :(isnothing($w) || begin
 50              $w = $clamper($ai, $w)
 51          end))
 52      end
 53      expr
 54  end
 55  
 56  @doc """
 57  Ensures the price is within correct boundaries.
 58  
 59  $(TYPEDSIGNATURES)
 60  """
 61  macro price!(ai, prices...)
 62      _doclamp(:($(@__MODULE__).sanitize_price), ai, prices...)
 63  end
 64  
 65  @doc """
 66  Ensures the amount is within correct boundaries.
 67  
 68  $(TYPEDSIGNATURES)
 69  """
 70  macro amount!(ai, amounts...)
 71      _doclamp(:($(@__MODULE__).sanitize_amount), ai, amounts...)
 72  end
 73  
 74  @doc """
 75  Calculates the commitment for an increase order without margin.
 76  
 77  $(TYPEDSIGNATURES)
 78  """
 79  function committment(
 80      ::Type{<:IncreaseOrder}, ai::NoMarginInstance, price, amount; kwargs...
 81  )
 82      @deassert amount > 0.0
 83      withfees(cost(price, amount), maxfees(ai), IncreaseOrder)
 84  end
 85  
 86  @doc """
 87  Calculates the commitment for a leveraged position.
 88  
 89  $(TYPEDSIGNATURES)
 90  """
 91  function committment(
 92      o::Type{<:IncreaseOrder},
 93      ai::MarginInstance,
 94      price,
 95      amount;
 96      ntl=cost(price, amount),
 97      fees=ntl * maxfees(ai),
 98      lev=leverage(ai, positionside(o)()),
 99      kwargs...,
100  )
101      margin = ntl / lev
102      margin + fees
103  end
104  
105  @doc """
106  Calculates the commitment when exiting a position for longs.
107  
108  $(TYPEDSIGNATURES)
109  """
110  function committment(::Type{<:SellOrder}, ai, price, amount; fees_base=0.0, kwargs...)
111      @deassert amount > 0.0
112      amount_with_fees(amount, fees_base)
113  end
114  
115  @doc """
116  Calculates the commitment when exiting a position for shorts.
117  
118  $(TYPEDSIGNATURES)
119  """
120  function committment(::Type{<:ShortBuyOrder}, ai, price, amount; fees_base=0.0, kwargs...)
121      @deassert amount > 0.0
122      amount_with_fees(negate(amount), fees_base)
123  end
124  
125  @doc """
126  Calculates the partial commitment of a trade.
127  
128  $(TYPEDSIGNATURES)
129  """
130  function committment(ai::AssetInstance, t::Trade)
131      o = t.order
132      committment(
133          typeof(o), ai, o.price, t.amount; t.fees_base, t.fees, ntl=t.value, lev=t.leverage
134      )
135  end
136  
137  @doc """
138  Calculates the commitment for an order.
139  
140  $(TYPEDSIGNATURES)
141  """
142  function committment(ai::AssetInstance, o::Order; kwargs...)
143      committment(typeof(o), ai, o.price, o.amount; kwargs...)
144  end
145  
146  @doc """
147  Calculates the unfulfilled amount for a buy order.
148  
149  $(TYPEDSIGNATURES)
150  """
151  function unfillment(t::Type{<:AnyBuyOrder}, amount)
152      @deassert amount > 0.0
153      @deassert !(t isa AnySellOrder)
154      negate(abs(amount))
155  end
156  
157  @doc """
158  Calculates the unfulfilled amount for a sell order.
159  
160  $(TYPEDSIGNATURES)
161  """
162  function unfillment(t::Type{<:AnySellOrder}, amount)
163      @deassert amount > 0.0
164      @deassert !(t isa AnyBuyOrder)
165      amount
166  end
167  
168  @doc """
169  Calculates the unfulfilled amount for an order.
170  
171  $(TYPEDSIGNATURES)
172  """
173  unfillment(o::Order) = unfillment(typeof(o), o.amount)
174  
175  @doc """
176  Checks if a strategy can commit to an increase order.
177  
178  $(TYPEDSIGNATURES)
179  """
180  function iscommittable(s::Strategy, ::Type{<:IncreaseOrder}, commit, ai)
181      @deassert st.freecash(s) |> gtxzero
182      c = st.freecash(s)
183      comm = commit[]
184      c >= comm || isapprox(c, comm)
185  end
186  
187  @doc """
188  Checks if a strategy can commit to a sell order.
189  
190  $(TYPEDSIGNATURES)
191  """
192  function iscommittable(s::Strategy, ::Type{<:SellOrder}, commit, ai)
193      @deassert Instances.freecash(ai, Long()) |> gtxzero
194      @deassert commit[] |> gtxzero
195      c = Instances.freecash(ai, Long())
196      comm = commit[]
197      c >= comm || isapprox(c, comm)
198  end
199  
200  @doc """
201  Checks if a strategy can commit to a short buy order.
202  
203  $(TYPEDSIGNATURES)
204  """
205  function iscommittable(::Strategy, ::Type{<:ShortBuyOrder}, commit, ai)
206      @deassert Instances.freecash(ai, Short()) |> ltxzero
207      @deassert commit[] |> ltxzero
208      c = Instances.freecash(ai, Short())
209      comm = commit[]
210      c <= comm || isapprox(c, comm)
211  end
212  
213  @doc """
214  Iterates over all the orders in a strategy.
215  
216  $(TYPEDSIGNATURES)
217  """
218  function orders(s::Strategy)
219      OrderIterator((orders(s, ai, side) for side in (Buy, Sell) for ai in s.holdings))
220  end
221  
222  @doc """
223  Iterates over all the orderless orders in a strategy.
224  
225  $(TYPEDSIGNATURES)
226  """
227  function orders(s::Strategy, ::Val{:orderless})
228      (o for side in (Buy, Sell) for ai in s.holdings for o in orders(s, ai, side))
229  end
230  
231  function orders(s::Strategy, ::BySide{O}, ::Val{:orderless}) where {O<:Union{Buy,Sell}}
232      odict = ordersdict(s, O)
233      (o for ai in s.holdings for o in odict[ai])
234  end
235  
236  @doc """
237  Iterates over all the orders in a strategy (all the assets in the universe).
238  
239  $(TYPEDSIGNATURES)
240  """
241  function orders(s::Strategy, ::Val{:universe})
242      OrderIterator((orders(s, ai, side) for side in (Buy, Sell) for ai in s.universe))
243  end
244  
245  @doc """
246  Iterates orderlessly over all the orders in a strategy (all the assets in the universe).
247  
248  $(TYPEDSIGNATURES)
249  """
250  function orders(s::Strategy, ::Val{:orderless}, ::Val{:universe})
251      OrderIterator((orders(s, ai, side) for side in (Buy, Sell) for ai in s.universe))
252  end
253  
254  @doc """
255  Iterates over all the orders for an asset instance in a strategy.
256  
257  $(TYPEDSIGNATURES)
258  """
259  function orders(s::Strategy, ai::AssetInstance)
260      buys = orders(s, ai, Buy)
261      if length(buys) == 0
262          orders(s, ai, Sell)
263      else
264          sells = orders(s, ai, Sell)
265          if length(sells) == 0
266              buys
267          else
268              OrderIterator(buys, sells)
269          end
270      end
271  end
272  
273  @doc """
274  Iterates over all the orderless orders for an asset instance in a strategy.
275  
276  $(TYPEDSIGNATURES)
277  """
278  function orders(s::Strategy, ai::AssetInstance, ::Val{:orderless})
279      (o for side in (Buy, Sell) for o in orders(s, ai, side))
280  end
281  
282  @doc """
283  Returns all orders for an asset instance in a strategy.
284  
285  $(TYPEDSIGNATURES)
286  """
287  orders(s, ai, ::Type{BuyOrSell}) = orders(s, ai)
288  
289  @doc """
290  Returns all buy orders for a strategy.
291  
292  $(TYPEDSIGNATURES)
293  """
294  orders(s::Strategy, ::BySide{Buy}) =
295      OrderIterator(Iterators.flatten(pairs(dict) for dict in values(s.buyorders)))
296  
297  @doc """
298  Returns all sell orders for a strategy.
299  
300  $(TYPEDSIGNATURES)
301  """
302  orders(s::Strategy, ::BySide{Sell}) =
303      OrderIterator(Iterators.flatten(pairs(dict) for dict in values(s.sellorders)))
304  
305  orders(s::Strategy, ::Type{BuyOrSell}) = orders(s)
306  
307  @doc """
308  Returns all buy orders for an asset in a strategy.
309  
310  $(TYPEDSIGNATURES)
311  """
312  function orders(s::Strategy{M,S,E}, ai, ::BySide{Buy}) where {M,S,E}
313      @lget! s.buyorders ai st.BuyOrdersDict{E}(st.BuyPriceTimeOrdering())
314  end
315  
316  @doc """
317  Returns all sell orders for an asset in a strategy.
318  
319  $(TYPEDSIGNATURES)
320  """
321  function orders(s::Strategy{M,S,E}, ai, ::BySide{Sell}) where {M,S,E}
322      @lget! s.sellorders ai st.SellOrdersDict{E}(st.SellPriceTimeOrdering())
323  end
324  
325  """
326  Returns a unique list of orders from the trade history of a given asset instance.
327  
328  $(TYPEDSIGNATURES)
329  """
330  function ordershistory(ai::AssetInstance)
331      unique(t.order for t in trades(ai))
332  end
333  
334  @doc """
335  Returns all keys for orders in a strategy.
336  
337  $(TYPEDSIGNATURES)
338  """
339  Base.keys(s::Strategy, args...; kwargs...) = (k for (k, _) in orders(s, args...; kwargs...))
340  
341  @doc """
342  Returns all values for orders in a strategy.
343  
344  $(TYPEDSIGNATURES)
345  """
346  function Base.values(s::Strategy, args...; kwargs...)
347      (o for (_, o) in orders(s, args...; kwargs...))
348  end
349  
350  @doc """
351  Returns the first order for an asset in a strategy.
352  
353  $(TYPEDSIGNATURES)
354  """
355  function Base.first(s::Strategy{M,S,E}, ai, bs::BySide=BuyOrSell) where {M,S,E}
356      values(s, ai, bs) |> first
357  end
358  
359  @doc """
360  Returns the first index for an order for an asset in a strategy.
361  
362  $(TYPEDSIGNATURES)
363  """
364  function Base.firstindex(s::Strategy{M,S,E}, ai, bs::BySide=BuyOrSell) where {M,S,E}
365      keys(s, ai, bs) |> first
366  end
367  
368  @doc """
369  Returns the last order for an asset in a strategy.
370  
371  $(TYPEDSIGNATURES)
372  """
373  function Base.last(s::Strategy{M,S,E}, ai, bs::BySide=BuyOrSell) where {M,S,E}
374      ans = missing
375      for v in values(s, ai, bs)
376          ans = v
377      end
378      ismissing(ans) && throw(BoundsError())
379      ans
380  end
381  
382  @doc """
383  Returns the last index for an order for an asset in a strategy.
384  
385  $(TYPEDSIGNATURES)
386  """
387  function Base.lastindex(s::Strategy{M,S,E}, ai, bs::BySide=BuyOrSell) where {M,S,E}
388      ans = missing
389      for k in keys(s, ai, bs)
390          ans = k
391      end
392      ismissing(ans) && throw(BoundsError())
393      ans
394  end
395  
396  function ordersdict(s::Strategy, bs::BySide{O}) where {O<:Union{Buy,Sell}}
397      bs === Buy ? s.buyorders : s.sellorders
398  end
399  
400  @doc """
401  Returns the count of orders in a strategy.
402  
403  $(TYPEDSIGNATURES)
404  """
405  function orderscount(s::Strategy, ::BySide{O}) where {O}
406      ans = 0
407      foreach(ordersdict(s, O)) do (_, dict)
408          ans += length(dict)
409      end
410      ans
411  end
412  
413  @doc """
414  Returns the count of pending entry orders in a strategy.
415  
416  $(TYPEDSIGNATURES)
417  """
418  function orderscount(s::Strategy, ::Val{:increase})
419      ans = 0
420      itr = values(s)
421      foreach(itr) do o
422          if o isa IncreaseOrder
423              ans += 1
424          end
425      end
426      ans
427  end
428  
429  @doc """
430  Returns the count of pending exit orders in a strategy.
431  
432  $(TYPEDSIGNATURES)
433  """
434  function orderscount(s::Strategy, ::Val{:reduce})
435      ans = 0
436      itr = values(s)
437      foreach(itr) do o
438          if o isa ReduceOrder
439              ans += 1
440          end
441      end
442      ans
443  end
444  
445  function orderscount(s::Strategy, ::Val{:inc_red})
446      inc_ans = 0
447      dec_ans = 0
448      itr = values(s)
449      foreach(itr) do o
450          if o isa IncreaseOrder
451              inc_ans += 1
452          elseif o isa ReduceOrder
453              dec_ans += 1
454          end
455      end
456      return inc_ans, dec_ans
457  end
458  
459  @doc """
460  Returns the total count of pending orders in a strategy.
461  
462  $(TYPEDSIGNATURES)
463  """
464  function orderscount(s::Strategy)
465      orderscount(s, Buy) + orderscount(s, Sell)
466  end
467  
468  @doc """
469  Returns the count of orders for an asset in a strategy.
470  
471  $(TYPEDSIGNATURES)
472  """
473  function orderscount(s::Strategy, ai::AssetInstance)
474      n = 0
475      foreach(orders(s, ai)) do _
476          n += 1
477      end
478      n
479  end
480  
481  @doc """
482  Returns the count of orders for an asset in a strategy.
483  
484  $(TYPEDSIGNATURES)
485  """
486  orderscount(s::Strategy, ai::AssetInstance, ::Type{BuyOrSell}) = orderscount(s, ai)
487  
488  @doc """
489  Returns the count of buy orders for an asset in a strategy.
490  
491  $(TYPEDSIGNATURES)
492  """
493  orderscount(s::Strategy, ai::AssetInstance, ::Type{Buy}) = length(buyorders(s, ai))
494  
495  @doc """
496  Returns the count of sell orders for an asset in a strategy.
497  
498  $(TYPEDSIGNATURES)
499  """
500  orderscount(s::Strategy, ai::AssetInstance, ::Type{Sell}) = length(sellorders(s, ai))
501  
502  @doc """Checks if any of the holdings has non dust cash.
503  
504  $(TYPEDSIGNATURES)
505  """
506  function hascash(s::Strategy)
507      for ai in s.holdings
508          iszero(ai) || return true
509      end
510      return false
511  end
512  
513  @doc """
514  Checks if a strategy has orders.
515  
516  $(TYPEDSIGNATURES)
517  """
518  hasorders(s::Strategy) = orderscount(s) > 0
519  
520  @doc """
521  Returns buy orders for an asset in a strategy.
522  
523  $(TYPEDSIGNATURES)
524  """
525  buyorders(s::Strategy, ai) = orders(s, ai, Buy)
526  
527  @doc """
528  Returns sell orders for an asset in a strategy.
529  
530  $(TYPEDSIGNATURES)
531  """
532  sellorders(s::Strategy, ai) = orders(s, ai, Sell)
533  
534  @doc """
535  Returns orders for an asset in a strategy by side.
536  
537  $(TYPEDSIGNATURES)
538  """
539  sideorders(s::Strategy, ai, ::Type{Buy}) = buyorders(s, ai)
540  
541  @doc """
542  Returns orders for an asset in a strategy by side.
543  
544  $(TYPEDSIGNATURES)
545  """
546  sideorders(s::Strategy, ai, ::Type{Sell}) = sellorders(s, ai)
547  
548  @doc """
549  Returns orders for an asset in a strategy by side.
550  
551  $(TYPEDSIGNATURES)
552  """
553  sideorders(s::Strategy, ai, ::BySide{S}) where {S} = sideorders(s, ai, S)
554  
555  @doc """
556  Checks if an asset instance has pending buy orders in a strategy.
557  
558  $(TYPEDSIGNATURES)
559  """
560  hasorders(s::Strategy, ai, ::Type{S}) where {S<:Union{Buy,Sell}} = length(orders(s, ai, S)) > 0
561  
562  @doc """
563  Checks if an asset instance has pending orders in a strategy.
564  
565  $(TYPEDSIGNATURES)
566  """
567  function hasorders(s::Strategy, ai::AssetInstance)
568      hasorders(s, ai, Sell) || hasorders(s, ai, Buy)
569  end
570  
571  function hasorders(s::Strategy, ai::AssetInstance, ::Type{BuyOrSell})
572      hasorders(s, ai)
573  end
574  
575  @doc """
576  Checks if an asset instance has a specific order in a strategy by side.
577  
578  $(TYPEDSIGNATURES)
579  """
580  function hasorders(s::Strategy, ai, id::String, ::BySide{S}=BuyOrSell) where {S<:OrderSide}
581      for o in values(s, ai, S)
582          o.id == id && return true
583      end
584      false
585  end
586  
587  @doc """
588  Checks if a strategy has a specific order for an asset.
589  
590  $(TYPEDSIGNATURES)
591  """
592  Base.haskey(s::Strategy, ai, o::Order) = haskey(sideorders(s, ai, o), pricetime(o))
593  
594  @doc """
595  Checks if a strategy has a specific order for an asset by price and time.
596  
597  $(TYPEDSIGNATURES)
598  """
599  function Base.haskey(s::Strategy, ai, pt::PriceTime, side::BySide{<:Union{Buy,Sell}})
600      haskey(sideorders(s, ai, side), pt)
601  end
602  
603  @doc """
604  Checks if a strategy has a specific order for an asset by price and time.
605  
606  $(TYPEDSIGNATURES)
607  """
608  function Base.haskey(s::Strategy, ai, pt::PriceTime, ::BySide{BuyOrSell})
609      haskey(sideorders(s, ai, Buy), pt) || haskey(sideorders(s, ai, Sell), pt)
610  end
611  
612  @doc """
613  Checks if a strategy has a specific order for an asset by price and time.
614  
615  $(TYPEDSIGNATURES)
616  """
617  Base.haskey(s::Strategy, ai, pt::PriceTime) = haskey(s, ai, pt, BuyOrSell)
618  
619  @doc """
620  Checks if a strategy has sell orders.
621  
622  $(TYPEDSIGNATURES)
623  """
624  function hasorders(s::Strategy, ::BySide{S}) where {S<:OrderSide}
625      for ai in universe(s)
626          ords = sideorders(s, ai, S)
627          if !isempty(ords)
628              return true
629          end
630      end
631      return false
632  end
633  
634  @doc """
635  Checks if a strategy is out of orders.
636  
637  $(TYPEDSIGNATURES)
638  """
639  function isoutof_orders(s::Strategy)
640      ltxzero(s.cash) && isempty(s.holdings) && orderscount(s) == 0
641  end
642  
643  @doc """
644  Checks a buy trade.
645  
646  $(TYPEDSIGNATURES)
647  """
648  function _check_trade(t::BuyTrade, ai)
649      @deassert t.price <= t.order.price || ordertype(t) <: MarketOrderType
650      @deassert t.size < 0.0
651      @deassert t.amount > 0.0
652      @deassert if isshort(t)
653          ltxzero(ai, committed(t.order), Val(:amount))
654      else
655          gtxzero(committed(t.order), atol=fees(t))
656      end committed(t.order), t.order
657  end
658  
659  @doc """
660  Checks a sell trade.
661  
662  $(TYPEDSIGNATURES)
663  """
664  function _check_trade(t::SellTrade, ai)
665      @deassert t.price >= t.order.price || ordertype(t) <: MarketOrderType (
666          t.price, t.order.price
667      )
668      @deassert t.size > 0.0
669      @deassert t.amount < 0.0
670      @deassert committed(t.order) >= -1e-12
671  end
672  
673  @doc """
674  Checks a short sell trade.
675  
676  $(TYPEDSIGNATURES)
677  """
678  function _check_trade(t::ShortSellTrade, ai)
679      @deassert t.price >= t.order.price || ordertype(t) <: MarketOrderType
680      @deassert t.size < 0.0
681      @deassert t.amount < 0.0
682      @deassert abs(committed(t.order)) <= t.fees || t.order isa ShortSellOrder
683  end
684  
685  @doc """
686  Checks a short buy trade.
687  
688  $(TYPEDSIGNATURES)
689  """
690  function _check_trade(t::ShortBuyTrade, ai)
691      @deassert t.price <= t.order.price || ordertype(t) <: MarketOrderType (
692          t.price, t.order.price
693      )
694      @deassert t.size > 0.0
695      @deassert t.amount > 0.0
696      @deassert committed(t.order) |> ltxzero
697  end
698  
699  @doc """
700  Checks the cash for an asset instance in a strategy for long.
701  
702  $(TYPEDSIGNATURES)
703  """
704  function _check_cash(ai::AssetInstance, ::Long)
705      @deassert gtxzero(ai, committed(ai, Long()), Val(:amount)) ||
706          ordertype(last(ai.history)) <: MarketOrderType committed(ai, Long()).value
707      @deassert cash(ai, Long()) |> gtxzero
708  end
709  
710  @doc """
711  Checks the cash for an asset instance in a strategy for short.
712  
713  $(TYPEDSIGNATURES)
714  """
715  _check_cash(ai::AssetInstance, ::Short) = begin
716      @deassert committed(ai, Short()) |> ltxzero
717      @deassert cash(ai, Short()) |> ltxzero
718  end
719  
720  _cur_by_side(o::BuyOrder) = :fees_base
721  _cur_by_side(o::SellOrder) = :fees
722  @doc """
723  The sum of all the trades fees that have heppened for the order.
724  
725  $(TYPEDSIGNATURES)
726  """
727  function feespaid(o::Order)
728      ot = trades(o)
729      if isempty(ot)
730          0.0
731      else
732          cur = _cur_by_side(o)
733          sum(getproperty(t, cur) for t in trades(o))
734      end
735  end
736  
737  tradetuple(t::Trade) = (t.order, t.price, t.size, t.amount)
738  function tradetuple(ai::AssetInstance, t::Trade)
739      (
740          t.order.id,
741          t.order.date,
742          toprecision(t.price, ai.precision.price),
743          toprecision(t.size, ai.precision.amount),
744          toprecision(t.amount, ai.precision.amount),
745      )
746  end
747  
748  @doc """
749  Check if the given trade is in the order.
750  
751  $(TYPEDSIGNATURES)
752  """
753  hastrade(o::Order, t::Trade) = begin
754      tup = tradetuple(t)
755      for t in trades(o)
756          if tup == tradetuple(t)
757              return true
758          end
759      end
760      return false
761  end
762  
763  @doc "More precise version of `hastrade`."
764  function hastrade(ai::AssetInstance, o::Order, t::Trade)
765      tup = tradetuple(ai, t)
766      for t in trades(o)
767          if tup == tradetuple(ai, t)
768              return true
769          end
770      end
771      return false
772  end
773  
774  @doc """
775  Returns the order that matches the given id (if any).
776  
777  $(TYPEDSIGNATURES)
778  """
779  function order_byid(s::Strategy, ai::AssetInstance, id::String)
780      for o in values(s, ai)
781          if o.id == id
782              return o
783          end
784      end
785  end
786  
787  function order_byid(s::Strategy, id::String)
788      for ai in s.holdings
789          o = order_byid(s, ai, id)
790          if !isnothing(o)
791              return o
792          end
793      end
794  end
795  
796  isnocost(o::BySide) = ordertype(o) <: MarketOrderType