Packages

An Elixir client for the Polymarket API.

Current section

Files

Jump to
ex_polymarket lib schemas market.ex
Raw

lib/schemas/market.ex

defmodule Polymarket.Schemas.Market do
@moduledoc """
A market returned by the Gamma REST API (e.g. `GET /markets/:id`).
The Gamma API delivers its keys in `camelCase`; `from_attrs/1` normalises them
to the `snake_case` fields below. A handful of fields are delivered as
JSON-encoded strings (e.g. `outcomes` arrives as `"[\\"Up\\", \\"Down\\"]"`);
`from_attrs/1` decodes these via `Polymarket.Schemas.Coerce`.
Field coverage follows the Gamma OpenAPI `Market` schema. The remaining nested
relational objects (`events`, `categories`, `imageOptimized`, `iconOptimized`)
are intentionally omitted: `GET /markets/:id` does not return them, and
`events` is a large, self-referential `Market` <-> `Event` graph.
"""
use TypedEctoSchema
alias Polymarket.JsonUtil
alias Polymarket.Schemas.ClobReward
alias Polymarket.Schemas.Coerce
alias Polymarket.Schemas.FeeSchedule
alias Polymarket.Schemas.MarketMetadata
alias Polymarket.Schemas.Tag
@primary_key false
@derive Jason.Encoder
typed_embedded_schema do
# Identity / description
field(:id, :string)
field(:question, :string)
field(:question_id, :string)
field(:condition_id, :string)
field(:slug, :string)
field(:description, :string)
field(:resolution_source, :string)
field(:image, :string)
field(:icon, :string)
field(:market_maker_address, :string)
field(:submitted_by, :string)
field(:resolved_by, :string)
field(:creator, :string)
field(:created_by, :integer)
field(:updated_by, :integer)
field(:category, :string)
field(:subcategory, :string)
field(:market_type, :string)
field(:format_type, :string)
field(:market_group, :integer)
field(:curation_order, :integer)
field(:score, :integer)
field(:twitter_card_image, :string)
field(:twitter_card_location, :string)
field(:twitter_card_last_refreshed, :string)
field(:twitter_card_last_validated, :string)
field(:sponsor_name, :string)
field(:sponsor_image, :string)
field(:chart_color, :string)
field(:series_color, :string)
field(:mailchimp_tag, :string)
field(:category_mailchimp_tag, :string)
field(:disqus_thread, :string)
field(:past_slugs, :string)
field(:game_id, :string)
field(:team_aid, :string)
field(:team_bid, :string)
field(:group_item_range, :string)
# Scalar / range markets
field(:amm_type, :string)
field(:fee, :string)
field(:denomination_token, :string)
field(:lower_bound, :string)
field(:upper_bound, :string)
field(:lower_bound_date, :string)
field(:upper_bound_date, :string)
field(:x_axis_value, :string)
field(:y_axis_value, :string)
# Status flags
field(:active, :boolean)
field(:closed, :boolean)
field(:archived, :boolean)
field(:new, :boolean)
field(:featured, :boolean)
field(:restricted, :boolean)
field(:ready, :boolean)
field(:funded, :boolean)
field(:approved, :boolean)
field(:cyom, :boolean)
field(:deploying, :boolean)
field(:pending_deployment, :boolean)
field(:automatically_active, :boolean)
field(:automatically_resolved, :boolean)
field(:manual_activation, :boolean)
field(:accepting_orders, :boolean)
field(:enable_order_book, :boolean)
field(:clear_book_on_start, :boolean)
field(:has_reviewed_dates, :boolean)
field(:fees_enabled, :boolean)
field(:holding_rewards_enabled, :boolean)
field(:rfq_enabled, :boolean)
field(:requires_translation, :boolean)
field(:pager_duty_notification_enabled, :boolean)
field(:neg_risk, :boolean)
field(:neg_risk_other, :boolean)
field(:show_gmp_series, :boolean)
field(:show_gmp_outcome, :boolean)
field(:wide_format, :boolean)
field(:ready_for_cron, :boolean)
field(:comments_enabled, :boolean)
field(:notifications_enabled, :boolean)
field(:sent_discord, :boolean)
field(:fpmm_live, :boolean)
# Dates
field(:start_date, :utc_datetime_usec)
field(:end_date, :utc_datetime_usec)
field(:start_date_iso, :date)
field(:end_date_iso, :date)
field(:created_at, :utc_datetime_usec)
field(:updated_at, :utc_datetime_usec)
field(:closed_time, :utc_datetime_usec)
field(:accepting_orders_timestamp, :utc_datetime_usec)
field(:deploying_timestamp, :utc_datetime_usec)
field(:event_start_time, :utc_datetime_usec)
field(:game_start_time, :utc_datetime_usec)
field(:ready_timestamp, :utc_datetime_usec)
field(:funded_timestamp, :utc_datetime_usec)
field(:scheduled_deployment_timestamp, :utc_datetime_usec)
# The spec types both of these as plain strings (no `date-time` format).
# `GET /markets` returns ISO timestamps for `uma_end_date`, but the events
# endpoint returns free-form dates (e.g. "April 25, 2022") for older markets,
# so it is kept a string. The `*_iso` fields, by contrast, the fixtures prove
# are dates.
field(:uma_end_date, :string)
field(:uma_end_date_iso, :string)
# Pricing / order book state
field(:best_bid, :float)
field(:best_ask, :float)
field(:spread, :float)
field(:last_trade_price, :float)
field(:one_hour_price_change, :float)
field(:one_day_price_change, :float)
field(:one_week_price_change, :float)
field(:one_month_price_change, :float)
field(:one_year_price_change, :float)
field(:competitive, :float)
field(:line, :float)
# Order configuration
field(:order_min_size, :integer)
field(:order_price_min_tick_size, :float)
field(:combo_status, :string)
field(:custom_liveness, :integer)
field(:seconds_delay, :integer)
field(:group_item_threshold, :string)
field(:group_item_title, :string)
field(:sports_market_type, :string)
# Liquidity / volume (the `*_num`/`*_clob` variants are numeric; the bare
# `liquidity`/`volume` fields arrive as strings and are kept verbatim).
field(:liquidity, :string)
field(:liquidity_num, :float)
field(:liquidity_amm, :float)
field(:liquidity_clob, :float)
field(:volume, :string)
field(:volume_num, :float)
field(:volume_clob, :float)
field(:volume24hr, :float)
field(:volume24hr_clob, :float)
field(:volume1wk, :float)
field(:volume1wk_clob, :float)
field(:volume1mo, :float)
field(:volume1mo_clob, :float)
field(:volume1yr, :float)
field(:volume1yr_clob, :float)
field(:volume_amm, :float)
field(:volume24hr_amm, :float)
field(:volume1wk_amm, :float)
field(:volume1mo_amm, :float)
field(:volume1yr_amm, :float)
# Fees / rewards
field(:fee_type, :string)
field(:maker_base_fee, :integer)
field(:taker_base_fee, :integer)
field(:maker_rebates_fee_share_bps, :integer)
field(:rewards_min_size, :integer)
field(:rewards_max_spread, :float)
# UMA resolution
field(:uma_bond, :string)
field(:uma_reward, :string)
field(:uma_resolution_status, :string)
field(:neg_risk_request_id, :string)
field(:neg_risk_market_id, :string)
# Array fields. `outcomes`/`outcome_prices`/`clob_token_ids`/
# `uma_resolution_statuses` arrive as JSON-encoded strings; `position_ids`
# arrives as a real array.
field(:outcomes, {:array, :string})
field(:short_outcomes, {:array, :string})
field(:outcome_prices, {:array, :float})
field(:clob_token_ids, {:array, :string})
field(:uma_resolution_statuses, {:array, :string})
field(:position_ids, {:array, :string})
embeds_one(:fee_schedule, FeeSchedule)
embeds_one(:market_metadata, MarketMetadata)
embeds_many(:tags, Tag)
embeds_many(:clob_rewards, ClobReward)
end
@required [:id, :condition_id, :slug]
@doc """
Builds a `Market` from the raw (JSON-decoded) Gamma attributes. Keys may be in
`camelCase` (atom or string); they are normalised to the `snake_case` fields
and each value is coerced to its field type. 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
attrs = JsonUtil.snake_case_keys(attrs)
%__MODULE__{
id: attrs["id"],
question: attrs["question"],
question_id: attrs["question_id"],
condition_id: attrs["condition_id"],
slug: attrs["slug"],
description: attrs["description"],
resolution_source: attrs["resolution_source"],
image: attrs["image"],
icon: attrs["icon"],
market_maker_address: attrs["market_maker_address"],
submitted_by: attrs["submitted_by"],
resolved_by: attrs["resolved_by"],
creator: attrs["creator"],
created_by: Coerce.to_int(attrs["created_by"]),
updated_by: Coerce.to_int(attrs["updated_by"]),
category: attrs["category"],
subcategory: attrs["subcategory"],
market_type: attrs["market_type"],
format_type: attrs["format_type"],
market_group: Coerce.to_int(attrs["market_group"]),
curation_order: Coerce.to_int(attrs["curation_order"]),
score: Coerce.to_int(attrs["score"]),
twitter_card_image: attrs["twitter_card_image"],
twitter_card_location: attrs["twitter_card_location"],
twitter_card_last_refreshed: attrs["twitter_card_last_refreshed"],
twitter_card_last_validated: attrs["twitter_card_last_validated"],
sponsor_name: attrs["sponsor_name"],
sponsor_image: attrs["sponsor_image"],
chart_color: attrs["chart_color"],
series_color: attrs["series_color"],
mailchimp_tag: attrs["mailchimp_tag"],
category_mailchimp_tag: attrs["category_mailchimp_tag"],
disqus_thread: attrs["disqus_thread"],
past_slugs: attrs["past_slugs"],
game_id: attrs["game_id"],
team_aid: attrs["team_aid"],
team_bid: attrs["team_bid"],
group_item_range: attrs["group_item_range"],
amm_type: attrs["amm_type"],
fee: attrs["fee"],
denomination_token: attrs["denomination_token"],
lower_bound: attrs["lower_bound"],
upper_bound: attrs["upper_bound"],
lower_bound_date: attrs["lower_bound_date"],
upper_bound_date: attrs["upper_bound_date"],
x_axis_value: attrs["x_axis_value"],
y_axis_value: attrs["y_axis_value"],
active: attrs["active"],
closed: attrs["closed"],
archived: attrs["archived"],
new: attrs["new"],
featured: attrs["featured"],
restricted: attrs["restricted"],
ready: attrs["ready"],
funded: attrs["funded"],
approved: attrs["approved"],
cyom: attrs["cyom"],
deploying: attrs["deploying"],
pending_deployment: attrs["pending_deployment"],
automatically_active: attrs["automatically_active"],
automatically_resolved: attrs["automatically_resolved"],
manual_activation: attrs["manual_activation"],
accepting_orders: attrs["accepting_orders"],
enable_order_book: attrs["enable_order_book"],
clear_book_on_start: attrs["clear_book_on_start"],
has_reviewed_dates: attrs["has_reviewed_dates"],
fees_enabled: attrs["fees_enabled"],
holding_rewards_enabled: attrs["holding_rewards_enabled"],
rfq_enabled: attrs["rfq_enabled"],
requires_translation: attrs["requires_translation"],
pager_duty_notification_enabled: attrs["pager_duty_notification_enabled"],
neg_risk: attrs["neg_risk"],
neg_risk_other: attrs["neg_risk_other"],
show_gmp_series: attrs["show_gmp_series"],
show_gmp_outcome: attrs["show_gmp_outcome"],
wide_format: attrs["wide_format"],
ready_for_cron: attrs["ready_for_cron"],
comments_enabled: attrs["comments_enabled"],
notifications_enabled: attrs["notifications_enabled"],
sent_discord: attrs["sent_discord"],
fpmm_live: attrs["fpmm_live"],
start_date: Coerce.to_datetime(attrs["start_date"]),
end_date: Coerce.to_datetime(attrs["end_date"]),
start_date_iso: Coerce.to_date(attrs["start_date_iso"]),
end_date_iso: Coerce.to_date(attrs["end_date_iso"]),
created_at: Coerce.to_datetime(attrs["created_at"]),
updated_at: Coerce.to_datetime(attrs["updated_at"]),
closed_time: Coerce.to_datetime(attrs["closed_time"]),
accepting_orders_timestamp: Coerce.to_datetime(attrs["accepting_orders_timestamp"]),
deploying_timestamp: Coerce.to_datetime(attrs["deploying_timestamp"]),
event_start_time: Coerce.to_datetime(attrs["event_start_time"]),
game_start_time: Coerce.to_datetime(attrs["game_start_time"]),
ready_timestamp: Coerce.to_datetime(attrs["ready_timestamp"]),
funded_timestamp: Coerce.to_datetime(attrs["funded_timestamp"]),
scheduled_deployment_timestamp: Coerce.to_datetime(attrs["scheduled_deployment_timestamp"]),
uma_end_date: attrs["uma_end_date"],
uma_end_date_iso: attrs["uma_end_date_iso"],
best_bid: Coerce.to_float(attrs["best_bid"]),
best_ask: Coerce.to_float(attrs["best_ask"]),
spread: Coerce.to_float(attrs["spread"]),
last_trade_price: Coerce.to_float(attrs["last_trade_price"]),
one_hour_price_change: Coerce.to_float(attrs["one_hour_price_change"]),
one_day_price_change: Coerce.to_float(attrs["one_day_price_change"]),
one_week_price_change: Coerce.to_float(attrs["one_week_price_change"]),
one_month_price_change: Coerce.to_float(attrs["one_month_price_change"]),
one_year_price_change: Coerce.to_float(attrs["one_year_price_change"]),
competitive: Coerce.to_float(attrs["competitive"]),
line: Coerce.to_float(attrs["line"]),
order_min_size: Coerce.to_int(attrs["order_min_size"]),
order_price_min_tick_size: Coerce.to_float(attrs["order_price_min_tick_size"]),
combo_status: attrs["combo_status"],
custom_liveness: Coerce.to_int(attrs["custom_liveness"]),
seconds_delay: Coerce.to_int(attrs["seconds_delay"]),
group_item_threshold: attrs["group_item_threshold"],
group_item_title: attrs["group_item_title"],
sports_market_type: attrs["sports_market_type"],
liquidity: attrs["liquidity"],
liquidity_num: Coerce.to_float(attrs["liquidity_num"]),
liquidity_amm: Coerce.to_float(attrs["liquidity_amm"]),
liquidity_clob: Coerce.to_float(attrs["liquidity_clob"]),
volume: attrs["volume"],
volume_num: Coerce.to_float(attrs["volume_num"]),
volume_clob: Coerce.to_float(attrs["volume_clob"]),
volume24hr: Coerce.to_float(attrs["volume24hr"]),
volume24hr_clob: Coerce.to_float(attrs["volume24hr_clob"]),
volume1wk: Coerce.to_float(attrs["volume1wk"]),
volume1wk_clob: Coerce.to_float(attrs["volume1wk_clob"]),
volume1mo: Coerce.to_float(attrs["volume1mo"]),
volume1mo_clob: Coerce.to_float(attrs["volume1mo_clob"]),
volume1yr: Coerce.to_float(attrs["volume1yr"]),
volume1yr_clob: Coerce.to_float(attrs["volume1yr_clob"]),
volume_amm: Coerce.to_float(attrs["volume_amm"]),
volume24hr_amm: Coerce.to_float(attrs["volume24hr_amm"]),
volume1wk_amm: Coerce.to_float(attrs["volume1wk_amm"]),
volume1mo_amm: Coerce.to_float(attrs["volume1mo_amm"]),
volume1yr_amm: Coerce.to_float(attrs["volume1yr_amm"]),
fee_type: attrs["fee_type"],
maker_base_fee: Coerce.to_int(attrs["maker_base_fee"]),
taker_base_fee: Coerce.to_int(attrs["taker_base_fee"]),
maker_rebates_fee_share_bps: Coerce.to_int(attrs["maker_rebates_fee_share_bps"]),
rewards_min_size: Coerce.to_int(attrs["rewards_min_size"]),
rewards_max_spread: Coerce.to_float(attrs["rewards_max_spread"]),
uma_bond: attrs["uma_bond"],
uma_reward: attrs["uma_reward"],
uma_resolution_status: attrs["uma_resolution_status"],
neg_risk_request_id: attrs["neg_risk_request_id"],
neg_risk_market_id: attrs["neg_risk_market_id"],
outcomes: Coerce.to_string_list(attrs["outcomes"]),
short_outcomes: Coerce.to_string_list(attrs["short_outcomes"]),
outcome_prices: Coerce.to_float_list(attrs["outcome_prices"]),
clob_token_ids: Coerce.to_string_list(attrs["clob_token_ids"]),
uma_resolution_statuses: Coerce.to_string_list(attrs["uma_resolution_statuses"]),
position_ids: attrs["position_ids"],
fee_schedule: Coerce.embed(FeeSchedule, attrs["fee_schedule"]),
market_metadata: Coerce.embed(MarketMetadata, attrs["market_metadata"]),
tags: Coerce.embed_list(Tag, attrs["tags"]),
clob_rewards: Coerce.embed_list(ClobReward, attrs["clob_rewards"])
}
|> Coerce.require_fields(@required)
end
end