Developers
Implementing a new AbstractBroker
We will use AlpacaBroker
as an example for how to implement the AbstractBroker
interface.
Base.@kwdef mutable struct AlpacaBroker <: AbstractBroker
key_id::String
secret_key::String
cache::DataCache = DataCache()
rate::Int = 200
last::TimeDate = current_time()
@atomic nrequests::Int
function AlpacaBroker(key_id, secret_key, cache, rate, last, nrequests)
try
header = ["APCA-API-KEY-ID" => key_id, "APCA-API-SECRET-KEY" => secret_key]
testurl = URI("https://paper-api.alpaca.markets/v2/clock")
resp = HTTP.get(testurl, header)
catch e
throw(AuthenticationException(e))
end
return new(key_id, secret_key, cache, rate, last, nrequests)
end
end
key_id
and secret_key
are used in the constructor for authentication verification, and later on for making api calls. However, these are not assumed to be present and are thus not part of the interface.
.cache
DataCache
The functions:
bars(broker)
quotes(broker)
trades(broker)
are defined for an AbstractBroker
, but assume that the AbstractBroker
has a field .cache
which is a DataCache
so it is advised for any AbstractBroker
to follow the convention that it has a .cache
field.
Wrapper Broker
If you would want to construct a "wrapper" broker, i.e. wrapping the broker
which supplies the actual API to alter its default behavior (HistoricalBroker
is an example of this), you should overload the broker(broker)
function to return the "actual" broker:
broker(b::HistoricalBroker) = b.broker
broker(b::AlpacaBroker) = b
data_query
Next up is the data_query(broker, asset, start, stop, args...; section = ["bars" or "quotes" or"trades"], [timeframe=DateTime if section == "bars"])
function, which performs the actual api calls to retrieve either bars
, quotes
or trades
from the broker
. What to retrieve is communicated through the section
kwarg, and in the case of bars
it will be called using the timeframe
kwarg. This should return a TimeFrame
.
Bars
The bars(broker, msg::Vector)
function should be overloaded to be used in DataStream
to parse incoming bar updates. It should return a Vector
of (asset, (datetime, (bar_open, bar_high, bar_low, bar_close, bar_volume)))
:
function bars(::Union{AlpacaBroker,MockBroker}, msg::AbstractVector)
return map(filter(x -> x[:T] == "b", msg)) do bar
asset = bar[:S]
return asset, (parse_time(bar[:t]), (bar[:o], bar[:h], bar[:l], bar[:c], bar[:v]))
end
end
The function subscribe_bars(broker, asset, websocket)
should be overloaded in order to communicate to a DataStream
which assets
we want to receive updates for.
data_stream_url(broker)
should point to a url from which we can stream bar update data. authenticate_data(broker)
is called just after opening the stream.
Orders
The first function to overload is submit_order(broker, order::EntityState{Purchase or Sale})
which actually submites the orders. I think the AlpacaBroker
one can be used with other Brokers, given that a couple of overloads are defined, but I'm not sure if the structure would always be the same.
Next up is receive_order(broker, websocket)
which is called by the trading_task
to listen to Order updates and thus potentially portfolio updates (when filled). It should return an Order
.
To be able to open the TradingStream
trading_stream_url(broker)
should be overloaded and authenticate_trading(broker, websocket)
which also sends the right message to start listening to the updates.
Account Details
Upon construction a Trader
will fill out the current portfolio by calling account_details(broker)
which should return (cash, positions)
where positions = [(asset, quantity), (asset, quantity), ...]
.
References
Trading.DataCache
— TypeA cache used by Brokers to store previously retrieved historical data.