API
Documentation for CryptoMarketData.
Types
AbstractExchange
Every exchange is a subtype of AbstractExchange.
AbstractCandle
Every exchange also has a matching candle type that's a subtype of AbstractCandle. Its purpose is to capture the data given to us by the exchange.
Functions
General Functions
CryptoMarketData.get_saved_markets — Function
get_saved_markets(; datadir) -> DataFrames.DataFrame
Return a DataFrame that lists the currently saved markets.
Keyword Arguments
- datadir="./data" - directory where saved data is stored
Example
julia> saved = get_saved_markets()
10×4 DataFrame
Row │ exchange market start stop
│ Any Any Any Any
─────┼───────────────────────────────────────────────────────
1 │ binance BTCUSD_240628 2023-12-29 2024-02-17
2 │ binance BTCUSD_PERP 2020-08-11 2020-08-16
3 │ bitget BTCUSD_DMCBL 2019-04-23 2024-02-16
4 │ bitget DOGEUSD_DMCBL 2024-02-01 2024-02-20
5 │ bitmex ETHUSD 2018-08-02 2024-02-19
6 │ bitstamp BTCUSD 2011-08-18 2024-02-25
7 │ bybit ADAUSD 2022-03-24 2022-04-21
8 │ bybit-inverse ADAUSD 2022-03-24 2022-04-20
9 │ bybit-linear 10000LADYSUSDT 2023-05-11 2024-03-04
10 │ pancakeswap BTCUSD 2023-03-15 2024-03-04Generalized on Exchange
CryptoMarketData.save! — Function
save!(exchange, market; datadir, startday, endday, delay)
Download 1m candles from the given exchange and market, and save them locally.
Keyword Arguments
- datadir="./data" - directory where saved data is stored
- startday - a
Dateto start fetching candles from - endday - a
Dateto stop fetching candles - delay - a delay to be passed to
sleep()that will pause between internal calls tosave_day!()
Example
julia> bitstamp = Bitstamp()
julia> save!(bitstamp, "BTC/USD", endday=Date("2020-08-16"))To monitor its progress on long downloads, set the JULIA_DEBUG environment variable. This will cause debug log messages to be emitted before each day of candles is downloaded.
julia> ENV["JULIA_DEBUG"] = "CryptoMarketData"CryptoMarketData.load — Function
load(exchange, market; datadir, span, tf, table)
Load candles for the given exchange and market from the file system.
Keyword Arguments
- datadir="./data" - directory where saved data is stored
- span - a
Datespan that defines what Dates to load candles. If it'smissing, load everything. - tf - a
Periodthat is used to aggregate 1m candles into higher timeframes. - table - a Tables.jl-compatible struct to load candles into. The default is
DataFrame.
Example
julia> bitstamp = Bitstamp()
julia> btcusd4h = load(bitstamp, "BTC/USD"; span=Date("2024-01-01"):Date("2024-02-10"), tf=Hour(4))CryptoMarketData.earliest_candle — Function
earliest_candle(exchange, market)Return the earliest candle for the given market in the 1m timeframe.
CryptoMarketData.get_candles_for_day — Function
get_candles_for_day(
exchange::CryptoMarketData.AbstractExchange,
market,
day::Dates.Date
) -> Vector
Fetch all of the 1m candles for the given exchange, market, and day. The vector and candles returned is just the right size to save to the archives.
CryptoMarketData.save_day! — Function
save_day!(exchange, market, candles; datadir="./data")Save a day worth of 1m candles the caller provides for the given exchange and market.
Keyword Arguments
- datadir="./data" - directory where saved data is stored
Exchange Specific Implementations
csv_headers- csv_select
- ts2datetime_fn
- short_name
- candles_max
get_markets- get_candles
subscribeupdate!Base.merge
CryptoMarketData.csv_headers — Function
csv_headers(exchange::AbstractExchange) -> Vector{Symbol}Return headings for each column of candle data.
Example
julia> bitstamp = Bitstamp()
julia> columns = csv_headers(bitstamp)
6-element Vector{Symbol}:
:ts
:o
:h
:l
:c
:vCryptoMarketData.get_markets — Function
get_markets(exchange) -> Vector{String}Fetch the available markets for the given exchange.
Example
julia> bitstamp = Bitstamp()
julia> markets = get_markets(bitstamp)CryptoMarketData.update! — Function
update!(
candles::AbstractVector{<:CryptoMarketData.AbstractCandle},
candle::CryptoMarketData.AbstractCandle
) -> Symbol
Destructively update a vector of candles with new candle data. One of 3 things can happen as a result of calling this function.
- Return
:firstifcandleswas previously empty. - Return
:updatedif the timestamps are the same and update the last candle. - Return
:newif the timestamps are different and push the new candle.
Base.merge — Function
Base.merge(a::C, b::C) where {C <: CryptoMarketData.AbstractCandle} -> CEvery concrete candle type should implement a Base.merge method that will take the current candle a and a newer candle b with the same timestamp, and perform a merge such that high, low, and volume are updated as necessary. It should return a new candle with the merged data.
(This will be used by code that consumes unfinished candle data from WebSockets.)
CryptoMarketData.subscribe — Function
subscribe(url::String)This is a convenience method that accepts URLs as strings.
subscribe(uri::URI)This is the general version of websocket subscription that the other exchange-specific versions of subscribe are built on. It connects to the given uri and returns a struct that contains two Channels that can be used to interact with the WebSocket.
Example
julia> using URIs, JSON3
julia> s = subscribe(URI("wss://ws.bitstamp.net"))
CryptoMarketData.Session(URI("wss://ws.bitstamp.net"), missing, missing, missing, Task (runnable) @0x00007970dac63d00)
julia> btcusd_subscribe = Dict(:event => "bts:subscribe", :data => Dict(:channel => "live_trades_btcusd"))
Dict{Symbol, Any} with 2 entries:
:event => "bts:subscribe"
:data => Dict(:channel=>"live_trades_btcusd")
julia> put!(s.commands, JSON3.write(btcusd_subscribe))
"{"event":"bts:subscribe","data":{"channel":"live_trades_btcusd"}}"
julia> s.messages
Channel{Any}(32) (2 items available)
julia> take!(s.messages)
"{"event":"bts:subscription_succeeded","channel":"live_trades_btcusd","data":{}}"
julia> JSON3.read(take!(s.messages))
JSON3.Object{Base.CodeUnits{UInt8, String}, Vector{UInt64}} with 3 entries:
:data => {…
:channel => "live_trades_btcusd"
:event => "trade"Select
This was originally NHDaly/Select.jl, but because it wasn't easily installable, I absorbed it into this project. If a better way to multiplex multiple streams comes along, this may go away, but I'm going to use it until then.
CryptoMarketData.Select.select — Function
select(clauses[, block=true]) -> (clause_index, clause_value)
Functional form of the @select macro, intended to be used when the set of clauses is dynamic. In general, this method will be less performant than the macro variant.
Clauses are specified as an array of tuples. Each tuple is expected to have 2 or 3 elements, as follows:
- The clause type (
:takeor:put) - The waitable object
- If the clause type is
:put, the value to insert into the object.
If block is true (the default), wait for at least one clause to be satisfied and return a tuple whose first elmement is the index of the clause which unblocked first and whose whose second element is the value of the clause (see the manual on select for the meaning of clause value).
Otherwise, an arbitrary available clause will be executed, or a return value of (0, nothing) will be returned immediately if no clause is available.
CryptoMarketData.Select.@select — Macro
@select A select expression of the form:
@select begin
clause1 => body1
clause2 => body2
_ => default_body
end
endWait for multiple clauses simultaneously using a pattern matching syntax, taking a different action depending on which clause is available first. A clause has three possible forms:
event |> value
If event is an AbstractChannel, wait for a value to become available in the channel and assign take!(event) to value. if event is a Task, wait for the task to complete and assign value the return value of the task.
event |< value
Only suppored for AbstractChannels. Wait for the channel to capabity to store an element, and then call put!(event, value).
event
Calls wait on event, discarding the return value. Usable on any "waitable" events", which include channels, tasks, Condition objects, and processes.
If a default branch is provided, @select will check arbitrary choose any event which is ready and execute its body, or will execute default_body if none of them are.
Otherise, @select blocks until at least one event is ready.
For example,
channel1 = Channel()
channel2 = Channel()
task = @task ...
result = @select begin
channel1 |> value => begin
info("Took from channel1")
value
end
channel2 <| :test => info("Put :test into channel2")
task => info("task finished")
end