Packages
tai
0.0.2
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
"""
use GenServer
alias Tai.PubSub
alias Tai.Markets.{OrderBook, PriceLevel}
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(:bid, _from, state) do
bid = state.order_book
|> ordered_bids
|> List.first
{:reply, {:ok, bid}, state}
end
def handle_call({:bids, depth}, _from, state) do
bids = state.order_book
|> ordered_bids
|> take(depth)
{:reply, {:ok, bids}, state}
end
def handle_call(:ask, _from, state) do
ask = state.order_book
|> ordered_asks
|> List.first
{:reply, {:ok, ask}, state}
end
def handle_call({:asks, depth}, _from, state) do
asks = state.order_book
|> ordered_asks
|> take(depth)
{:reply, {:ok, asks}, 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 = state |> Map.put(:order_book, 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
def quotes(name, depth \\ :all) do
GenServer.call(name, {:quotes, depth: depth})
end
def bid(name) do
GenServer.call(name, :bid)
end
def bids(name, depth \\ :all) do
GenServer.call(name, {:bids, depth})
end
def ask(name) do
GenServer.call(name, :ask)
end
def asks(name, depth \\ :all) do
GenServer.call(name, {:asks, depth})
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: :btcusd)
Tai.Markets.OrderBook_my_test_feed_btcusd
"""
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