-
Notifications
You must be signed in to change notification settings - Fork 74
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Start adding Delta Exchange venue adapter
- Add products - Add order book streaming - Add maker taker fees
- Loading branch information
Showing
20 changed files
with
591 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
apps/tai/fixture/vcr_cassettes/venue_adapters/shared/products/delta_exchange/success.json
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
defmodule Tai.VenueAdapters.DeltaExchange do | ||
alias Tai.VenueAdapters.DeltaExchange.{ | ||
StreamSupervisor, | ||
Products, | ||
Accounts, | ||
MakerTakerFees, | ||
# Positions, | ||
# CreateOrder, | ||
# CancelOrder | ||
} | ||
|
||
@behaviour Tai.Venues.Adapter | ||
|
||
def stream_supervisor, do: StreamSupervisor | ||
defdelegate products(venue_id), to: Products | ||
defdelegate accounts(venue_id, credential_id, credentials), to: Accounts | ||
defdelegate maker_taker_fees(venue_id, credential_id, credentials), to: MakerTakerFees | ||
def positions(_venue_id, _credential_id, _credentials), do: {:error, :not_implemented} | ||
def create_order(_order, _credentials), do: {:error, :not_implemented} | ||
def amend_order(_order, _attrs, _credentials), do: {:error, :not_supported} | ||
def amend_bulk_orders(_orders_with_attrs, _credentials), do: {:error, :not_supported} | ||
def cancel_order(_order, _credentials), do: {:error, :not_implemented} | ||
end |
12 changes: 12 additions & 0 deletions
12
apps/tai/lib/tai/venue_adapters/delta_exchange/accounts.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
defmodule Tai.VenueAdapters.DeltaExchange.Accounts do | ||
def accounts(_venue_id, _credential_id, _credentials) do | ||
# venue_credentials = struct!(ExDeltaExchange.Credentials, credentials) | ||
|
||
# with {:ok, balances} <- ExDeltaExchange.Wallet.Balances.get(venue_credentials) do | ||
# accounts = balances |> Enum.map(&build(&1, venue_id, credential_id)) | ||
# {:ok, accounts} | ||
# end | ||
|
||
{:ok, []} | ||
end | ||
end |
13 changes: 13 additions & 0 deletions
13
apps/tai/lib/tai/venue_adapters/delta_exchange/maker_taker_fees.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
defmodule Tai.VenueAdapters.DeltaExchange.MakerTakerFees do | ||
def maker_taker_fees(_venue_id, _credential_id, _credentials) do | ||
# venue_credentials = struct!(ExDeltaExchange.Credentials, credentials) | ||
|
||
# with {:ok, account} <- ExDeltaExchange.Account.Show.get(venue_credentials) do | ||
# maker = account.maker_fee |> Tai.Utils.Decimal.cast!() | ||
# taker = account.taker_fee |> Tai.Utils.Decimal.cast!() | ||
# {:ok, {maker, taker}} | ||
# end | ||
|
||
{:ok, nil} | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
defmodule Tai.VenueAdapters.DeltaExchange.Product do | ||
@type venue :: Tai.Venue.id() | ||
@type product :: Tai.Venues.Product.t() | ||
@type venue_product :: ExDeltaExchange.Product.t() | ||
|
||
@spec build(venue_product, venue) :: product | ||
def build(%ExDeltaExchange.Product{} = venue_product, venue_id) do | ||
%Tai.Venues.Product{ | ||
venue_id: venue_id, | ||
symbol: venue_product.symbol |> to_symbol, | ||
venue_symbol: venue_product.symbol, | ||
alias: nil, | ||
base: venue_product.contract_unit_currency |> downcase_and_atom(), | ||
quote: venue_product.quoting_asset.symbol |> downcase_and_atom(), | ||
venue_base: venue_product.contract_unit_currency, | ||
venue_quote: venue_product.quoting_asset.symbol, | ||
status: venue_product |> status(), | ||
type: venue_product |> type(), | ||
collateral: false, | ||
price_increment: venue_product.tick_size |> Decimal.new(), | ||
size_increment: venue_product.tick_size |> Decimal.new(), | ||
min_price: venue_product.tick_size |> Decimal.new(), | ||
min_size: venue_product.tick_size |> Decimal.new(), | ||
value: venue_product.contract_value |> Decimal.new(), | ||
value_side: :base, | ||
maker_fee: venue_product.maker_commission_rate |> Decimal.new(), | ||
taker_fee: venue_product.taker_commission_rate |> Decimal.new(), | ||
is_quanto: venue_product.is_quanto, | ||
is_inverse: false | ||
} | ||
end | ||
|
||
def to_symbol(venue_product_name), do: venue_product_name |> downcase_and_atom() | ||
|
||
def status(venue_product) do | ||
case venue_product.trading_status do | ||
"operational" -> :trading | ||
_ -> :unknown | ||
end | ||
end | ||
|
||
defp type(venue_product) do | ||
cond do | ||
venue_product.contract_type == "call_options" -> :option | ||
venue_product.contract_type == "put_options" -> :option | ||
venue_product.contract_type == "futures" -> :future | ||
venue_product.contract_type == "perpetual_futures" -> :swap | ||
venue_product.contract_type == "interest_rate_swaps" -> :swap | ||
venue_product.contract_type == "spreads" -> :swap | ||
venue_product.contract_type == "spot" -> :spot | ||
String.starts_with?(venue_product.symbol, "MV-") -> :move | ||
true -> :unknown | ||
end | ||
end | ||
|
||
defp downcase_and_atom(str), do: str |> String.downcase() |> String.to_atom() | ||
end |
10 changes: 10 additions & 0 deletions
10
apps/tai/lib/tai/venue_adapters/delta_exchange/products.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
defmodule Tai.VenueAdapters.DeltaExchange.Products do | ||
alias Tai.VenueAdapters.DeltaExchange | ||
|
||
def products(venue_id) do | ||
with {:ok, venue_products} <- ExDeltaExchange.Products.List.get() do | ||
products = venue_products |> Enum.map(& DeltaExchange.Product.build(&1, venue_id)) | ||
{:ok, products} | ||
end | ||
end | ||
end |
97 changes: 97 additions & 0 deletions
97
apps/tai/lib/tai/venue_adapters/delta_exchange/stream/connection.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
defmodule Tai.VenueAdapters.DeltaExchange.Stream.Connection do | ||
use Tai.Venues.Streams.ConnectionAdapter | ||
require Logger | ||
alias Tai.VenueAdapters.DeltaExchange.Stream | ||
|
||
@type stream :: Tai.Venues.Stream.t() | ||
@type venue_id :: Tai.Venue.id() | ||
@type credential_id :: Tai.Venue.credential_id() | ||
@type credential :: Tai.Venue.credential() | ||
|
||
@spec start_link( | ||
endpoint: String.t(), | ||
stream: stream, | ||
credential: {credential_id, credential} | nil | ||
) :: {:ok, pid} | {:error, term} | ||
def start_link(endpoint: endpoint, stream: stream, credential: credential) do | ||
routes = %{ | ||
order_books: stream.venue.id |> Stream.RouteOrderBooks.to_name(), | ||
} | ||
|
||
state = %Tai.Venues.Streams.ConnectionAdapter.State{ | ||
venue: stream.venue.id, | ||
routes: routes, | ||
channels: stream.venue.channels, | ||
credential: credential, | ||
order_books: stream.order_books, | ||
quote_depth: stream.venue.quote_depth, | ||
heartbeat_interval: stream.venue.stream_heartbeat_interval, | ||
heartbeat_timeout: stream.venue.stream_heartbeat_timeout, | ||
opts: stream.venue.opts | ||
} | ||
|
||
name = process_name(stream.venue.id) | ||
WebSockex.start_link(endpoint, __MODULE__, state, name: name) | ||
end | ||
|
||
@impl true | ||
def subscribe(:init, state) do | ||
send(self(), {:subscribe, :orderbook}) | ||
{:ok, state} | ||
end | ||
|
||
@subscribe_request %{ | ||
"type" => "subscribe", | ||
"payload" => %{} | ||
} | ||
@order_books_chunk_count 100 | ||
@impl true | ||
def subscribe(:orderbook, state) do | ||
state.order_books | ||
|> Enum.map(& &1.venue_symbol) | ||
|> Enum.chunk_every(@order_books_chunk_count) | ||
|> Enum.each(fn chunk_symbols -> | ||
payload = %{ | ||
"channels" => [%{ | ||
"name" => "l2_orderbook", | ||
"symbols" => chunk_symbols | ||
}] | ||
} | ||
msg = @subscribe_request |> Map.put("payload", payload) | ||
send(self(), {:send_msg, msg}) | ||
end) | ||
|
||
{:ok, state} | ||
end | ||
|
||
@impl true | ||
def on_msg(%{"type" => "l2_orderbook"} = msg, received_at, state) do | ||
msg |> forward(:order_books, received_at, state) | ||
{:ok, state} | ||
end | ||
|
||
@impl true | ||
def on_msg(%{"type" => "subscriptions"}, _received_at, state) do | ||
{:ok, state} | ||
end | ||
|
||
@impl true | ||
def on_msg(%{"error" => %{"code" => code, "context" => context}}, _received_at, state) do | ||
case code do | ||
429 -> | ||
%{"limit_reset_in" => limit_reset_in} = context | ||
Logger.error "Too many requests limit_reset_in=#{limit_reset_in}" | ||
|
||
c -> | ||
Logger.warn "Unhandled error code=#{c}, context=#{inspect(context)}" | ||
end | ||
|
||
{:ok, state} | ||
end | ||
|
||
defp forward(msg, to, received_at, state) do | ||
state.routes | ||
|> Map.fetch!(to) | ||
|> GenServer.cast({msg, received_at}) | ||
end | ||
end |
69 changes: 69 additions & 0 deletions
69
apps/tai/lib/tai/venue_adapters/delta_exchange/stream/process_order_book.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
defmodule Tai.VenueAdapters.DeltaExchange.Stream.ProcessOrderBook do | ||
use GenServer | ||
alias Tai.Markets.OrderBook | ||
|
||
defmodule State do | ||
@type venue_id :: Tai.Venue.id() | ||
@type product_symbol :: Tai.Venues.Product.symbol() | ||
@type t :: %State{ | ||
venue: venue_id, | ||
symbol: product_symbol, | ||
table: %{optional(String.t()) => number} | ||
} | ||
|
||
@enforce_keys ~w[venue symbol table]a | ||
defstruct ~w[venue symbol table]a | ||
end | ||
|
||
@type venue_id :: Tai.Venue.id() | ||
@type product :: Tai.Venues.Product.t() | ||
@type venue_symbol :: Tai.Venues.Product.venue_symbol() | ||
@type state :: State.t() | ||
|
||
@spec start_link(product) :: GenServer.on_start() | ||
def start_link(product) do | ||
state = %State{venue: product.venue_id, symbol: product.symbol, table: %{}} | ||
name = to_name(product.venue_id, product.venue_symbol) | ||
|
||
GenServer.start_link(__MODULE__, state, name: name) | ||
end | ||
|
||
@spec to_name(venue_id, venue_symbol) :: atom | ||
def to_name(venue, symbol), do: :"#{__MODULE__}_#{venue}_#{symbol}" | ||
|
||
@impl true | ||
def init(state), do: {:ok, state} | ||
|
||
@impl true | ||
def handle_cast({:snapshot, data, received_at}, state) do | ||
{data, received_at, state} | ||
|> build_change_set() | ||
|> OrderBook.replace() | ||
|
||
{:noreply, state} | ||
end | ||
|
||
defp build_change_set({{bids, asks}, received_at, state}) do | ||
normalized_bids = bids |> normalize_changes(:bid) | ||
normalized_asks = asks |> normalize_changes(:ask) | ||
|
||
%OrderBook.ChangeSet{ | ||
venue: state.venue, | ||
symbol: state.symbol, | ||
last_received_at: received_at, | ||
changes: Enum.concat(normalized_bids, normalized_asks) | ||
} | ||
end | ||
|
||
defp normalize_changes(data, :bid), do: data |> normalize_side(:bid) | ||
defp normalize_changes(data, :ask), do: data |> normalize_side(:ask) | ||
|
||
defp normalize_side(data, side) do | ||
data | ||
|> Enum.map(fn | ||
%{"limit_price" => raw_price, "size" => size} -> | ||
{price, _} = Float.parse(raw_price) | ||
{:upsert, side, price, size} | ||
end) | ||
end | ||
end |
Oops, something went wrong.