Market PnL
Market PnL
GET /api/operator/market-pnl
Requires both trades:read and redemptions:read, or *. Operator API keys and staff sessions are accepted. The operator is derived from authentication; operatorId and operator_id query parameters return 400 OPERATOR_SELECTOR_NOT_ALLOWED.
This read-only Opinion report groups actual wallet cash by operator user and resolved market, including users who sold their entire position before resolution. It is not position valuation, unpaid winning-share entitlement, operator profit, or monthly settlement. Public markets and the operator's own private markets are eligible; other operators' private markets are excluded.
Filters and pagination
All parameters are optional. There is no default date cutoff.
| Parameter | Values/default |
|---|---|
market_id | Exact positive integer market ID, maximum 2147483647; decimal digits only. |
external_user_id | Exact operator external user ID; URL-encode it. Leading/trailing query whitespace is trimmed. |
search | Case-insensitive literal substring of market title or slug. |
resolved_from_date | Inclusive YYYY-MM-DD lower bound in the operator timezone. |
resolved_to_date | Inclusive YYYY-MM-DD upper bound; implemented as exclusive next-day local midnight. |
sort | resolved_at_desc (default), resolved_at_asc, pnl_desc, pnl_asc, spent_desc, returned_desc. |
page | Default 1, maximum 10000. |
page_size | Default 25, maximum 100. |
Dates select markets, not individual wallet movements. All lifetime qualifying cash for a selected market contributes. resolved_at currently uses the market update timestamp, not a dedicated immutable resolution timestamp; metadata edits can change the date selection. Reversed date bounds return 400 INVALID_RESOLVED_DATE_RANGE. Invalid dates, ID, or sort return their INVALID_* code. Pagination uses the standard defaulting/capping rules.
curl -sS -G "$OPERATOR_API_BASE_URL/api/operator/market-pnl" \
-H "Authorization: Bearer $OPERATOR_API_KEY" \
--data-urlencode "market_id=51" \
--data-urlencode "external_user_id=operator-user-123" \
--data-urlencode "page_size=25"
Response
The envelope is { items, summary, pagination }. pagination contains numeric page, page_size, total, and total_pages (at least 1). total counts user/market groups, not trades.
Each item contains:
operator_user: numericid,external_user_id, nullabledisplay_name;market: numericid,slug,title;resolved_at: timestamp ornull;outcome: resolved outcome string;currency_code(currentlyIDRfor Opinion wallet cash);- the amounts and warning counts below.
| Field | JSON type | Meaning |
|---|---|---|
spent_minor | integer string | Succeeded debits minus succeeded debit reversals, including buy fees. |
sales_returned_minor | integer string | Succeeded order credits minus succeeded order credit reversals. |
redemption_returned_minor | integer string | Succeeded redemption credits minus succeeded redemption credit reversals. |
returned_minor | integer string | Sale returns plus redemption returns. |
pnl_minor | signed integer string | User cash PnL: returned minus spent. Positive means the user received more than they spent. |
quoted_fee_credits | integer string | Accumulated linked executed-trade fees in Polynion Credits, once per order. |
quoted_fee_minor | integer string | The same fees converted per order using historical external/credit amounts and flooring, then accumulated. |
pending_operation_count | integer | Unresolved wallet operations or source-level work awaiting reconciliation. |
failed_payout_count | integer | Source records with a failed credit/refund and a remaining positive unpaid amount; not the count of individual wallet attempts. |
failed_payout_minor | integer string | Remaining failed/unpaid credit or refund amount. |
All *_minor values are exact decimal integer strings in currency_code; IDR is zero-decimal. Preserve precision with integer/string arithmetic. quoted_fee_credits is a different unit (10000 Credits = 1 USDT) and must not be added to currency amounts.
summary repeats every amount, warning count, and currency_code across all filtered groups, plus numeric user_market_count, user_count, and market_count, and string timezone. It is not limited to the current page. Responses use Cache-Control: private, no-store.
Cash versus quoted fees
Only succeeded wallet operations enter cash amounts. Retries that reuse one operation do not create another movement, but distinct successful operations are each counted, even if they represent an accidental duplicate payment. Source status does not erase confirmed cash. Reversals offset the corresponding cash bucket, including compensated buys.
Both quoted-fee columns are reference-only. They come from executed trades independently of wallet-operation count or reconciliation state; they are not additional wallet debits and must not be subtracted again from pnl_minor. Historical fee conversion uses the order's executed external amount, falling back to quoted/requested amount, divided by absolute executed Credits (or quoted Credits when execution Credits are unavailable), with per-order flooring. It never uses today's FX rate. Missing fee evidence contributes zero rather than proving that no fee was charged.
Pending or ambiguous work is not cash until confirmed. A failed/unpaid amount is not automatically a debt write-off or an applied payout. A zero pending count does not prove that every winning holding has been redeemed. PnL can change after resolution as wallet operations and reversals complete.
Use cash-flow triage for warning sources and redacted evidence. It additionally requires balance_operations:read. Use Resolutions for market exposure and Opinion Settlement Reports for monthly operator accounting; these reports answer different questions.
