defmodule Polymarket.Data do @moduledoc """ Read-only wrappers for Polymarket's public Data API. All endpoints are public GETs against `Polymarket.Config.data_api_host/0` (`https://data-api.polymarket.com`). No authentication is required — the wallet address is passed as a query parameter, so callers can read any address they have, not just their own. This is the source of truth for fee-inclusive trade costs (`/activity`) and current holdings including unredeemed winners (`/positions`). The CLOB fills endpoint reports `size * price` and excludes taker fees; prefer Data API `usdcSize` when computing P&L on completed BUY → REDEEM cycles. ## Response shape and options All wrappers delegate to `Polymarket.HTTP.get/3`. See its `@moduledoc` for the response contract and the per-call options list (`:host`, `:plug`, `:timeout`, `:retry`, `:max_retries`, `:headers`). GET requests default to Req's `:transient` retry policy; pass `retry: false` to disable. """ @type result :: Polymarket.HTTP.result() # Defaults aligned with production usage — these values have shipped # against the live API without rejection. @default_size_threshold 0.1 @default_activity_limit 1000 @default_trades_limit 20 @doc """ Lists current positions for a wallet address. Returns positions with camelCase fields as sent by the Data API — including `size`, `avgPrice`, `currentValue`, and `redeemable`. This wrapper does **not** normalize keys to snake_case; pipe the result through `Polymarket.Activity` or your own mapper if you need that. ## Options * `:size_threshold` — minimum position size to return (default `0.1`). The Data API filters dust positions on the server side. * Plus any option supported by `Polymarket.HTTP.get/3`. > #### Pagination {: .info} > > This wrapper does not send `limit`/`offset`. The Data API defaults to > `limit=100`, so wallets with more than 100 positions are truncated by > the server. Paginated fetching is not yet exposed here. """ @spec get_positions(String.t(), keyword()) :: result() def get_positions(address, opts \\ []) when is_binary(address) do {size_threshold, opts} = Keyword.pop(opts, :size_threshold, @default_size_threshold) params = [user: address, sizeThreshold: size_threshold] request("/positions", Keyword.put(opts, :params, params)) end @doc """ Lists activity (trades + redemptions) for a wallet address. Activity events include: * `TRADE` — buys and sells with fee-inclusive `usdcSize` (the actual wallet debit/credit, including taker fees). * `REDEEM` — payouts on resolved markets. Records are keyed by `transactionHash`, not by order hash. ## Options * `:limit` — max events to return (default `1000`). * `:offset` — pagination offset. * Plus any option supported by `Polymarket.HTTP.get/3`. """ @spec get_activity(String.t(), keyword()) :: result() def get_activity(address, opts \\ []) when is_binary(address) do {limit, opts} = Keyword.pop(opts, :limit, @default_activity_limit) {offset, opts} = Keyword.pop(opts, :offset, nil) params = [user: address, limit: limit] |> maybe_put_param(:offset, offset) request("/activity", Keyword.put(opts, :params, params)) end @doc """ Lists public trade history for a wallet address. ## Options * `:limit` — max trades to return (default `20`). * `:after` — sent as-is to the API. **Note:** the live Data API `GET /trades` endpoint does not currently document an `after` filter, so this may be ignored server-side — do not rely on it for time-windowing until verified against the live API. * `:asset_id` — sent as-is. **Note:** the live `GET /trades` endpoint does not currently document an asset filter, so this may likewise be ignored server-side. * Plus any option supported by `Polymarket.HTTP.get/3`. """ @spec get_trades(String.t(), keyword()) :: result() def get_trades(address, opts \\ []) when is_binary(address) do {limit, opts} = Keyword.pop(opts, :limit, @default_trades_limit) {after_ts, opts} = Keyword.pop(opts, :after, nil) {asset_id, opts} = Keyword.pop(opts, :asset_id, nil) params = [user: address, limit: limit] |> maybe_put_param(:after, after_ts) |> maybe_put_param(:asset_id, asset_id) request("/trades", Keyword.put(opts, :params, params)) end # ── Private ── defp request(path, opts) do host = Keyword.get(opts, :host, Polymarket.Config.data_api_host()) Polymarket.HTTP.get(host, path, Keyword.delete(opts, :host)) end defp maybe_put_param(params, _key, nil), do: params defp maybe_put_param(params, key, value), do: Keyword.put(params, key, value) end