/ Exchanges / src / currency.jl
currency.jl
  1  using .Misc: MM, toprecision, DFT
  2  using .Python: pybuiltins, pyisinstance, pyfloat, pyint
  3  using .Instruments: AbstractCash, atleast!, Cash
  4  import Instruments: value, addzero!
  5  Instruments.@importcash!
  6  import Base: ==, +, -, ÷, /, *
  7  import .Misc: gtxzero, ltxzero, approxzero, ZERO
  8  
  9  @doc "The cache for currencies which lasts for 1 hour by exchange."
 10  const currenciesCache1Hour = safettl(ExchangeID, Py, Hour(1))
 11  @doc "This lock is only used during currency construction."
 12  const currency_lock = ReentrantLock()
 13  
 14  @doc "Convert a Python object to a float number."
 15  function to_float(py::Py, T::Type{<:AbstractFloat}=DFT)
 16      something(pyconvert(Option{T}, py), zero(T))
 17  end
 18  to_float(v::Number) = v
 19  
 20  @doc "Convert a Python object to a number."
 21  function to_num(py::Py)
 22      @something if pyisnone(py)
 23          0.0
 24      elseif pyisinstance(py, pybuiltins.int)
 25          pyconvert(Option{Int}, py)
 26      elseif pyisinstance(py, pybuiltins.float)
 27          pyconvert(Option{DFT}, py)
 28      elseif pyisinstance(py, (pybuiltins.tuple, pybuiltins.list)) && length(py) > 0
 29          to_num(py[0])
 30      elseif pyisinstance(py, pybuiltins.str)
 31          isempty(py) ? 0 : pyconvert(DFT, pyfloat(py))
 32      else
 33          pyconvert(Option{DFT}, pyfloat(py))
 34      end 0.0
 35  end
 36  
 37  @doc "Returns the limits, precision, and fees for a currency as a named tuple.
 38  
 39  $(TYPEDSIGNATURES)
 40  
 41  The tuple fields can be nothing if the currency property is not provided.
 42  "
 43  function _lpf(exc, cur)
 44      local limits, precision, fees
 45      if isnothing(cur) || pyisnone(cur)
 46          limits = (; min=1e-8, max=1e8)
 47          precision = 8
 48          fees = zero(DFT)
 49      else
 50          limits = let l = get(cur, "limits", nothing)
 51              if isnothing(l) || pyisnone(l)
 52                  (min=1e-8, max=1e8)
 53              elseif haskey(l, "amount")
 54                  MM{DFT}((to_float(l["amount"]["min"]), to_float(l["amount"]["max"])))
 55              else
 56                  (min=1e-8, max=1e8)
 57              end
 58          end
 59          precision = let p = get(cur, "precision", nothing)
 60              if isnothing(p) || pyisnone(p)
 61                  8
 62              else
 63                  to_num(p)
 64              end
 65          end
 66          fees = to_float(get(cur, "fee", nothing))
 67      end
 68      (; limits, precision, fees)
 69  end
 70  
 71  @doc "Returns the currency from the exchange if found."
 72  function _cur(exc, sym)
 73      sym_str = uppercase(string(sym))
 74      curs = @lget! currenciesCache1Hour exc.id let v = pyfetch(exc.fetchCurrencies)
 75          v isa PyException ? exc.currencies : v
 76      end
 77      (pyisnone(curs) || isempty(curs)) ? nothing : get(curs, sym_str, nothing)
 78  end
 79  
 80  @doc "A `CurrencyCash` contextualizes a `Cash` instance w.r.t. an exchange.
 81  Operations are rounded to the currency precision.
 82  
 83  $(FIELDS)
 84  "
 85  struct CurrencyCash{C<:Cash,E<:ExchangeID} <: AbstractCash
 86      cash::C
 87      limits::MM{DFT}
 88      precision::T where {T<:Real}
 89      fees::DFT
 90      sandbox::Bool
 91      @doc """Create a CurrencyCash object.
 92  
 93      $(TYPEDSIGNATURES)
 94      """
 95      function CurrencyCash(id::Type{<:ExchangeID}, cash_type::Type{<:Cash}, v; sandbox=false, account="")
 96          @lock currency_lock begin
 97              exc = getexchange!(Symbol(id); sandbox, account)
 98              c = cash_type(v)
 99              lpf = _lpf(exc, _cur(exc, nameof(c)))
