/ Instances / src / positions.jl
positions.jl
  1  using .Misc: MarginMode, WithMargin, Long, Short, PositionSide, ExecAction, HedgedMode
  2  import .Misc: opposite, reset!, Misc
  3  using .Instruments.Derivatives: Derivative
  4  using Exchanges: LeverageTier, LeverageTiersDict, leverage_tiers, tier
  5  import Exchanges: maxleverage, tier
  6  using .Lang: @ifdebug
  7  using Base: negate
  8  import OrderTypes: isshort, islong, commit!, PositionUpdated, LeverageUpdated, MarginUpdated
  9  import .Misc: marginmode
 10  import .Instruments: cash!
 11  
 12  @doc "A constant representing a vector of `DFT` type."
 13  const OneVec = Vector{DFT}
 14  
 15  @doc "A position has been opened."
 16  struct PositionOpen <: ExecAction end
 17  @doc "A position has been updated."
 18  struct PositionUpdate <: ExecAction end
 19  @doc "A position has been closed."
 20  struct PositionClose <: ExecAction end
 21  @doc "Position status is one of `PositionOpen`, `PositionClose`."
 22  const PositionStatus = Union{PositionOpen,PositionClose}
 23  @doc "Position change is one of `PositionOpen`, `PositionUpdate`, `PositionClose`."
 24  const PositionChange = Union{PositionOpen,PositionUpdate,PositionClose}
 25  opposite(::PositionOpen) = PositionClose()
 26  opposite(::PositionClose) = PositionOpen()
 27  
 28  @doc """ A position tracks the margin state of an asset instance.
 29  
 30  $(FIELDS)
 31  """
 32  @kwdef struct Position{P<:PositionSide,E<:ExchangeID,M<:MarginMode}
 33      "Current status of the position"
 34      status::Vector{PositionStatus} = [PositionClose()]
 35      "Asset being tracked"
 36      asset::Derivative
 37      "Timestamp of the last update"
 38      timestamp::Vector{DateTime} = [DateTime(0)]
 39      "Asset liquidation price"
 40      liquidation_price::OneVec = [0.0]
 41      "Price at which the position was entered"
 42      entryprice::OneVec = [0.0]
 43      "Maintenance margin required for the position"
 44      maintenance_margin::OneVec = [0.0]
 45      "Initial margin required for the position"
 46      initial_margin::OneVec = [0.0]
 47      "Additional margin required for the position"
 48      additional_margin::OneVec = [0.0]
 49      "Notional value of the position"
 50      notional::OneVec = [0.0]
 51      "Cash value of the position"
 52      cash::CCash{E}{S1} where {S1}
 53      "Cash committed to the position"
 54      cash_committed::CCash{E}{S2} where {S2}
 55      "Leverage applied to the position"
 56      leverage::OneVec = [1.0]
 57      "Minimum size of the position"
 58      min_size::T where {T<:Real}
 59      "Whether the position is hedged or not"
 60      hedged::Bool = false
 61      "Leverage tiers applicable to the position"
 62      tiers::Vector{LeverageTiersDict}
 63      "Current tier applicable to the position"
 64      this_tier::Vector{LeverageTier}
 65      function Position{P,E,M}(
 66          args...; kwargs...
 67      ) where {P<:PositionSide,E<:ExchangeID,M<:MarginMode}
 68          M == NoMargin && error("Trying to construct a position in `NoMargin` mode")
 69          new{P,E,M}(args...; kwargs...)
 70      end
 71  end
 72  
 73  
 74  exchangeid(::Position{<:PositionSide,E}) where {E<:ExchangeID} = E
 75  
 76  @doc """ Resets position to initial state.
 77  
 78  !!! warning "Also resets leverage"
 79    When reopening a position, leverage should be set again.
 80  
 81  """
 82  reset!(po::Position, ::Val{:full}) = begin
 83      po.status[] = PositionClose()
 84      po.timestamp[] = DateTime(0)
 85      po.notional[] = 0.0
 86      leverage!(po, 1.0)
 87      tier!(po)
 88      po.liquidation_price[] = 0.0
 89      entryprice!(po, 0.0)
 90      maintenance!(po, 0.0)
 91      margin!(po)
 92      additional!(po)
 93      cash!(cash(po), 0.0)
 94      cash!(committed(po), 0.0)
 95  end
 96  
 97  @doc """ Resets the bare fields to close a position.
 98  
 99  $(TYPEDSIGNATURES)
100  
101  """
102  reset!(po::Position) = begin
103      po.status[] = PositionClose()
104      po.notional[] = 0.0
105      po.liquidation_price[] = 0.0
106      entryprice!(po, 0.0)
107      maintenance!(po, 0.0)
108      margin!(po)
109      additional!(po)
110      cash!(cash(po), 0.0)
111      cash!(committed(po), 0.0)
112  end
113  
114  @doc "A constant representing a long position with margin in a specific exchange."
115  const LongPosition{E<:ExchangeID,M<:WithMargin} = Position{Long,E,M}
116  @doc "A constant representing a short position with margin in a specific exchange."
117  const ShortPosition{E<:ExchangeID,M<:WithMargin} = Position{Short,E,M}
118  
119  @doc "The number of digits to keep for margin calculations."
120  const POSITION_PRECISION = 4
121  @doc "The number of digits allowed for leverage values."
122  const LEVERAGE_PRECISION = 2
123  @doc "A constant defining the rounding mode for positions as `RoundToZero`."
124  const POSITION_ROUNDING_MODE = RoundToZero
125  
126  @doc """ Round function for values of position fields.
127  
128  $(TYPEDSIGNATURES)
129  
130  This function rounds the values of position fields to a specified precision. The default precision is `POSITION_PRECISION`.
131  
132  """
133  function _roundpos(v, digits=POSITION_PRECISION)
134      round(v, POSITION_ROUNDING_MODE; digits)
135  end
136  
137  _roundlev(po, lev) = _roundpos(clamp(lev, 1.0, maxleverage(po)), LEVERAGE_PRECISION)
138  
139  @doc "Updates position leverage."
140  function leverage!(po::Position, v)
141      @deassert maxleverage(po) == maxleverage(po, notional(po))
142      po.leverage[] = _roundlev(po, v)
143  end
144  
145  @doc """ Returns the maximum leverage for a given position and size.
146  
147  $(TYPEDSIGNATURES)
148  
149  The function retrieves the leverage tier applicable to the provided position and size, and returns the maximum leverage allowed within that tier.
150  
151  """
152  maxleverage(po::Position, size::Real) = tier(po, size)[2].max_leverage
153  maxleverage(po::Position) = po.this_tier[].max_leverage
154  
155  _status!(po::Position, ::PositionClose) = begin
156      @assert po.status[] == PositionOpen()
157      po.status[] = PositionClose()
158  end
159  
160  _status!(po::Position, ::PositionOpen) = begin
161      @assert po.status[] == PositionClose()
162      po.status[] = PositionOpen()
163  end
164  
165  isopen(po::Position) = po.status[] == PositionOpen()
166  islong(::Position{Long}) = true
167  islong(::Position{Short}) = false
168  islong(::Union{Type{Long},Long}) = true
169  islong(::Union{Type{Short},Short}) = false
170  islong(::Missing) = false
171  isshort(::Position{Short}) = true
172  isshort(::Position{Long}) = false
173  isshort(::Union{Type{Short},Short}) = true
174  isshort(::Union{Type{Long},Long}) = false
175  isshort(::Missing) = false
176  Misc.marginmode(::Position{<:PositionSide,<:ExchangeID,M}) where {M<:MarginMode} = M()
177  function ishedged(::Position{<:PositionSide,<:ExchangeID,M}) where {M<:MarginMode}
178      ishedged(M())
179  end
180  @doc """ Retrieves the leverage tier for a given position and size.
181  
182  $(TYPEDSIGNATURES)
183  
184  This function returns the tier that applies to a position of the provided size.
185  
186  """
187  function tier(po::Position, size)
188      # tier should work with abs values
189      @deassert tier(po.tiers[], po.cash.value) == tier(po.tiers[], negate(po.cash.value))
190      tier(po.tiers[], size)
191  end
192  posside(::Position{P}) where {P<:PositionSide} = P()
193  posside(::ByPos{P}) where {P<:PositionSide} = P()
194  function posside(::Type{<:Order{<:O,<:A,<:E,P}}) where {O,A,E,P<:PositionSide}
195      P()
196  end
197  
198  @doc "Position entryprice."
199  price(po::Position) = po.entryprice[]
200  entryprice(po::Position) = price(po)
201  @doc "Position liquidation price."
202  liqprice(pos::Position) = pos.liquidation_price[]
203  @doc "Position leverage."
204  leverage(pos::Position) = pos.leverage[]
205  @doc "Position status (open or closed)."
206  status(pos::Position) = pos.status[]
207  @doc "Position maintenance margin."
208  maintenance(pos::Position) = pos.maintenance_margin[]
209  @doc "Position initial margin (includes additional)."
210  margin(pos::Position) = pos.initial_margin[]
211  initial(args...; kwargs...) = margin(args...; kwargs...)
212  @doc "Position additional margin."
213  additional(pos::Position) = pos.additional_margin[]
214  @doc "Position maintenance margin rate."
215  mmr(pos::Position) = pos.this_tier[].mmr
216  @doc "Position notional value."
217  notional(pos::Position) = pos.notional[]
218  @doc "Held position."
219  cash(po::Position) = po.cash
220  @doc "Position locked in pending orders."
221  committed(po::Position) = po.cash_committed
222  @doc "Maximum value that can be lost by the position"
223  collateral(po::Position) = margin(po) + additional(po)
224  @doc "Last position update time"
225  timestamp(po::Position) = po.timestamp[]
226  
227  @doc """ The price where the position is fully liquidated.
228  
229  $(TYPEDSIGNATURES)
230  
231  This function calculates and returns the price at which a position, given its leverage (`lev`), would be fully liquidated.
232  
233  """
234  function bankruptcy(price::Real, lev::Real, ::Long)
235      @deassert lev != 0.0
236      price * (lev - 1.0) / lev
237  end
238  function bankruptcy(price::Real, lev::Real, ::Short)
239      @deassert lev != 0.0
240      price * (lev + 1.0) / lev
241  end
242  function bankruptcy(pos::Position{P}, price) where P<:PositionSide
243      lev = leverage(pos)
244      bankruptcy(price, lev, P())
245  end
246  function bankruptcy(pos::Position, o::Order{T,A,E,P}) where {T,A,E,P<:PositionSide}
247      bankruptcy(pos, o.price)
248  end
249  
250  @doc """ Updates the timestamp of a position.
251  
252  $(TYPEDSIGNATURES)
253  
254  This function sets the timestamp of a given position (`po`) to the provided DateTime value (`d`).
255  
256  """
257  function timestamp!(po::Position, d::DateTime)
258      @deassert po.timestamp[] <= d "Position dates can only go forward.($(po.timestamp[]), $d)"
259      po.timestamp[] = d
260  end
261  
262  @doc """ Updates position leverage tier according to size.
263  
264  $(TYPEDSIGNATURES)
265  
266  This function adjusts the leverage tier of a given position (`po`) based on the provided size. If no size is provided, the notional value of the position is used.
267  
268  """
269  function tier!(po::Position, size=notional(po))
270      po.this_tier[] = tier(po, size)[2]
271  end
272  
273  @doc "Update the entry price.
274  
275  $(TYPEDSIGNATURES)
276  "
277  function entryprice!(po::Position, v=abs(notional(po) / cash(po)))
278      po.entryprice[] = v
279  end
280  
281  @doc """ Update the notional value.
282  
283  $(TYPEDSIGNATURES)
284  
285  This function updates the notional value of a given position (`po`) to the provided value (`v`).
286  
287  """
288  function notional!(po::Position, v)
289      @ifdebug v < 0.0 && @warn "Notional value should never be negative ($v)"
290      po.notional[] = abs(v)
291      # update leverage tier after notional update
292      tier!(po)
293      v
294  end
295  
296  @doc """ Sets initial margin given notional and leverage values.
297  
298  $(TYPEDSIGNATURES)
299  
300  This function sets the initial margin of a given position (`po`) based on the provided notional value (`ntl`) and leverage (`lev`). If no values are provided, the current notional value and leverage of the position are used.
301  
302  """
303  function margin!(po::Position; ntl=notional(po), lev=leverage(po))
304      m = _roundpos(ntl / lev)
305      @deassert m <= notional(po)
306      po.initial_margin[] = m
307  end
308  
309  @doc """ Sets initial margin (should always be positive).
310  
311  $(TYPEDSIGNATURES)
312  
313  This function sets the initial margin of a given position (`po`) to the provided value (`v`). If no value is provided, it defaults to 0.0.
314  
315  """
316  function initial!(po::Position, v=0.0)
317      po.initial_margin[] = abs(v) |> _roundpos
318  end
319  
320  @doc """ Sets additional margin (should always be positive).
321  
322  $(TYPEDSIGNATURES)
323  
324  This function sets the additional margin of a given position (`po`) to the provided value (`v`). If no value is provided, it defaults to 0.0.
325  
326  """
327  function additional!(po::Position, v=0.0)
328      @deassert v + margin(po) <= notional(po)
329      po.additional_margin[] = abs(v) |> _roundpos
330  end
331  
332  @doc """ Adds margin to a position.
333  
334  $(TYPEDSIGNATURES)
335  
336  This function adds a specified amount (`v`) to the margin of a given position (`po`).
337  
338  """
339  function addmargin!(po::Position, v)
340      @deassert margin(po) + v <= notional(po)
341      po.additional_margin[] = max(0.0, v + additional(po)) |> _roundpos
342  end
343  
344  @doc """ Sets maintenance margin.
345  
346  $(TYPEDSIGNATURES)
347  
348  This function sets the maintenance margin of a given position (`po`) to the provided value (`v`).
349  
350  """
351  function maintenance!(po::Position, v)
352      po.maintenance_margin[] = _roundpos(v)
353      @deassert maintenance(po) <= margin(po)
354      v
355  end
356  
357  @doc "Set position cash value."
358  Instruments.cash!(po::Position, v) = cash!(cash(po), v)
359  @doc "Set position committed cash value."
360  OrderTypes.commit!(po::Position, v) = cash!(committed(po), v)
361  
362  @doc """ Calc PNL for long position given `current_price` as input.
363  
364  $(TYPEDSIGNATURES)
365  
366  This function calculates the Profit and Loss (PNL) for a long position (`po`), given the current price (`current_price`) and an optional amount (`amount`). If no amount is provided, the cash value of the position is used.
367  
368  """
369  function pnl(po::Position{Long}, current_price, amount=cash(po))
370      @deassert notional(po) ~= abs(cash(po)) * price(po)
371      isopen(po) || return 0.0
372      pnl(price(po), current_price, amount, Long())
373  end
374  
375  @doc """ Calc PNL for short position given `current_price` as input.
376  
377  $(TYPEDSIGNATURES)
378  
379  This function calculates the Profit and Loss (PNL) for a short position (`po`), given the current price (`current_price`) and an optional amount (`amount`). If no amount is provided, the cash value of the position is used.
380  
381  """
382  function pnl(po::Position{Short}, current_price, amount=cash(po))
383      isopen(po) || return 0.0
384      pnl(price(po), current_price, amount, Short())
385  end
386  
387  @doc """ Calc PNL percentage.
388  
389  $(TYPEDSIGNATURES)
390  
391  This function calculates the Profit and Loss (PNL) percentage for a given position (`po`) and value (`v`).
392  
393  """
394  pnlpct(po::Position, v) = begin
395      isopen(po) || return 0.0
396      pnl(po, v, 1.0) / price(po)
397  end
398  
399  @doc """ Calculate PNL for a long position.
400  
401  $(TYPEDSIGNATURES)
402  
403  This function calculates the Profit and Loss (PNL) for a long position, given the entry price (`entryprice`), the current price (`current_price`), and the amount.
404  
405  """
406  function pnl(entryprice::T, current_price::T, amount, ::ByPos{Long}) where {T}
407      (current_price - entryprice) * abs(amount)
408  end
409  
410  @doc """ Calculate PNL for a short position.
411  
412  $(TYPEDSIGNATURES)
413  
414  This function calculates the Profit and Loss (PNL) for a short position, given the entry price (`entryprice`), the current price (`current_price`), and the amount.
415  
416  """
417  function pnl(entryprice::T, current_price::T, amount, ::ByPos{Short}) where {T}
418      (entryprice - current_price) * abs(amount)
419  end
420  
421  @doc """ Sets the liquidation price for a long position.
422  
423  $(TYPEDSIGNATURES)
424  
425  This function sets the liquidation price of a given long position (`po`) to the provided value (`v`).
426  
427  """
428  function liqprice!(po::Position{Long}, v)
429      @deassert v <= price(po)
430      po.liquidation_price[] = v
431  end
432  
433  @doc """ Sets the liquidation price for a short position.
434  
435  $(TYPEDSIGNATURES)
436  
437  This function sets the liquidation price of a given short position (`po`) to the provided value (`v`).
438  
439  """
440  function liqprice!(po::Position{Short}, v)
441      @deassert v >= price(po) (v, price(po))
442      po.liquidation_price[] = v
443  end
444  
445  function Base.abs(po::Position)
446      abs(cash(po))
447  end
448  
449  function Base.print(io::IO, po::Position)
450      write(io, "Position($(posside(po)), $(po.asset))\n")
451      write(io, "entryprice: ")
452      write(io, string(price(po)))
453      write(io, "\namount: ")
454      write(io, string(cash(po)))
455      write(io, "\nnotional: ")
456      write(io, string(notional(po)))
457      write(io, "\ncollateral: ")
458      write(io, string(collateral(po)))
459      write(io, "\nleverage: ")
460      write(io, string(leverage(po)))
461      write(io, "\nmaintenance: ")
462      write(io, string(maintenance(po)))
463      write(io, "\nliquidation price: ")
464      write(io, string(liqprice(po)))
465      write(io, "\ndate: ")
466      write(io, string(po.timestamp[]))
467  end
468  
469  Base.show(io::IO, ::MIME"text/plain", po::Position) = print(io, po)
470  Base.show(io::IO, po::Position) = print(io, po)
471  
472  function PositionUpdated(tag, group, pos::Position)
473      PositionUpdated{exchangeid(pos)}(
474          Symbol(tag),
475          Symbol(group),
476          raw(pos.asset),
477          (posside(pos), isopen(pos)),
478          timestamp(pos),
479          liqprice(pos),
480          entryprice(pos),
481          maintenance(pos),
482          initial(pos),
483          leverage(pos),
484          notional(pos),
485      )
486  end
487  
488  # TODO: add `AddMargin` call! function
489  function MarginUpdated(tag, group, pos::Position; from_value::DFT=0.0)
490      MarginUpdated{exchangeid(pos)}(
491          Symbol(tag),
492          Symbol(group),
493          raw(pos.asset),
494          posside(pos),
495          timestamp(pos),
496          string(marginmode(pos)),
497          from_value,
498          margin(pos),
499      )
500  end
501  
502  function LeverageUpdated(tag, group, pos::Position; from_value::DFT=one(0.0))
503      LeverageUpdated{exchangeid(pos)}(
504          Symbol(tag),
505          Symbol(group),
506          raw(pos.asset),
507          posside(pos),
508          timestamp(pos),
509          from_value,
510          leverage(pos),
511      )
512  end
513  
514  export notional, additional, price, bankruptcy, pnl, collateral
515  export status, timestamp, tier
516  export timestamp!, leverage!, tier!
517  export liqprice!, margin!, maintenance!, initial!, additional!, notional!
518  export PositionOpen, PositionClose, PositionUpdate, PositionStatus, PositionChange