funding.jl
1 using Exchanges.Instruments 2 using .TimeTicks 3 using .Misc: DFT, FUNDING_PERIOD 4 using .Misc.TimeToLive 5 using .Misc.Lang: @lget!, @debug_backtrace 6 using .Instruments.Derivatives 7 using .Python: PyDict 8 using Processing: _fill_missing_candles 9 using Processing.Data: Not, select! 10 11 @doc """Retrieves all or a subset of funding data for a symbol from an exchange. 12 13 $(TYPEDSIGNATURES) 14 15 The `funding_data` function retrieves all funding data returned by an exchange `exc` for a symbol `sym`. 16 """ 17 function funding_data(exc::Exchange, sym::AbstractString) 18 try 19 pyfetch(exc.fetchFundingRate, sym) 20 catch 21 @debug_backtrace 22 end 23 end 24 funding_data(exc, a::Derivative, args...) = funding_data(exc, a.raw) 25 funding_data(v, args...) = funding_data(exc, v, args...) 26 27 @doc """Retrieves the funding rate for a symbol from an exchange. 28 29 $(TYPEDSIGNATURES) 30 31 The `funding_rate` function retrieves the funding rate for a symbol `s` from an exchange `exc`. 32 """ 33 function funding_rate(exc::Exchange, s::AbstractString) 34 id = exc.id 35 @lget! FUNDING_RATE_CACHE (s, id) begin 36 resp = if exc.has[:fetchFundingRates] 37 rates = @lget! FUNDING_RATES_CACHE id pyfetch(exc.fetchFundingRates) 38 rates[s] 39 else 40 pyfetch(exc.fetchFundingRate, s) 41 end 42 get(k) = 43 let 44 v = (resp.get(k)) 45 if Python.pyisTrue(pytype(v) == pybuiltins.str) 46 pytofloat(v) 47 else 48 pyconvert(Option{DFT}, v) 49 end 50 end 51 @something get("fundingRate") get("nextFundingRate") 0.00001 52 end 53 end 54 funding_rate(exc, a::Derivative) = funding_rate(exc, a.raw) 55 funding_rate(ai) = funding_rate(ai.exchange, ai.asset) 56 57 const FUNDING_RATE_COLUMNS = (:timestamp, :pair, :rate) 58 const FUNDING_RATE_COLS = [FUNDING_RATE_COLUMNS...] 59 @doc """Parses a row of funding data from a Python object. 60 61 $(TYPEDSIGNATURES) 62 63 The `parse_funding_row` function takes a row of funding data `r` from a Python object and parses it into a format suitable for further processing or analysis. 64 """ 65 function parse_funding_row(r::Py) 66 pyconvert(Tuple{Int64,String,Float64}, (r["timestamp"], r["symbol"], r["fundingRate"])) 67 end 68 @doc """Extracts futures data from a Python object. 69 70 $(TYPEDSIGNATURES) 71 72 The `extract_futures_data` function takes futures data `data` from a Python object and extracts it into a format suitable for further processing or analysis. 73 """ 74 function extract_futures_data(data::Py) 75 ts, sym, rate = DateTime[], String[], Float64[] 76 for r in data 77 push!(ts, dt(pyconvert(Int64, r["timestamp"]))) 78 push!(sym, pyconvert(String, r["symbol"])) 79 push!(rate, pyconvert(Float64, r["fundingRate"])) 80 end 81 DataFrame([ts, sym, rate], FUNDING_RATE_COLS) 82 end 83 84 @doc "Defines limit values for fetching futures data from exchanges." 85 const futures_limits = IdDict(:binance => 1000) 86 87 @doc """Fetches funding rate history from an exchange for a list of `Derivative` pairs. 88 89 $(TYPEDSIGNATURES) 90 91 The `funding_history` function fetches funding rate history from a given exchange `exc` for a list of `assets`. The `from` and `to` parameters define the date range for which to fetch the funding rate history. Additional parameters can be specified through the `params` dictionary. The function will wait for `sleep_t` seconds between each request to the exchange. The `limit` parameter can be used to limit the amount of data fetched. If `cleanup` is set to true, the function will perform a cleanup on the fetched data before returning it. 92 """ 93 function funding_history( 94 exc::Exchange, 95 assets::Vector; 96 from::DateType="", 97 to::DateType="", 98 params=Dict(), 99 sleep_t=1, 100 limit=nothing, 101 cleanup=true, 102 ) 103 from, to = from_to_dt(FUNDING_PERIOD, from, to) 104 from, to = _check_from_to(from, to) 105 ff = 106 (pair, since, limit; kwargs...) -> begin 107 try 108 pyfetch(exc.py.fetchFundingRateHistory, pair; since, limit, params) 109 catch err 110 # HACK: `since` is supposed to be the timestamp of the beginning of the 111 # period to fetch. However if it considered invalid, use a negative value 112 # representing the milliseconds that have passed since the start date. 113 if occursin("Time Is Invalid", string(err)) 114 delta = -Int(timefloat(now() - dt(since))) 115 pyfetch( 116 exc.py.fetchFundingRateHistory, pair; since=delta, limit, params 117 ) 118 else 119 throw(err) 120 end 121 end 122 end 123 if isnothing(limit) 124 limit = get(futures_limits, Symbol(exc.id), nothing) 125 end 126 ans = Dict( 127 begin 128 out = DataFrame( 129 [DateTime[], String[], Float64[]], FUNDING_RATE_COLS; copycols=false 130 ) 131 _fetch_loop( 132 ff, 133 exc, 134 raw(a); 135 from, 136 to, 137 sleep_t, 138 converter=extract_futures_data, 139 limit, 140 out, 141 ) 142 a => out 143 end for a in assets 144 ) 145 if cleanup 146 # use a shorter timeframe to avoid overlapping 147 half_tf = TimeFrame(Millisecond(FUNDING_PERIOD) / 2) 148 f_tf = TimeFrame(Millisecond(FUNDING_PERIOD)) 149 for k in keys(ans) 150 _cleanup_funding_history(ans[k], k, half_tf, f_tf) 151 end 152 end 153 return ans 154 end 155 156 @doc """Cleans up fetched funding history data. 157 158 $(TYPEDSIGNATURES) 159 160 The `_cleanup_funding_history` function takes a DataFrame `df` of fetched funding history data for a `name` and performs cleanup operations on it. The `half_tf` and `f_tf` parameters are used in the cleanup process. 161 """ 162 function _cleanup_funding_history(df, name, half_tf, f_tf) 163 # normalize timestamps 164 df.timestamp[:] = apply.(half_tf, df.timestamp) 165 unique!(df, :timestamp) 166 # resample to funding timestamp 167 resample(df, half_tf, f_tf) 168 # add close because of fill_missing_candles 169 df[!, :close] .= 0.0 170 buildf(ts, args...) = (; timestamp=ts, pair=string(name), rate=0.0001, close=NaN) 171 _fill_missing_candles( 172 df, 173 FUNDING_PERIOD; 174 strategy=:custom, 175 inplace=true, 176 def_strategy=buildf, 177 def_type=NamedTuple{ 178 (:timestamp, :pair, :rate, :close),Tuple{DateTime,String,DFT,DFT} 179 }, 180 ) 181 # remove close after fills 182 select!(df, Not(:close)) 183 end 184 185 @doc "Defines the time-to-live (TTL) for a funding rate as 5 seconds." 186 const FUNDING_RATE_TTL = Ref(Second(5)) 187 @doc "Initializes a safe TTL cache for storing funding rates with a specified TTL." 188 const FUNDING_RATE_CACHE = safettl(Tuple{String,Symbol}, DFT, FUNDING_RATE_TTL[]) 189 @doc "Initializes a safe TTL cache for storing multiple funding rates with a specified TTL." 190 const FUNDING_RATES_CACHE = safettl(Symbol, Py, FUNDING_RATE_TTL[]) 191 assetkey(ai) = (ai.raw, ai.exchange.id) 192 193 export funding_history, funding_rate