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