/ Instruments / src / derivatives.jl
derivatives.jl
  1  module Derivatives
  2  using ..Instruments
  3  using ..Instruments: FULL_SYMBOL_GROUPS_REGEX
  4  using ..Instruments.Misc.DocStringExtensions
  5  
  6  @doc "A symbol parsed as settlement currency."
  7  const SettlementCurrency = Symbol
  8  @doc "Differentiates between perpetuals and options."
  9  @enum DerivativeKind Unkn Call Put
 10  function parse_option(s::AbstractString)
 11      s == "C" && return Call
 12      s == "P" && return Put
 13      throw(ArgumentError("Failed to parse $s as `DerivativeKind`."))
 14  end
 15  
 16  _derivative_error(s) = "Failed to parse derivative symbols for $s."
 17  
 18  @doc """`Derivative` parsed accordingly to [`regex`](@ref Instruments.FULL_SYMBOL_GROUPS_REGEX).
 19  
 20  $(FIELDS)
 21  """
 22  struct Derivative8 <: AbstractAsset
 23      asset::Asset
 24      sc::SettlementCurrency
 25      id::SubString
 26      strike::Float64
 27      kind::DerivativeKind
 28      function Derivative8(a::A, args...; kwargs...) where {A<:Asset}
 29          new(a, args...; kwargs...)
 30      end
 31      function Derivative8(s::AbstractString, m)
 32          asset = Asset(SubString(s, 1, length(s)), m[1], m[2])
 33          @assert !isnothing(m[3]) _derivative_error(s)
 34          S = Symbol(m[3])
 35          id = isnothing(m[4]) ? SubString("") : m[4]
 36          strike = isnothing(m[5]) || isempty(m[5]) ? 0.0 : parse(Float64, m[5])
 37          kind = isnothing(m[6]) || isempty(m[6]) ? Unkn : parse_option(m[6])
 38          Derivative8(asset, S, id, strike, kind)
 39      end
 40  end
 41  Derivative = Derivative8
 42  
 43  @doc """Create a `Derivative` from a raw string representation raw, base currency bc, and quote currency qc.
 44  
 45  $(TYPEDSIGNATURES)
 46  """
 47  function perpetual(raw::AbstractString, bc, qc)
 48      Derivative(Asset(SubString(raw), bc, qc), Symbol(qc), SubString(""), 0.0, Unkn)
 49  end
 50  
 51  function Base.parse(::Type{Derivative}, s::AbstractString)
 52      m = match(FULL_SYMBOL_GROUPS_REGEX, SubString(s))
 53      @assert !(isnothing(m) || isnothing(m.captures)) _derivative_error(s)
 54      Derivative(s, m.captures)
 55  end
 56  
 57  function Base.parse(::Type{AbstractAsset}, s::AbstractString)
 58      m = match(FULL_SYMBOL_GROUPS_REGEX, SubString(s))
 59      @assert !(isnothing(m) || isnothing(m.captures)) _derivative_error(s)
 60      if length(m) > 2 && !isempty(m[3])
 61          Derivative(s, m.captures)
 62      else
 63          Asset(SubString(s, 1, length(s)), m[1], m[2])
 64      end
 65  end
 66  
 67  import Base.getproperty
 68  function getproperty(d::Derivative, s::Symbol)
 69      hasfield(Asset, s) && return getproperty(getfield(d, :asset), s)
 70      getfield(d, s)
 71  end
 72  
 73  @doc """Short-circuit the execution of a derivative calculation if the derivative d is zero.
 74  
 75  $(TYPEDSIGNATURES)
 76  """
 77  function sc(d::Derivative; orqc=true)
 78      s = getfield(d, :sc)
 79      isequal(s, Symbol("")) && orqc ? getfield(d, :asset).qc : s
 80  end
 81  
 82  @doc "Predicates according to [OctoBot](https://github.com/Drakkar-Software/OctoBot-Commons/blob/master/octobot_commons/symbols/symbol.py)"
 83  is_settled(d::Derivative) = d.sc != Symbol()
 84  has_strike(d::Derivative) = d.strike != 0.0
 85  expires(d::Derivative) = !isempty(d.id)
 86  # FIXME: `is_future` doesn't make sense. If anything it should be `is_derivative`. But we could also
 87  # raise an error inside the `Derivative` constructor when we can't parse the settlement currency.
 88  is_future(d::Derivative) = is_settled(d) && !has_strike(d) && d.kind == Unkn
 89  is_perp(d::Derivative) = is_future(d) && !expires(d.id)
 90  is_spot(d::Derivative) = !is_settled(d)
 91  is_option(d::Derivative) = d.kind != Unkn && is_settled(d) && has_strike(d) && expires(d)
 92  is_linear(d::Derivative) = is_settled(d) ? d.qc == d.sc : true
 93  is_inverse(d::Derivative) = is_settled(d) ? d.bc == d.sc : false
 94  
 95  @doc """Shortand for parsing derivatives:
 96  ```julia
 97  > drv = d"BTC/USDT:USDT"
 98  > typeof(drv)
 99  # Instruments.Derivatives.Derivative{Asset{:BTC, :USDT}}
100  ```
101  """
102  macro d_str(s)
103      :($(parse(Derivative, s)))
104  end
105  function Base.:(==)(b::NTuple{3,Symbol}, a::Derivative)
106      a.bc == b[1] && a.qc == b[2] && a.sc == b[3]
107  end
108  function Base.:(==)(a::Derivative, b::Derivative)
109      a.qc == b.qc && a.bc == b.bc && a.sc == b.sc
110  end
111  Base.hash(a::Derivative) = hash(getfield(a, :asset).raw) #hash(Instruments._hashtuple(a))
112  Base.hash(a::Derivative, h::UInt64) = hash(getfield(a, :asset).raw, h)
113  Base.string(a::Derivative) = "Derivative($(a.raw))"
114  Base.show(buf::IO, a::Derivative) = write(buf, string(a))
115  Base.Broadcast.broadcastable(q::Derivative) = Ref(q)
116  
117  export Derivative, DerivativeKind, @d_str, perpetual, sc
118  
119  end