100              Instruments.cash!(c, toprecision(c.value, lpf.precision))
101              new{cash_type,id}(c, lpf..., issandbox(exc))
102          end
103      end
104      function CurrencyCash(exc::Exchange, sym, v=0.0)
105          @lock currency_lock begin
106              cur = _cur(exc, sym)
107              pyisinstance(cur, pybuiltins.dict) ||
108                  @debug "$sym not found on $(exc.name) (using defaults)"
109              c = Cash(sym, v)
110              lpf = _lpf(exc, cur)
111              Instruments.cash!(c, toprecision(c.value, lpf.precision))
112              new{typeof(c),typeof(exc.id)}(c, lpf..., issandbox(exc))
113          end
114      end
115  end
116  
117  function CurrencyCash{C,E}(v; sandbox=false) where {C<:Cash,E<:ExchangeID}
118      CurrencyCash(E, C, v; sandbox)
119  end
120  
121  function CurrencyCash(c::CurrencyCash{C,E}, v) where {C<:Cash,E<:ExchangeID}
122      CurrencyCash(E, C, v; sandbox=c.sandbox)
123  end
124  
125  @doc "The currency cash as a number."
126  value(cc::CurrencyCash) = value(cc.cash)
127  Base.getproperty(c::CurrencyCash, s::Symbol) =
128      if s == :value
129          getfield(_cash(c), :value)[]
130      elseif s == :id
131          nameof(_cash(c))
132      else
133          getfield(c, s)
134      end
135  function Base.isapprox(cc::C, v::N) where {C<:CurrencyCash,N<:Number}
136      isapprox(value(cc), v; atol=_atol(cc))
137  end
138  Base.isapprox(v::N, cc::C) where {C<:CurrencyCash,N<:Number} = isapprox(cc, v)
139  Base.setproperty!(::CurrencyCash, ::Symbol, v) = error("CurrencyCash is private.")
140  Base.zero(c::CurrencyCash) = zero(c.cash)
141  Base.zero(::Type{<:CurrencyCash{C}}) where {C} = zero(C)
142  function Base.iszero(c::CurrencyCash{Cash{S,T}}) where {S,T}
143      isapprox(value(c), zero(T); atol=_atol(c))
144  end
145  
146  function Base.show(io::IO, c::CurrencyCash{<:Cash,E}) where {E<:ExchangeID}
147      write(io, "$(c.cash) (on $(E.parameters[1]))")
148  end
149  Base.hash(c::CurrencyCash, args...) = hash(_cash(c), args...)
150  Base.nameof(c::CurrencyCash) = nameof(_cash(c))
151  
152  Base.promote(c::CurrencyCash, n) = promote(_cash(c), n)
153  Base.promote(n, c::CurrencyCash) = promote(n, _cash(c))
154  
155  Base.convert(::Type{T}, c::CurrencyCash) where {T<:Real} = convert(T, _cash(c))
156  Base.isless(a::CurrencyCash, b::CurrencyCash) = isless(_cash(a), _cash(b))
157  Base.isless(a::CurrencyCash, b::Number) = isless(promote(a, b)...)
158  Base.isless(b::Number, a::CurrencyCash) = isless(promote(b, a)...)
159  Base.isless(a::Cash, b::CurrencyCash) = isless(a, b.cash)
160  
161  Base.abs(c::CurrencyCash) = _toprec(c, abs(_cash(c)))
162  Base.real(c::CurrencyCash) = _toprec(c, real(_cash(c)))
163  
164  _cash(cc::CurrencyCash) = getfield(cc, :cash)
165  _prec(cc::CurrencyCash) = getfield(cc, :precision)
166  _toprec(cc::AbstractCash, v) = toprecision(v, _prec(cc))
167  _toprec(cc::AbstractCash, c::C) where {C<:AbstractCash} = toprecision(value(c), _prec(cc))
168  _asatol(v::F) where {F<:AbstractFloat} = v
169  _asatol(v::I) where {I<:Integer} = 1 / 10^v
170  _atol(cc::CurrencyCash) = _prec(cc) |> _asatol
171  
172  -(a::CurrencyCash, b::Real) = _toprec(a, a.cash - b)
173  +(a::CurrencyCash, b::Real) = _toprec(a, a.cash + b)
174  *(a::CurrencyCash, b::Real) = _toprec(a, a.cash * b)
175  /(a::CurrencyCash, b::Real) = _toprec(a, a.cash / b)
176  ÷(a::CurrencyCash, b::Real) = a.cash ÷ b
177  
178  ==(a::CurrencyCash{S}, b::CurrencyCash{S}) where {S} = _cash(b) == _cash(a)
179  ÷(a::CurrencyCash{S}, b::CurrencyCash{S}) where {S} = _cash(a) ÷ _cash(b)
180  *(a::CurrencyCash{S}, b::CurrencyCash{S}) where {S} = _toprec(a, _cash(a) * _cash(b))
181  /(a::CurrencyCash{S}, b::CurrencyCash{S}) where {S} = _toprec(a, _cash(a) / _cash(b))
182  +(a::CurrencyCash{S}, b::CurrencyCash{S}) where {S} = _toprec(a, _cash(a) + _cash(b))
183  -(a::CurrencyCash{S}, b::CurrencyCash{S}) where {S} = _toprec(a, _cash(a) - _cash(b))
184  
185  _applyop!(op, c, v) =
186      let cv = getfield(_cash(c), :value)
187          cv[] = _toprec(c, op(cv[], v))
188      end
189  
190  add!(c::CurrencyCash, v) = _applyop!(+, c, v)
191  sub!(c::CurrencyCash, v) = _applyop!(-, c, v)
192  mul!(c::CurrencyCash, v) = _applyop!(*, c, v)
193  rdiv!(c::CurrencyCash, v) = _applyop!(/, c, v)
194  div!(c::CurrencyCash, v) = div!(_cash(c), v)
195  mod!(c::CurrencyCash, v) = mod!(_cash(c), v)
196  cash!(c::CurrencyCash, v) = cash!(_cash(c), _toprec(c, v))
197  addzero!(c::CurrencyCash, v, args...; atol=_atol(c), kwargs...) = begin
198      add!(c, v)
199      atleast!(c; atol)
200      c
201  end
202  
203  gtxzero(c::CurrencyCash) = gtxzero(value(c); atol=_atol(c))
204  ltxzero(c::CurrencyCash) = ltxzero(value(c); atol=_atol(c))
205  approxzero(c::CurrencyCash) = approxzero(value(c); atol=_atol(c))