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