Current section
Files
Jump to
Current section
Files
lib/schemas/book_event.ex
defmodule Polymarket.Schemas.BookEvent do
@moduledoc """
Book event. A full order book snapshot for an asset.
"""
use TypedEctoSchema
alias Polymarket.Schemas.Ask
alias Polymarket.Schemas.Bid
alias Polymarket.Schemas.BookEvent
alias Polymarket.Schemas.Coerce
@primary_key false
@derive Jason.Encoder
typed_embedded_schema do
field(:market, :string)
field(:asset_id, :string)
field(:hash, :string)
# Unix epoch in milliseconds, delivered as a string (e.g. "1779658820355").
field(:timestamp, :integer)
field(:event_type, :string)
field(:last_trade_price, :float)
field(:tick_size, :float)
embeds_many(:bids, Bid)
embeds_many(:asks, Ask)
end
@required [:market, :asset_id, :hash, :timestamp, :event_type]
@doc """
Builds a `BookEvent` from a decoded (atom-keyed) websocket message.
Constructs the struct directly — no `Ecto.Changeset` — for the websocket hot
path. Numeric fields (delivered as JSON strings) are coerced via
`Polymarket.Schemas.Coerce`. Returns `{:error, {:missing_fields, fields}}` when
a required field is absent.
"""
@spec from_attrs(map()) :: {:ok, t()} | {:error, {:missing_fields, [atom()]}}
def from_attrs(attrs) do
%BookEvent{
market: attrs[:market],
asset_id: attrs[:asset_id],
hash: attrs[:hash],
timestamp: Coerce.to_int(attrs[:timestamp]),
event_type: attrs[:event_type],
last_trade_price: Coerce.to_float(attrs[:last_trade_price]),
tick_size: Coerce.to_float(attrs[:tick_size]),
bids: bids(attrs[:bids]),
asks: asks(attrs[:asks])
}
|> Coerce.require_fields(@required)
end
@spec bids([map()] | nil) :: [Bid.t()]
defp bids(nil), do: []
defp bids(levels) do
Enum.map(levels, fn level ->
%Bid{price: Coerce.to_float(level[:price]), size: Coerce.to_float(level[:size])}
end)
end
@spec asks([map()] | nil) :: [Ask.t()]
defp asks(nil), do: []
defp asks(levels) do
Enum.map(levels, fn level ->
%Ask{price: Coerce.to_float(level[:price]), size: Coerce.to_float(level[:size])}
end)
end
end