Packages
tai
0.0.10
0.0.75
0.0.74
0.0.73
0.0.72
0.0.71
0.0.70
0.0.69
0.0.68
0.0.67
0.0.66
0.0.65
0.0.64
0.0.63
0.0.62
0.0.61
0.0.60
0.0.59
0.0.58
0.0.57
0.0.56
0.0.55
0.0.54
0.0.53
0.0.52
0.0.51
0.0.50
0.0.49
0.0.48
0.0.47
0.0.46
0.0.45
0.0.44
0.0.43
0.0.42
0.0.41
0.0.40
0.0.39
0.0.38
0.0.37
0.0.36
0.0.35
0.0.34
0.0.33
0.0.32
0.0.31
0.0.30
0.0.29
0.0.28
0.0.27
0.0.26
0.0.25
0.0.24
0.0.23
0.0.22
0.0.21
0.0.20
0.0.19
0.0.18
0.0.17
0.0.16
0.0.15
0.0.14
0.0.13
0.0.12
0.0.11
0.0.10
0.0.9
0.0.8
0.0.7
0.0.6
0.0.5
0.0.4
0.0.3
0.0.2
0.0.1
A composable, real time, market data and trade execution toolkit
Current section
Files
Jump to
Current section
Files
lib/tai/markets/order_book.ex
defmodule Tai.Markets.OrderBook do
@moduledoc """
Manage and query the state for an order book for a symbol on a feed
"""
@type t :: %Tai.Markets.OrderBook{
bids: map,
asks: map
}
use GenServer
alias Tai.PubSub
alias Tai.Markets.{OrderBook, PriceLevel, Quote}
defstruct bids: %{}, asks: %{}
def start_link(feed_id: feed_id, symbol: symbol) do
GenServer.start_link(
__MODULE__,
%{
feed_id: feed_id,
symbol: symbol,
order_book: %OrderBook{}
},
name: to_name(feed_id: feed_id, symbol: symbol)
)
end
def init(state) do
{:ok, state}
end
def handle_call({:quotes, depth: depth}, _from, state) do
order_book = %OrderBook{
bids: state |> Map.get(:order_book) |> ordered_bids |> take(depth),
asks: state |> Map.get(:order_book) |> ordered_asks |> take(depth)
}
{:reply, {:ok, order_book}, state}
end
def handle_call({:replace, snapshot}, _from, state) do
PubSub.broadcast(
{:order_book_snapshot, state.feed_id, state.symbol},
{:order_book_snapshot, state.feed_id, state.symbol, snapshot}
)
new_state = Map.put(state, :order_book, snapshot)
Tai.Events.broadcast(%Tai.Events.OrderBookSnapshot{
venue_id: new_state.feed_id,
symbol: new_state.symbol,
snapshot: snapshot
})
{:reply, :ok, new_state}
end
def handle_call({:update, %OrderBook{bids: bids, asks: asks} = changes}, _from, state) do
PubSub.broadcast(
{:order_book_changes, state.feed_id, state.symbol},
{:order_book_changes, state.feed_id, state.symbol, changes}
)
new_order_book =
state.order_book
|> update_side(:bids, bids)
|> update_side(:asks, asks)
new_state =
state
|> Map.put(:order_book, new_order_book)
{:reply, :ok, new_state}
end
@doc """
Return bid/asks up to the given depth. If depth is not provided it returns
the full order book.
"""
def quotes(name, depth \\ :all) do
GenServer.call(name, {:quotes, depth: depth})
end
@doc """
Return the bid/ask at the top of the book
"""
def inside_quote(feed_id, symbol) do
[feed_id: feed_id, symbol: symbol]
|> to_name()
|> quotes(1)
|> case do
{:ok, %{bids: bids, asks: asks}} ->
with top_bid <- List.first(bids),
top_ask <- List.first(asks) do
{:ok, %Quote{bid: top_bid, ask: top_ask}}
end
end
end
def replace(name, %OrderBook{} = replacement) do
GenServer.call(name, {:replace, replacement})
end
def update(name, %OrderBook{} = changes) do
GenServer.call(name, {:update, changes})
end
@doc """
Returns an atom that will identify the process
## Examples
iex> Tai.Markets.OrderBook.to_name(feed_id: :my_test_feed, symbol: :btc_usd)
Tai.Markets.OrderBook_my_test_feed_btc_usd
"""
def to_name(feed_id: feed_id, symbol: symbol) do
:"#{__MODULE__}_#{feed_id}_#{symbol}"
end
defp ordered_bids(state) do
state.bids
|> Map.keys()
|> Enum.sort()
|> Enum.reverse()
|> with_price_levels(state.bids)
end
defp ordered_asks(state) do
state.asks
|> Map.keys()
|> Enum.sort()
|> with_price_levels(state.asks)
end
defp with_price_levels(prices, level_details) do
prices
|> Enum.map(fn price ->
{size, processed_at, server_changed_at} = level_details[price]
%PriceLevel{
price: price,
size: size,
processed_at: processed_at,
server_changed_at: server_changed_at
}
end)
end
defp take(list, :all), do: list
defp take(list, depth) do
list
|> Enum.take(depth)
end
defp update_side(state, side, price_levels) do
new_side =
state
|> Map.get(side)
|> Map.merge(price_levels)
|> Map.drop(price_levels |> drop_prices)
state
|> Map.put(side, new_side)
end
defp drop_prices(price_levels) do
price_levels
|> Enum.filter(fn {_price, {size, _processed_at, _server_changed_at}} -> size == 0 end)
|> Enum.map(fn {price, _} -> price end)
end
end