module.jl
1 using Exchanges 2 using OrderTypes 3 4 import Exchanges.ExchangeTypes: exchangeid, exchange 5 using Exchanges: CurrencyCash, Data, TICKERS_CACHE10, markettype, @tickers! 6 using OrderTypes: ByPos, AssetEvent, positionside, Instruments, ordertype 7 using .Data: load, zi, empty_ohlcv, DataFrame, DataStructures 8 using .Data.DFUtils: daterange, timeframe 9 import .Data: stub! 10 using .Data.DataFrames: metadata 11 using .Instruments: Instruments, compactnum, AbstractAsset, Cash, add!, sub!, Misc 12 import .Instruments: _hashtuple, cash!, cash, freecash, value, raw, bc, qc 13 using .Misc: config, MarginMode, NoMargin, WithMargin, MM, DFT, toprecision, ZERO 14 using .Misc: Lang, TimeTicks, SortedArray, SafeLock 15 using .Misc: Isolated, Cross, Hedged, IsolatedHedged, CrossHedged, CrossMargin 16 using .Misc: setattr!, attr, attr!, attrs, hasattr 17 using .Misc.DocStringExtensions 18 import .Misc: approxzero, gtxzero, ltxzero, marginmode, load! 19 using .TimeTicks 20 import .TimeTicks: timeframe 21 using .DataStructures: SortedDict 22 using .Lang: Option, @deassert, @lget!, @caller 23 import Base: position, isopen 24 import Exchanges: lastprice, leverage! 25 import OrderTypes: trades 26 27 baremodule InstancesLock end 28 29 @doc """Defines the abstract type for an instance. 30 31 The `AbstractInstance` type is a generic abstract type for an instance. It is parameterized by two types: `A`, which must be a subtype of `AbstractAsset`, and `E`, which must be a subtype of `ExchangeID`. 32 """ 33 abstract type AbstractInstance{A<:AbstractAsset,E<:ExchangeID} end 34 35 @doc "Defines a NamedTuple structure for limits, including leverage, amount, price, and cost, each of which is a subtype of Real." 36 const Limits{T<:Real} = NamedTuple{(:leverage, :amount, :price, :cost),<:NTuple{4,MM{<:T}}} 37 @doc "Defines a NamedTuple structure for precision, including amount and price, each of which is a subtype of Real." 38 const Precision{T<:Real} = NamedTuple{(:amount, :price),<:Tuple{<:T,<:T}} 39 @doc "Defines a NamedTuple structure for fees, including taker, maker, minimum, and maximum fees, each of which is a subtype of Real." 40 const Fees{T<:Real} = NamedTuple{(:taker, :maker, :min, :max),<:NTuple{4,<:T}} 41 @doc "Defines a type for currency cash, which is parameterized by an exchange `E` and a symbol `S`." 42 const CCash{E} = CurrencyCash{Cash{S,DFT},E} where {S} 43 const AnyTrade{T,E} = Trade{O,T,E} where {O<:OrderType} 44 const DEFAULT_FIELDS = (; 45 limits=(; 46 leverage=(; min=1.0, max=10.0), 47 amount=(; min=1e-8, max=1e8), 48 price=(; min=1e-8, max=1e8), 49 cost=(; min=1e-8, max=1e8), 50 ), 51 precision=(; amount=1e-8, price=1e-8), 52 fees=(; taker=0.01, maker=0.01, min=0.01, max=0.01), 53 ) 54 55 include("positions.jl") 56 57 @doc """Defines a structure for an asset instance. 58 59 $(FIELDS) 60 61 An `AssetInstance` holds all known state about an exchange asset like `BTC/USDT`. 62 """ 63 struct AssetInstance{T<:AbstractAsset,E<:ExchangeID,M<:MarginMode} <: AbstractInstance{T,E} 64 "Genric dict for instance specific parameters." 65 attrs::Dict{Symbol,Any} 66 "The identifier of the asset." 67 asset::T 68 "The OHLCV (Open, High, Low, Close, Volume) series for the asset." 69 data::SortedDict{TimeFrame,DataFrame} 70 "The trade history of the pair." 71 history::SortedArray{AnyTrade{T,E},1} 72 "A lock for synchronizing access to the asset instance." 73 lock::SafeLock 74 _internal_lock::SafeLock 75 "The amount of the asset currently held. This can be positive or negative (short)." 76 cash::Option{CCash{E}{S1}} where {S1} 77 "The amount of the asset currently committed for orders." 78 cash_committed::Option{CCash{E}{S2}} where {S2} 79 "The exchange instance that this asset instance belongs to." 80 exchange::Exchange{E} 81 "The long position of the asset." 82 longpos::Option{Position{Long,E,M}} 83 "The short position of the asset." 84 shortpos::Option{Position{Short,E,M}} 85 "The last position of the asset." 86 lastpos::Vector{Option{Position{P,E,M} where {P<:PositionSide}}} 87 "The minimum order size (from the exchange)." 88 limits::Limits{DFT} 89 "The number of decimal points (from the exchange)." 90 precision::Precision{<:Union{Int,DFT}} 91 "The fees associated with the asset (from the exchange)." 92 fees::Fees{DFT} 93 @doc """ Create an `AssetInstance` object. 94 95 $(TYPEDSIGNATURES) 96 97 This function constructs an `AssetInstance` with defined asset, data, exchange, margin, and optional parameters for limits, precision, and fees. It initializes long and short positions based on the provided margin and ensures that the margin is not hedged. 98 99 """ 100 function AssetInstance( 101 a::A, data, e::Exchange{E}, margin::M; limits, precision, fees 102 ) where {A<:AbstractAsset,E<:ExchangeID,M<:MarginMode} 103 @assert !ishedged(margin) "Hedged margin not yet supported." 104 local longpos, shortpos 105 longpos, shortpos = positions(M, a, limits, e) 106 cash, comm = if M == NoMargin 107 (CurrencyCash(e, a.bc, 0.0), CurrencyCash(e, a.bc, 0.0)) 108 else 109 (nothing, nothing) 110 end 111 lastpos = Vector{Option{Position{<:PositionSide,E,M}}}() 112 push!(lastpos, nothing) 113 if !(ispercentage(e.markets[raw(a)])) 114 @warn "Exchange uses fixed amount fees, fees calculation will not match!" 115 end 116 new{A,E,M}( 117 Dict{Symbol,Any}(), 118 a, 119 data, 120 SortedArray(AnyTrade{A,E}[]; by=trade -> trade.date), 121 SafeLock(), 122 SafeLock(), 123 cash, 124 comm, 125 e, 126 longpos, #::Option{Position{Long,E,<:WithMargin}}, 127 shortpos, #::Option{Position{Short,E,<:WithMargin}}, 128 lastpos, 129 limits, 130 precision, 131 fees, 132 ) 133 end 134 end 135 136 @doc "A type alias representing an asset instance with no margin." 137 const NoMarginInstance = AssetInstance{<:AbstractAsset,<:ExchangeID,NoMargin} 138 @doc "A type alias for an asset instance with either isolated or cross margin." 139 const MarginInstance{M<:Union{Isolated,Cross}} = AssetInstance{ 140 <:AbstractAsset,<:ExchangeID,M 141 } 142 @doc "A type alias for an asset instance with either isolated or cross hedged margin." 143 const HedgedInstance{M<:Union{IsolatedHedged,CrossHedged}} = AssetInstance{ 144 <:AbstractAsset,<:ExchangeID,M 145 } 146 @doc "A type alias representing an asset instance with cross margin." 147 const CrossInstance{M<:CrossMargin} = AssetInstance{<:AbstractAsset,<:ExchangeID,M} 148 @doc " Retrieve the margin mode of an `AssetInstance`. " 149 marginmode(::AssetInstance{<:AbstractAsset,<:ExchangeID,M}, args...) where {M<:WithMargin} = M() 150 marginmode(::NoMarginInstance, args...) = NoMargin() 151 152 @doc """ Generate positions for a specific margin mode. 153 154 $(TYPEDSIGNATURES) 155 156 This function generates long and short positions for a given asset on a specific exchange. The number and size of the positions are determined by the `limits` argument and the margin mode `M`. 157 158 """ 159 function positions(M::Type{<:MarginMode}, a::AbstractAsset, limits::Limits, e::Exchange) 160 if M == NoMargin 161 nothing, nothing 162 else 163 let tiers = leverage_tiers(e, a.raw) 164 function pos_kwargs() 165 (; 166 asset=a, 167 min_size=limits.amount.min, 168 tiers=[tiers], 169 this_tier=[first(values(tiers))], 170 cash=CurrencyCash(e, a.bc, 0.0), 171 cash_committed=CurrencyCash(e, a.bc, 0.0), 172 ) 173 end 174 175 LongPosition{typeof(e.id),M}(; pos_kwargs()...), 176 ShortPosition{typeof(e.id),M}(; pos_kwargs()...) 177 end 178 end 179 end 180 181 _external_lock(ai::AssetInstance) = getfield(ai, :lock) 182 _internal_lock(ai::AssetInstance) = getfield(ai, :_internal_lock) 183 184 function _hashtuple(ai::AssetInstance) 185 ( 186 Instruments._hashtuple(getfield(ai, :asset))..., 187 getfield(getfield(ai, :exchange), :id), 188 ) 189 end 190 Base.hash(ai::AssetInstance) = hash(_hashtuple(ai)) 191 Base.hash(ai::AssetInstance, h::UInt) = hash(_hashtuple(ai), h) 192 function Base.propertynames(ai::AssetInstance) 193 (fieldnames(AssetInstance)..., :ohlcv, :funding, keys(attrs(ai))...) 194 end 195 Base.Broadcast.broadcastable(s::AssetInstance) = Ref(s) 196 function Base.lock(ai::AssetInstance) 197 @debug "instances: locking" _module = InstancesLock ai tid = Threads.threadid() f = @caller(10) 198 lock(getfield(ai, :lock)) 199 @debug "instances: locked" _module = InstancesLock ai tid = Threads.threadid() f = @caller(10) 200 end 201 Base.lock(f, ai::AssetInstance) = begin 202 l = getfield(ai, :lock) 203 lock(f, getfield(ai, :lock)) 204 end 205 function Base.unlock(ai::AssetInstance) 206 unlock(getfield(ai, :lock)) 207 @debug "instances: unlocked" _module = InstancesLock ai tid = Threads.threadid() f = @caller(10) 208 end 209 Base.islocked(ai::AssetInstance) = islocked(getfield(ai, :lock)) 210 @doc " Get the cash value of a `AssetInstance`. " 211 Base.float(ai::AssetInstance) = nothing 212 Base.float(ai::NoMarginInstance) = cash(ai).value 213 Base.float(ai::MarginInstance) = 214 let c = cash(ai) 215 if isnothing(c) 216 0.0 217 else 218 c.value 219 end 220 end 221 Base.abs(ai::MarginInstance) = 222 let pos = position(ai) 223 if isnothing(pos) 224 0.0 225 else 226 abs(pos) 227 end 228 end 229 Base.getindex(ai::AssetInstance, k::Symbol) = attr(ai, k) 230 Base.get(ai::AssetInstance, keys::Tuple{Vararg{Symbol}}) = attr(ai, keys...) 231 Base.get(ai::AssetInstance, k, v) = attr(ai, k, v) 232 Base.setindex!(ai::AssetInstance, v, k::Symbol) = setattr!(ai, v, k) 233 Base.setindex!(ai::AssetInstance, v, keys::Vararg{Symbol}) = setattr!(ai, v, keys...) 234 Base.get!(ai::AssetInstance, k, v) = attr!(ai, k, v) 235 Base.haskey(ai::AssetInstance, k::Symbol) = hasattr(ai, k) 236 Base.keys(ai::AssetInstance) = keys(attrs(ai)) 237 Base.values(ai::AssetInstance) = values(attrs(ai)) 238 239 posside(::NoMarginInstance) = Long() 240 @doc "Get the position side of an `AssetInstance`. " 241 posside(ai::MarginInstance) = 242 let pos = position(ai) 243 isnothing(pos) ? nothing : posside(pos) 244 end 245 _ishedged(::Union{T,Type{T}}) where {T<:MarginMode{H}} where {H} = H == Hedged 246 # NOTE: wrap the function here to quickly overlay methods 247 @doc "Check if the margin mode is hedged." 248 ishedged(args...; kwargs...) = _ishedged(args...; kwargs...) 249 @doc "Check if the `AssetInstance` is hedged." 250 ishedged(ai::AssetInstance) = marginmode(ai) |> ishedged 251 @doc "Check if the `AssetInstance` is open." 252 isopen(ai::NoMarginInstance) = !iszero(ai) 253 isopen(ai::MarginInstance) = 254 let po = position(ai) 255 !isnothing(po) && isopen(po) 256 end 257 @doc "Check if the `AssetInstance` is long." 258 islong(ai::NoMarginInstance) = true 259 @doc "Check if the `AssetInstance` is short." 260 isshort(ai::NoMarginInstance) = false 261 islong(ai::MarginInstance) = 262 let pos = position(ai) 263 isnothing(pos) && return false 264 islong(pos) 265 end 266 isshort(ai::MarginInstance) = 267 let pos = position(ai) 268 isnothing(pos) && return false 269 isshort(pos) 270 end 271 272 @doc """ Check if the position value of the asset is below minimum quantity. 273 274 $(TYPEDSIGNATURES) 275 276 This function checks if the position value of a given `AssetInstance` at a specific price is below the minimum limit for that asset. The position side `p` determines if it's a long or short position. 277 278 """ 279 function isdust(ai::MarginInstance, price::Number, p::PositionSide) 280 pos = position(ai, p) 281 if isnothing(pos) 282 return true 283 end 284 this_cash = cash(pos) |> value |> abs 285 if this_cash >= ai.limits.amount.min 286 return false 287 else 288 this_cash * price * leverage(pos) < ai.limits.cost.min 289 end 290 end 291 function isdust(ai::MarginInstance, price::Number) 292 isdust(ai, price, Long()) && isdust(ai, price, Short()) 293 end 294 function isdust(ai::NoMarginInstance, price::Number) 295 this_cash = cash(ai) |> value |> abs 296 if this_cash >= ai.limits.amount.min 297 return false 298 else 299 this_cash * price < ai.limits.cost.min 300 end 301 end 302 function isdust(ai::AssetInstance, o::Type{<:Order}, price::Number) 303 if o <: ReduceOnlyOrder 304 false 305 else 306 invoke(isdust, Tuple{MarginInstance,Number,PositionSide}, ai, price, posside(ai)) 307 end 308 end 309 @doc """ Get the asset cash rounded to precision. 310 311 $(TYPEDSIGNATURES) 312 313 This function returns the asset cash of a `MarginInstance` rounded according to the asset's precision. The position side `p` is determined by the `posside` function. 314 315 """ 316 function nondust(ai::MarginInstance, price::Number, p=posside(ai)) 317 pos = position(ai, p) 318 if isnothing(pos) 319 return zero(price) 320 end 321 c = cash(pos) 322 amt = c.value 323 abs(amt * price * leverage(pos)) < ai.limits.cost.min ? zero(amt) : amt 324 end 325 326 function nondust(ai::MarginInstance, o::Type{<:Order}, price) 327 if o <: ReduceOnlyOrder 328 cash(ai, o).value 329 else 330 invoke(nondust, Tuple{MarginInstance,Number,PositionSide}, ai, price, posside(o)) 331 end 332 end 333 334 @doc """ Check if the amount is below the asset instance's minimum limit. 335 336 $(TYPEDSIGNATURES) 337 338 This function checks if a specified amount in base currency is considered zero with respect to an `AssetInstance`'s minimum limit. The amount is considered zero if it is less than the minimum limit minus a small epsilon value. 339 340 """ 341 function Base.iszero(ai::AssetInstance, v; atol=ai.limits.amount.min - eps(DFT)) 342 isapprox(v, zero(DFT); atol) 343 end 344 @doc """ Check if the asset cash for a position side is zero. 345 346 $(TYPEDSIGNATURES) 347 348 This function checks if the cash value of an `AssetInstance` for a specific `PositionSide` is zero. This is used to determine if there are no funds in a certain position side (long or short). 349 350 """ 351 function Base.iszero(ai::AssetInstance, p::PositionSide) 352 isapprox(value(cash(ai, p)), zero(DFT); atol=ai.limits.amount.min - eps(DFT)) 353 end 354 @doc """ Check if the asset cash is zero. 355 356 $(TYPEDSIGNATURES) 357 358 This function checks if the cash value of an `AssetInstance` is zero. This is used to determine if there are no funds in the asset. 359 360 """ 361 function Base.iszero(ai::AssetInstance) 362 iszero(ai, Long()) && iszero(ai, Short()) 363 end 364 approxzero(ai::AssetInstance, args...; kwargs...) = iszero(ai, args...; kwargs...) 365 @doc """ Check if an amount is greater than zero for an `AssetInstance`. 366 367 $(TYPEDSIGNATURES) 368 369 This function checks if a specified amount `v` is greater than zero for an `AssetInstance`. It's used to validate the amount before performing operations on the asset. 370 371 """ 372 function gtxzero(ai::AssetInstance, v, ::Val{:amount}) 373 gtxzero(v; atol=ai.limits.amount.min + eps()) 374 end 375 @doc """ Check if an amount is less than zero for an `AssetInstance`. 376 377 $(TYPEDSIGNATURES) 378 379 This function checks if a specified amount `v` is less than zero for an `AssetInstance`. It's used to validate the amount before performing operations on the asset. 380 381 """ 382 function ltxzero(ai::AssetInstance, v, ::Val{:amount}) 383 ltxzero(v; atol=ai.limits.amount.min + eps()) 384 end 385 @doc """ Check if a price is greater than zero for an `AssetInstance`. 386 387 $(TYPEDSIGNATURES) 388 389 This function checks if a specified price `v` is greater than zero for an `AssetInstance`. The price is considered greater than zero if it is above the minimum limit minus a small epsilon value. 390 391 """ 392 gtxzero(ai::AssetInstance, v, ::Val{:price}) = gtxzero(v; atol=ai.limits.price.min + eps()) 393 @doc """ Check if a price is less than zero for an `AssetInstance`. 394 395 $(TYPEDSIGNATURES) 396 397 This function checks if a specified price `v` is less than zero for an `AssetInstance`. The price is considered less than zero if it is below the minimum limit minus a small epsilon value. 398 399 """ 400 ltxzero(ai::AssetInstance, v, ::Val{:price}) = ltxzero(v; atol=ai.limits.price.min + eps()) 401 @doc """ Check if a cost is greater than zero for an `AssetInstance`. 402 403 $(TYPEDSIGNATURES) 404 405 This function checks if a specified cost `v` is greater than zero for an `AssetInstance`. The cost is considered greater than zero if it is above the minimum limit minus a small epsilon value. 406 407 """ 408 gtxzero(ai::AssetInstance, v, ::Val{:cost}) = gtxzero(v; atol=ai.limits.cost.min + eps()) 409 @doc """ Check if a cost is less than zero for an `AssetInstance`. 410 411 $(TYPEDSIGNATURES) 412 413 This function checks if a specified cost `v` is less than zero for an `AssetInstance`. The cost is considered less than zero if it is below the minimum limit minus a small epsilon value. 414 415 """ 416 ltxzero(ai::AssetInstance, v, ::Val{:cost}) = ltxzero(v; atol=ai.limits.cost.min + eps()) 417 @doc """ Check if two amounts are approximately equal for an `AssetInstance`. 418 419 $(TYPEDSIGNATURES) 420 421 This function checks if two specified amounts `v1` and `v2` are approximately equal for an `AssetInstance`. It's used to validate whether two amounts are similar considering small variations. 422 423 """ 424 function Base.isapprox( 425 ai::AssetInstance, v1, v2, ::Val{:amount}; atol=ai.precision.amount + eps(DFT) 426 ) 427 isapprox(value(v1), value(v2); atol) 428 end 429 @doc """ Check if two prices are approximately equal for an `AssetInstance`. 430 431 $(TYPEDSIGNATURES) 432 433 This function checks if two specified prices `v1` and `v2` are approximately equal for an `AssetInstance`. It's used to validate whether two prices are similar considering small variations. 434 435 """ 436 function Base.isapprox( 437 ai::AssetInstance, v1, v2, ::Val{:price}; atol=ai.precision.price + eps(DFT) 438 ) 439 isapprox(value(v1), value(v2); atol) 440 end 441 442 function Base.isequal(ai::AssetInstance, v1, v2, kind::Val{:amount}) 443 isapprox(ai, v1, v2, kind; atol=ai.limits.amount.min - eps(DFT)) 444 end 445 446 function Base.isequal(ai::AssetInstance, v1, v2, kind::Val{:price}) 447 isapprox(ai, v1, v2, kind; atol=ai.limits.price.min - eps(DFT)) 448 end 449 450 @doc """ Create an `AssetInstance` from a zarr instance. 451 452 $(TYPEDSIGNATURES) 453 454 This function constructs an `AssetInstance` by loading data from a zarr instance and requires an external constructor defined in `Engine`. The `MarginMode` can be specified, with `NoMargin` being the default. 455 456 """ 457 function instance(exc::Exchange, a::AbstractAsset, m::MarginMode=NoMargin(); zi=zi) 458 data = Dict() 459 @assert a.raw ∈ keys(exc.markets) "Market $(a.raw) not found on exchange $(exc.name)." 460 for tf in config.timeframes 461 data[tf] = load(zi, exc.name, a.raw, string(tf)) 462 end 463 AssetInstance(a; data, exc, margin=m) 464 end 465 instance(a) = instance(exc, a) 466 467 @doc """ Load OHLCV data for an `AssetInstance`. 468 469 $(TYPEDSIGNATURES) 470 471 This function loads OHLCV (Open, High, Low, Close, Volume) data for a given `AssetInstance`. If `reset` is set to true, it will re-fetch the data even if it's already been loaded. 472 473 """ 474 function load!(ai::AssetInstance; reset=true, zi=zi) 475 for (tf, df) in ai.data 476 reset && empty!(df) 477 loaded = load(zi, ai.exchange.name, raw(ai), string(tf)) 478 append!(df, loaded) 479 end 480 end 481 Base.getproperty(ai::AssetInstance, f::Symbol) = begin 482 if f == :ohlcv 483 ohlcv(ai) 484 elseif f == :bc 485 ai.asset.bc 486 elseif f == :qc 487 ai.asset.qc 488 elseif f == :funding 489 metadata(ohlcv(ai), "funding") 490 elseif hasfield(AssetInstance, f) 491 getfield(ai, f) 492 else 493 attr(ai, f) 494 end 495 end 496 497 @doc " Get the parsed `AbstractAsset` of an `AssetInstance`. " 498 function asset(ai::AssetInstance) 499 getfield(ai, :asset) 500 end 501 502 @doc " Get the raw string id of an `AssetInstance`. " 503 function raw(ai::AssetInstance) 504 raw(asset(ai)) 505 end 506 507 @doc " Get the base currency of an `AssetInstance`. " 508 bc(ai::AssetInstance) = bc(asset(ai)) 509 @doc " Get the quote currency of an `AssetInstance`. " 510 qc(ai::AssetInstance) = qc(asset(ai)) 511 512 @doc """ Round a value based on the `precision` field of the `ai` asset instance. 513 514 $(TYPEDSIGNATURES) 515 516 This macro rounds a value `v` based on the `precision` field of an `AssetInstance`. By default, it rounds the `amount`, but it can also round other fields like `price` or `cost` if specified. 517 518 """ 519 macro _round(v, kind=:amount) 520 @assert kind isa Symbol 521 quote 522 toprecision( 523 $(esc(v)), getfield(getfield($(esc(esc(:ai))), :precision), $(QuoteNode(kind))) 524 ) 525 end 526 end 527 528 @doc """ Round a value based on the `precision` (price) field of the `ai` asset instance. 529 530 $(TYPEDSIGNATURES) 531 532 This macro rounds a price value `v` based on the `precision` field of an `AssetInstance`. 533 534 """ 535 macro rprice(v) 536 quote 537 $(@__MODULE__).@_round $(esc(v)) price 538 end 539 end 540 541 @doc """ Round a value based on the `precision` (amount) field of the `ai` asset instance. 542 543 $(TYPEDSIGNATURES) 544 545 This macro rounds an amount value `v` based on the `precision` field of an `AssetInstance`. 546 547 """ 548 macro ramount(v) 549 quote 550 $(@__MODULE__).@_round $(esc(v)) amount 551 end 552 end 553 554 @doc """ Get the last available candle strictly lower than `apply(tf, date)`. 555 556 $(TYPEDSIGNATURES) 557 558 This function retrieves the last available candle (Open, High, Low, Close, Volume data for a specific time period) from the `AssetInstance` that is strictly lower than the date adjusted by the `TimeFrame` `tf`. 559 560 """ 561 function Data.candlelast(ai::AssetInstance, tf::TimeFrame=first(keys(ohlcv_dict(ai))), args...) 562 Data.candlelast(ai.data[tf]) 563 end 564 565 function OrderTypes.Order(ai::AssetInstance, type; kwargs...) 566 Order(ai.asset, ai.exchange.id, type; kwargs...) 567 end 568 569 @doc """ Create a similar `AssetInstance` with cash and orders reset. 570 571 $(TYPEDSIGNATURES) 572 573 This function returns a similar `AssetInstance` to the one provided, but resets the cash and orders. The limits, precision, and fees can be specified, and will default to those of the original instance. 574 575 """ 576 function Base.similar( 577 ai::AssetInstance; 578 exc=ai.exchange, 579 limits=ai.limits, 580 precision=ai.precision, 581 fees=ai.fees, 582 ) 583 AssetInstance(ai.asset, ai.data, exc, marginmode(ai); limits, precision, fees) 584 end 585 586 @doc "Get the asset instance cash." 587 cash(ai::NoMarginInstance) = getfield(ai, :cash) 588 @doc "Get the asset instance cash for the long position." 589 cash(ai::NoMarginInstance, ::ByPos{Long}) = cash(ai) 590 @doc "Get the asset instance cash for the short position." 591 cash(ai::NoMarginInstance, ::ByPos{Short}) = 0.0 592 cash(ai::MarginInstance) = 593 let pos = position(ai) 594 isnothing(pos) && return nothing 595 getfield((pos), :cash) 596 end 597 cash(ai::MarginInstance, ::ByPos{Long}) = getfield(position(ai, Long()), :cash) 598 cash(ai::MarginInstance, ::ByPos{Short}) = getfield(position(ai, Short()), :cash) 599 @doc "Get the asset instance committed cash." 600 committed(ai::NoMarginInstance) = getfield(ai, :cash_committed) 601 committed(ai::NoMarginInstance, ::ByPos{Long}) = committed(ai) 602 committed(ai::NoMarginInstance, ::ByPos{Short}) = 0.0 603 function committed(ai::MarginInstance, ::ByPos{P}) where {P} 604 getfield(position(ai, P), :cash_committed) 605 end 606 committed(ai::MarginInstance) = getfield((@something position(ai) ai), :cash_committed) 607 @doc "Get the asset instance ohlcv data for the smallest time frame." 608 ohlcv(ai::AssetInstance) = getfield(first(getfield(ai, :data)), :second) 609 ohlcv(ai::AssetInstance, tf::TimeFrame) = getfield(ai, :data)[tf] 610 @doc "Get the asset instance ohlcv data dictionary." 611 ohlcv_dict(ai::AssetInstance) = getfield(ai, :data) 612 Instruments.add!(ai::NoMarginInstance, v, args...) = add!(cash(ai), v) 613 Instruments.add!(ai::MarginInstance, v, p::PositionSide) = add!(cash(ai, p), v) 614 Instruments.sub!(ai::NoMarginInstance, v, args...) = sub!(cash(ai), v) 615 Instruments.sub!(ai::MarginInstance, v, p::PositionSide) = sub!(cash(ai, p), v) 616 Instruments.cash!(ai::NoMarginInstance, v, args...) = cash!(cash(ai), v) 617 Instruments.cash!(ai::MarginInstance, v, p::PositionSide) = cash!(cash(ai, p), v) 618 # Positive `fees_base` go `trade --> exchange` 619 # Negative `fees_base` go `exchange --> trade` 620 # When updating a position t.amount must be fee adjusted if there are (positive) fees in base currency. 621 # We assume the amount field in a trade is always PRE fees. So 622 # - If the trade amount is 1 and fees are 0.01, the cash to add (sub) to the asset will be ±0.99 623 # - If the trade amount is 1 and fees are -0.01 (rebates), the cash to add (sub) to the asset will be ±1.01 624 @doc "The amount of a trade include fees (either positive or negative)." 625 amount_with_fees(amt, fb) = 626 if fb > 0.0 # trade --> exchange (the amount spent is the trade amount plus the base fees) 627 amt - fb 628 else # exchange --> trade (rebates, the amount spent is the trade amount minus the base fees (which we get back)) 629 amt + fb 630 end 631 amount_with_fees(t::Trade) = amount_with_fees(t.amount, t.fees_base) 632 function Instruments.cash!(ai::NoMarginInstance, t::BuyTrade) 633 amt = amount_with_fees(t) 634 add!(cash(ai), amt) 635 end 636 @doc """ Update the cash value for a `NoMarginInstance` after a `SellTrade`. 637 638 $(TYPEDSIGNATURES) 639 640 This function updates the cash value of a `NoMarginInstance` after a `SellTrade`. The cash value would typically increase after a sell trade, as assets are sold in exchange for cash. 641 642 """ 643 function Instruments.cash!(ai::NoMarginInstance, t::SellTrade) 644 amt = amount_with_fees(t) 645 add!(cash(ai), amt) 646 add!(committed(ai), amt) 647 end 648 @doc """ Update the cash value for a `MarginInstance` after an `IncreaseTrade`. 649 650 $(TYPEDSIGNATURES) 651 652 This function updates the cash value of a `MarginInstance` after an `IncreaseTrade`. The cash value would typically decrease after an increase trade, as assets are bought using cash. 653 654 """ 655 function Instruments.cash!(ai::MarginInstance, t::IncreaseTrade) 656 amt = amount_with_fees(t) 657 add!(cash(ai, positionside(t)()), amt) 658 end 659 @doc """ Update the cash value for a `MarginInstance` after a `ReduceTrade`. 660 661 $(TYPEDSIGNATURES) 662 663 This function updates the cash value of a `MarginInstance` after a `ReduceTrade`. The cash value would typically increase after a reduce trade, as assets are sold in exchange for cash. 664 665 """ 666 function Instruments.cash!(ai::MarginInstance, t::ReduceTrade) 667 amt = amount_with_fees(t) 668 add!(cash(ai, positionside(t)()), amt) 669 add!(committed(ai, positionside(t)()), amt) 670 end 671 @doc """ Calculate the free cash for a `NoMarginInstance`. 672 673 $(TYPEDSIGNATURES) 674 675 This function calculates the free cash (cash that is not tied up in trades) of a `NoMarginInstance`. It takes into account the current cash, open orders, and any additional factors specified in `args`. 676 677 """ 678 function freecash(ai::NoMarginInstance, args...) 679 ca = cash(ai) - committed(ai) 680 @deassert ca |> gtxzero (cash(ai), committed(ai)) 681 ca 682 end 683 @doc """ Calculate the free cash for a `MarginInstance` with long position. 684 685 $(TYPEDSIGNATURES) 686 687 This function calculates the free cash (cash that is not tied up in trades) of a `MarginInstance` that has a long position. It takes into account the current cash, open long positions, and the margin requirements for those positions. 688 689 """ 690 function freecash(ai::MarginInstance, p::ByPos{Long}) 691 @deassert cash(ai, p) |> gtxzero 692 @deassert committed(ai, p) |> gtxzero 693 ca = max(0.0, cash(ai, p) - committed(ai, p)) 694 @deassert ca |> gtxzero (cash(ai, p), committed(ai, p)) 695 ca 696 end 697 @doc """ Calculate the free cash for a `MarginInstance` with short position. 698 699 $(TYPEDSIGNATURES) 700 701 This function calculates the free cash (cash that is not tied up in trades) of a `MarginInstance` that has a short position. It takes into account the current cash, open short positions, and the margin requirements for those positions. 702 703 """ 704 function freecash(ai::MarginInstance, p::ByPos{Short}) 705 @deassert cash(ai, p) |> ltxzero 706 @deassert committed(ai, p) |> ltxzero 707 ca = min(0.0, cash(ai, p) - committed(ai, p)) 708 @deassert ca |> ltxzero (cash(ai, p), committed(ai, p)) 709 ca 710 end 711 _reset!(ai) = begin 712 empty!(ai.history) 713 ai.lastpos[] = nothing 714 end 715 @doc """ Resets asset cash and commitments for a `NoMarginInstance`. 716 717 $(TYPEDSIGNATURES) 718 719 This function resets the cash and commitments (open trades) of a `NoMarginInstance` to initial values. Any additional arguments in `args` are used to adjust the reset process, if necessary. 720 721 """ 722 reset!(ai::NoMarginInstance, args...) = begin 723 cash!(ai, 0.0) 724 cash!(committed(ai), 0.0) 725 _reset!(ai) 726 end 727 @doc """ Resets asset positions for a `MarginInstance`. 728 729 $(TYPEDSIGNATURES) 730 731 This function resets the positions (open trades) of a `MarginInstance` to initial values. Any additional arguments in `args` are used to adjust the reset process, if necessary. 732 733 """ 734 reset!(ai::MarginInstance, args...) = begin 735 reset!(position(ai, Short()), args...) 736 reset!(position(ai, Long()), args...) 737 _reset!(ai) 738 end 739 740 reset!(ai::MarginInstance, p::PositionSide) = begin 741 reset!(position(ai, p)) 742 let sop = position(ai, opposite(p)) 743 if isopen(sop) 744 ai.lastpos[] = sop 745 else 746 ai.lastpos[] = nothing 747 end 748 end 749 end 750 Data.DFUtils.firstdate(ai::AssetInstance) = begin 751 df = ohlcv(ai) 752 isempty(df) ? DateTime(0) : first(df.timestamp) 753 end 754 Data.DFUtils.lastdate(ai::AssetInstance) = begin 755 df = ohlcv(ai) 756 isempty(df) ? DateTime(0) : last(df.timestamp) 757 end 758 759 function Base.print(io::IO, ai::NoMarginInstance) 760 write(io, raw(ai), "~[", compactnum(ai.cash.value), "]{", ai.exchange.name, "}") 761 end 762 function Base.print(io::IO, ai::MarginInstance) 763 long = compactnum(cash(ai, Long()).value) 764 short = compactnum(cash(ai, Short()).value) 765 write(io, "[\"", raw(ai), "\"][L:", long, "/S:", short, "][", ai.exchange.name, "]") 766 end 767 Base.show(io::IO, ::MIME"text/plain", ai::AssetInstance) = print(io, ai) 768 Base.show(io::IO, ai::AssetInstance) = print(io, "\"", raw(ai), "\"") 769 770 @doc """ Stub data for an `AssetInstance` with a `DataFrame`. 771 772 $(TYPEDSIGNATURES) 773 774 This function stabs data of an `AssetInstance` with a given `DataFrame`. It's used for testing or simulating scenarios with pre-defined data. 775 776 """ 777 stub!(ai::AssetInstance, df::DataFrame) = begin 778 tf = timeframe!(df) 779 ai.data[tf] = df 780 end 781 @doc """ Calculate the value of a `NoMarginInstance`. 782 783 $(TYPEDSIGNATURES) 784 785 This function calculates the value of a `NoMarginInstance`. It uses the current price (defaulting to the last historical price), the cash in the instance and the maximum fees. The value represents the amount of cash that could be obtained by liquidating the instance at the current price, taking into account the fees. 786 787 """ 788 function value( 789 ai::NoMarginInstance; 790 current_price=lastprice(ai, Val(:history)), 791 fees=current_price * cash(ai) * maxfees(ai), 792 ) 793 cash(ai) * current_price - fees 794 end 795 @doc "Taker fees for the asset instance (usually higher than maker fees.)" 796 takerfees(ai::AssetInstance) = ai.fees.taker 797 @doc "Maker fees for the asset instance (usually lower than taker fees.)" 798 makerfees(ai::AssetInstance) = ai.fees.maker 799 @doc "The minimum fees for trading in the asset market (usually the highest vip level.)" 800 minfees(ai::AssetInstance) = ai.fees.min 801 @doc "The maximum fees for trading in the asset market (usually the lowest vip level.)" 802 maxfees(ai::AssetInstance) = ai.fees.max 803 @doc "ExchangeID for the asset instance." 804 exchangeid(::AssetInstance{<:AbstractAsset,E}) where {E<:ExchangeID} = E 805 @doc "The exchange of the asset instance." 806 exchange(ai::AssetInstance) = getfield(ai, :exchange) 807 @doc "Asset instance long position." 808 position(ai::MarginInstance, ::ByPos{Long}) = getfield(ai, :longpos) 809 @doc "Asset instance short position." 810 position(ai::MarginInstance, ::ByPos{Short}) = getfield(ai, :shortpos) 811 @doc "Asset position by order." 812 position(ai::MarginInstance, ::ByPos{S}) where {S<:PositionSide} = position(ai, S) 813 @doc "Returns the last open asset position or nothing." 814 position(ai::MarginInstance) = getfield(ai, :lastpos)[] 815 @doc "Get the trade history of an `AssetInstance`." 816 trades(ai::AssetInstance) = getfield(ai, :history) 817 _history_timestamp(ai) = 818 let history = trades(ai) 819 if isempty(history) 820 DateTime(0) 821 else 822 last(history).date 823 end 824 end 825 @doc "Get the timestamp of the last trade." 826 timestamp(ai::NoMarginInstance, _=nothing) = _history_timestamp(ai) 827 timestamp(::MarginInstance, ::Nothing) = DateTime(0) 828 function timestamp(ai::MarginInstance, ::ByPos{P}=posside(ai)) where {P} 829 pos = position(ai, P()) 830 if isnothing(pos) 831 _history_timestamp(ai) 832 else 833 timestamp(pos) 834 end 835 end 836 @doc "Check if an asset position is open." 837 function isopen(ai::MarginInstance, ::Union{Type{S},S,Position{S}}) where {S<:PositionSide} 838 isopen(position(ai, S)) 839 end 840 @doc "Asset position notional value." 841 function notional(ai::MarginInstance, ::ByPos{S}) where {S<:PositionSide} 842 position(ai, S) |> notional 843 end 844 @doc "Asset entry price. 845 846 $(TYPEDSIGNATURES) 847 " 848 function price(ai::MarginInstance, fromprice, ::ByPos{S}) where {S<:PositionSide} 849 v = position(ai, S) |> price 850 ifelse(iszero(v), fromprice, v) 851 end 852 @doc "Asset entry price." 853 entryprice(ai::MarginInstance, fromprice, pos::ByPos) = price(ai, fromprice, pos) 854 @doc "Asset entry price. 855 856 $(TYPEDSIGNATURES) 857 " 858 price(::NoMarginInstance, fromprice, args...) = fromprice 859 @doc "Asset position liquidation price." 860 function liqprice(ai::MarginInstance, ::ByPos{S}) where {S<:PositionSide} 861 position(ai, S) |> liqprice 862 end 863 @doc "Sets asset position liquidation price. 864 865 $(TYPEDSIGNATURES) 866 " 867 function liqprice!(ai::MarginInstance, v, ::ByPos{S}) where {S<:PositionSide} 868 liqprice!(position(ai, S), v) 869 end 870 @doc "Asset position leverage." 871 function leverage(ai::MarginInstance, ::ByPos{S}=posside(ai)) where {S<:PositionSide} 872 position(ai, S) |> leverage 873 end 874 leverage(::MarginInstance, ::Nothing) = 1.0 875 leverage(::NoMarginInstance, args...) = 1.0 876 @doc "Asset position status (open or closed)." 877 function status(ai::MarginInstance, ::ByPos{S}) where {S<:PositionSide} 878 position(ai, S) |> status 879 end 880 @doc "Asset position maintenance margin." 881 function maintenance(ai::MarginInstance, ::ByPos{S}) where {S<:PositionSide} 882 position(ai, S) |> maintenance 883 end 884 @doc "Asset position initial margin." 885 function margin(ai::MarginInstance, ::ByPos{S}) where {S<:PositionSide} 886 position(ai, S) |> margin 887 end 888 @doc "Asset position additional margin." 889 function additional(ai::MarginInstance, ::ByPos{S}) where {S<:PositionSide} 890 position(ai, S) |> additional 891 end 892 @doc """ Get the position tier for a `MarginInstance`. 893 894 $(TYPEDSIGNATURES) 895 896 This function returns the tier of the position for a `MarginInstance` for a given size and position side (`Long` or `Short`). The tier indicates the level of risk or capital requirement for the position. 897 898 """ 899 function tier(ai::MarginInstance, size, ::ByPos{S}) where {S<:PositionSide} 900 tier(position(ai, S), size) 901 end 902 @doc """ Get the maintenance margin rate for a `MarginInstance`. 903 904 $(TYPEDSIGNATURES) 905 906 This function returns the maintenance margin rate for a `MarginInstance` for a given size and position side (`Long` or `Short`). The maintenance margin rate is the minimum amount of equity that must be maintained in a margin account. 907 908 """ 909 function mmr(ai::MarginInstance, size, s::ByPos) 910 mmr(position(ai, s), size) 911 end 912 @doc """ Get the bankruptcy price for an asset position. 913 914 $(TYPEDSIGNATURES) 915 916 This function calculates the bankruptcy price, which is the price at which the asset position would be fully liquidated. It takes into account the current price of the asset and the position side (`Long` or `Short`). 917 918 """ 919 function bankruptcy(ai, price, ps::ByPos{P}) where {P<:PositionSide} 920 bankruptcy(position(ai, ps), price) 921 end 922 function bankruptcy(ai, o::Order{T,A,E,P}) where {T,A,E,P<:PositionSide} 923 bankruptcy(ai, o.price, P()) 924 end 925 926 @doc """ Update the leverage for an asset position. 927 928 $(TYPEDSIGNATURES) 929 930 This function updates the leverage for a position in an asset instance. Leverage is the use of various financial instruments or borrowed capital to increase the potential return of an investment. The function takes a leverage value `v` and a position side (`Long` or `Short`) as inputs. 931 932 """ 933 function leverage!(ai, v, p::PositionSide) 934 po = position(ai, p) 935 leverage!(po, v) 936 # ensure leverage tiers and limits agree 937 @deassert leverage(po) <= ai.limits.leverage.max 938 end 939 940 @doc """ Set the leverage to maximum for a `CrossInstance`. 941 942 $(TYPEDSIGNATURES) 943 944 This function sets the leverage for a `CrossInstance` to the maximum value for the current tier. Some exchanges interpret a leverage value of 0 as max leverage in cross margin mode. This means that the maximum amount of borrowed capital will be used to increase the potential return of the investment. 945 946 """ 947 function leverage!(ai::CrossInstance, p::PositionSide, ::Val{:max}) 948 po = position(ai, p) 949 po.leverage[] = 0.0 950 end 951 952 @doc "The opposite position w.r.t. the asset instance and another `Position` or `PositionSide`." 953 function opposite(ai::MarginInstance, ::Union{P,Position{P}}) where {P} 954 position(ai, opposite(P)) 955 end 956 957 function _lastpos!(ai::MarginInstance, p::PositionSide, ::PositionClose) 958 sop = position(ai, opposite(p)) 959 isopen(sop) && (ai.lastpos[] = sop) 960 end 961 962 function _lastpos!(ai::MarginInstance, p::PositionSide, ::PositionOpen) 963 ai.lastpos[] = position(ai, p) 964 end 965 966 @doc """ Update the status of a hedged position in a `HedgedInstance`. 967 968 $(TYPEDSIGNATURES) 969 970 This function opens or closes the status of a hedged position in a `HedgedInstance`. A hedged position is a position that is offset by a corresponding position in a related commodity or security. The `PositionSide` and `PositionStatus` are provided as inputs. 971 972 """ 973 function status!(ai::HedgedInstance, p::PositionSide, pstat::PositionStatus) 974 pos = position(ai, p) 975 _status!(pos, pstat) 976 _lastpos!(ai, p, pstat) 977 end 978 979 @doc """ Update the status of a non-hedged position in a `MarginInstance`. 980 981 $(TYPEDSIGNATURES) 982 983 This function opens or closes the status of a non-hedged position in a `MarginInstance`. A non-hedged position is a position that is not offset by a corresponding position in a related commodity or security. The `PositionSide` and `PositionStatus` are provided as inputs. 984 985 """ 986 function status!(ai::MarginInstance, p::PositionSide, pstat::PositionStatus) 987 pos = position(ai, p) 988 opp = opposite(ai, p) 989 # HACK: the `!iszero` check is needed because in SimMode the `NewTrade` call! in `_update_from_trade!` can trigger aditional trades 990 if pstat == PositionOpen() && status(opp) == PositionOpen() && !iszero(cash(opp)) 991 @error "double position in non hedged mode" ai.longpos ai.shortpos 992 error() 993 end 994 _status!(pos, pstat) 995 _lastpos!(ai, p, pstat) 996 end 997 998 value(v::Real, args...; kwargs...) = v 999 @doc """ Calculate the value of a `MarginInstance`. 1000 1001 $(TYPEDSIGNATURES) 1002 1003 This function calculates the value of a `MarginInstance`. It takes into account the current price (defaulting to the price of the position), the cash in the position and the maximum fees. The value represents the amount of cash that could be obtained by liquidating the position at the current price, taking into account the fees. 1004 1005 """ 1006 function value( 1007 ai::MarginInstance, 1008 ::ByPos{P}=posside(ai); 1009 current_price=price(position(ai, P)), 1010 fees=current_price * abs(cash(ai, P)) * maxfees(ai), 1011 ) where {P} 1012 pos = position(ai, P) 1013 @deassert margin(pos) > 0.0 || !isopen(pos) 1014 @deassert additional(pos) >= 0.0 1015 margin(pos) + additional(pos) + pnl(pos, current_price) - fees 1016 end 1017 1018 @doc """ Calculate the profit and loss (PnL) of an asset position. 1019 1020 $(TYPEDSIGNATURES) 1021 1022 This function calculates the profit and loss (PnL) for an asset position. It takes into account the current price and the position. The PnL represents the gain or loss made on the position, based on the current price compared to the price at which the position was opened. 1023 1024 """ 1025 function pnl(ai, ::ByPos{P}, price) where {P} 1026 pos = position(ai, P) 1027 isnothing(pos) && return 0.0 1028 pnl(pos, price) 1029 end 1030 1031 @doc """ Calculate the profit and loss percentage (PnL%) of an asset position. 1032 1033 $(TYPEDSIGNATURES) 1034 1035 This function calculates the profit and loss percentage (PnL%) for an asset position in a `MarginInstance`. It takes into account the current price and the position. The PnL% represents the gain or loss made on the position, as a percentage of the investment, based on the current price compared to the price at which the position was opened. 1036 1037 """ 1038 function pnlpct(ai::MarginInstance, ::ByPos{P}, price; pos=position(ai, P)) where {P} 1039 isnothing(pos) && return 0.0 1040 pnlpct(pos, price) 1041 end 1042 pnlpct(ai::MarginInstance, v::Number) = begin 1043 pos = position(ai) 1044 isnothing(pos) && return 0.0 1045 pnlpct(pos, v) 1046 end 1047 1048 @doc """ Get the last price for an `AssetInstance`. 1049 1050 $(TYPEDSIGNATURES) 1051 1052 This function returns the last known price for an `AssetInstance`. Additional arguments and keyword arguments can be provided to adjust the way the last price is calculated, if necessary. 1053 1054 """ 1055 function lastprice(ai::AssetInstance, args...; hist=false, kwargs...) 1056 exc = ai.exchange 1057 tickers = @tickers! markettype(exc, marginmode(ai)) false TICKERS_CACHE10 1058 tick = get(tickers, raw(ai), nothing) 1059 this_args = if isnothing(tick) 1060 if hist 1061 (ai, Val(:history)) 1062 else 1063 (raw(ai), exc) 1064 end 1065 else 1066 (exc, tick) 1067 end 1068 lastprice(this_args...) 1069 end 1070 @doc """ Get the last price from the history for an `AssetInstance`. 1071 1072 $(TYPEDSIGNATURES) 1073 1074 This function returns the last known price from the historical data for an `AssetInstance`. It's useful when you need to reference the most recent historical price for calculations or comparisons. 1075 1076 """ 1077 function lastprice(ai::AssetInstance, ::Val{:history}) 1078 v = ai.history 1079 if length(v) > 0 1080 last(v).price 1081 else 1082 lastprice(ai; hist=true) 1083 end 1084 end 1085 1086 function lastprice(ai::AssetInstance, date::DateTime) 1087 h = trades(ai) 1088 if length(h) > 0 1089 trade = last(h) 1090 if date >= trade.date 1091 return trade.price 1092 end 1093 end 1094 lastprice(ai) 1095 end 1096 1097 @doc """ Get the timeframe for an `AssetInstance`. 1098 1099 $(TYPEDSIGNATURES) 1100 1101 This function returns the timeframe for an `AssetInstance`. The timeframe represents the interval at which the asset's price data is sampled or updated. 1102 1103 """ 1104 function timeframe(ai::AssetInstance) 1105 data = getfield(ai, :data) 1106 if length(data) > 0 1107 first(keys(data)) 1108 else 1109 @warn "asset: can't infer timeframe since there is not data" 1110 tf"1m" 1111 end 1112 end 1113 1114 include("constructors.jl") 1115 1116 export AssetInstance, instance, load!, @rprice, @ramount 1117 export asset, raw, ohlcv, ohlcv_dict, bc, qc, default_asset_df 1118 export takerfees, makerfees, maxfees, minfees, ishedged, isdust, nondust 1119 export Long, Short, position, posside, cash, committed 1120 export liqprice, leverage, bankruptcy, entryprice, price 1121 export additional, margin, maintenance 1122 export leverage, mmr, status!