> ## Documentation Index
> Fetch the complete documentation index at: https://docs.0xmonaco.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Portfolio Analytics

> Authoritative portfolio summary, performance, realized PnL, assets, and margin

```typescript theme={null}
import { MonacoSDK } from "@0xmonaco/core";

// Initialize SDK with wallet client (see Initialization guide)

// Current equity split plus period activity
await sdk.portfolio.getSummary({ period: "1m" });

// Transfer-neutral dollar PnL and sampled time-weighted return
await sdk.portfolio.getPerformance({ period: "1w" });

// Realized PnL split into perp closes, spot trades, funding, and fees
await sdk.portfolio.getRealizedPnl({ period: "all" });

// Per-asset balances priced by asset ID
await sdk.portfolio.getAssets({ period: "1d" });

// Current margin totals and per-risk-bucket projections (no period)
await sdk.portfolio.getMargin();
await sdk.portfolio.getMargin({ marginAccountId, includeAccounts: true });
```

These methods return authoritative calculations computed in Core — the same numbers across every client. They are application-scoped and require authentication.

`getSummary`, `getPerformance`, `getRealizedPnl`, `getAssets`, and the two `sdk.profile` methods below read from a dedicated replica, so they **may reflect replica lag** and can trail a live position or balance read by a short interval. Do not reconcile a number from this page against a live one to the cent. A reader outage returns a retryable `503` (gRPC `Unavailable`) rather than falling back to the writer, so treat unavailability as transient and retry. `getMargin()` is the exception: it is writer-backed rather than replica-backed, as is trade history. Its withdrawable-collateral bounds are still not unconditionally live, and the fallback has two scopes: if the matching engine serves no bounds at all, the account total and every risk bucket row fall back to a persisted reconstruction; if it serves bounds but one bucket is missing, only that row falls back. The reconstruction uses the same formulas the withdrawal gates enforce, but cannot see their resting-order reserves or the parent margin account's live allocation constraint, so treat a fallback value as an estimate rather than an enforceable bound. Do not make these analytics the sole input to a real-time control of your own — a withdrawal gate, a risk limit, or a liquidation decision; read the live surfaces for that.

Historical methods accept an optional `period` of `"1d"`, `"1w"`, `"1m"`, or `"all"`, defaulting to `all` when omitted. `getMargin()` is current-only and takes no period.

All decimal amounts and percentages are returned as strings. Every response carries a `window` (or `asOf`) describing the query cutoff and how much history the retained data actually covers. Dependent values that cannot be computed — a return over a window with no starting equity, or the value of an asset with no spot price — are **omitted** rather than reported as `0`; branch on their presence before displaying.

Two shared shapes appear across these responses:

* **`PortfolioWindow`** — `period`, `asOf` (query cutoff, ISO 8601), optional `dataThrough` / `requestedStartsAt` / `effectiveStartsAt` / `historyStartsAt`, and `coverage` (`"complete"`, `"partial"`, or `"none"`).
* **`PortfolioSeriesWindow`** — extends `PortfolioWindow` with `displayInterval` (`"5m"`/`"1h"`/`"4h"`/`"1d"`) and `calculationInterval` (`"1m"`/`"5m"`/`"15m"`/`"1h"`/`"4h"`/`"1d"`).

`valuationStatus` is `"complete"` when every input had a price and `"partial"` when at least one dependent value was omitted for a missing price.

***

<ResponseField name="getSummary" type="(params?: { period }) => Promise<PortfolioSummary>">
  Return the current equity breakdown plus trading activity over the requested window.

  Requires authentication.

  **Parameters:** `PortfolioPeriodParams` (optional):

  * `period`: `"1d" | "1w" | "1m" | "all"` — defaults to `all`

  **Returns:** `PortfolioSummary`:

  * `window`: `PortfolioWindow`
  * `valuationStatus`: `"complete" | "partial"`
  * `current`: current equity split — `perpsEquity` (string), plus optional `totalEquity`, `spotEquity`, `unrealizedPnl`, and `availableValue`
  * `activity`: period activity — `volume` (string), `tradeCount` (number), `orderCount` (number), `feesPaid` (string)

  **Example:**

  ```typescript theme={null}
  const summary = await sdk.portfolio.getSummary({ period: "1m" });
  console.log(summary.current.totalEquity ?? "—");
  console.log(`${summary.activity.tradeCount} trades, ${summary.activity.feesPaid} fees`);
  ```
</ResponseField>

<ResponseField name="getPerformance" type="(params?: { period }) => Promise<PortfolioPerformance>">
  Return transfer-neutral dollar PnL and a sampled time-weighted return (TWR) series for the
  window. Deposits and withdrawals are excluded from PnL so external flows do not distort return.

  For any period with a performance baseline, unpriceable external withdrawals are omitted from `withdrawals` and the dependent `deposits` total; successful responses with these omissions report `valuationStatus: "partial"`. A recognized quote asset without a valuation pair is valued at `1`, so it is not omitted for lacking a pair. Reconciliation checks still apply: if derived deposits become negative, the request fails as unavailable.

  Requires authentication.

  **Parameters:** `PortfolioPeriodParams` (optional):

  * `period`: `"1d" | "1w" | "1m" | "all"` — defaults to `all`

  **Returns:** `PortfolioPerformance`:

  * `window`: `PortfolioSeriesWindow`
  * `calculationMethod`: `"sampled_twr"`
  * `valuationStatus`: `"complete" | "partial"`
  * `startingEquity?`, `endingEquity?`: string
  * `deposits`, `withdrawals`, `netExternalFlows`: string
  * `pnl?`, `returnPct?`: string — omitted when the window lacks a starting equity to measure against
  * `maxDrawdown?`: `{ amount, pct, peakAt, troughAt }` — positive loss magnitude, peak-to-trough sampled-TWR percentage, and the peak/trough timestamps
  * `points`: `PortfolioPerformancePoint[]` — each `{ timestamp, equity, cumulativePnl, cumulativeReturnPct? }`

  **Example:**

  ```typescript theme={null}
  const perf = await sdk.portfolio.getPerformance({ period: "1w" });
  console.log(perf.returnPct ?? "n/a", perf.maxDrawdown?.pct ?? "0");
  ```
</ResponseField>

<ResponseField name="getRealizedPnl" type="(params?: { period }) => Promise<PortfolioRealizedPnl>">
  Return realized PnL for the window, split by source. Spot-withdrawal disposal PnL is excluded.

  Requires authentication.

  **Parameters:** `PortfolioPeriodParams` (optional):

  * `period`: `"1d" | "1w" | "1m" | "all"` — defaults to `all`

  **Returns:** `PortfolioRealizedPnl`:

  * `window`: `PortfolioSeriesWindow`
  * `bucketInterval`: `"1h" | "1d"` — the series bucket size (hourly for `1d`, daily otherwise)
  * `totals`: `PortfolioRealizedTotals` — `perpsClosedPnl`, `spotTradePnl`, `fundingPnl`, `feesPaid`, `netRealizedPnl` (all strings)
  * `closedTrades`: `{ count, winners, losers, breakEven, positions, winRatePct? }` — `winners`, `losers`, `breakEven`, and `winRatePct` are **position-based**: each position (one flat→open→flat round-trip) is classified once by its net realized PnL after all fees, not per closing fill. `positions` is the total classified positions (`winners + losers + breakEven`); `count` stays fill-based (closing fills in the window)
  * `bestDay?`: `{ date, netRealizedPnl }` — `date` is a UTC `YYYY-MM-DD` calendar day
  * `points`: `PortfolioRealizedPnlPoint[]` — the `totals` fields plus a `timestamp`, one per bucket

  **Example:**

  ```typescript theme={null}
  const rp = await sdk.portfolio.getRealizedPnl({ period: "all" });
  console.log(rp.totals.netRealizedPnl, rp.closedTrades.winRatePct ?? "—");
  ```
</ResponseField>

<ResponseField name="getAssets" type="(params?: { period }) => Promise<PortfolioAssets>">
  Return per-asset spot holdings priced by asset ID, with allocation percentages and window-scoped
  spot performance. Rows for assets without a spot price are returned `unpriced`, with `value`,
  `allocationPct`, and `markPrice` omitted.

  Requires authentication.

  **Parameters:** `PortfolioPeriodParams` (optional):

  * `period`: `"1d" | "1w" | "1m" | "all"` — defaults to `all`

  **Returns:** `PortfolioAssets`:

  * `window`: `PortfolioSeriesWindow`
  * `valuationStatus`: `"complete" | "partial"`
  * `totalSpotEquity?`: string — omitted when any held asset is unpriced
  * `performance`: `{ pnl?, returnPct?, calculationMethod: "sampled_twr" }`
  * `assets`: `PortfolioAssetRow[]` — each `{ assetId, symbol, totalBalance, availableBalance, lockedBalance, markPrice?, value?, allocationPct?, priceAsOf?, pricingStatus, avgCost?, unrealizedPnl?, unrealizedRoe?, cumRealized? }` where `pricingStatus` is `"priced"` or `"unpriced"`. The spot cost-basis fields — `avgCost` (weighted-average cost per unit), `unrealizedPnl` (`costBasisQuantity × (markPrice − avgCost)`), `unrealizedRoe` (percentage return on cost basis), and `cumRealized` (lifetime realized PnL, gross of fees) — are present when the asset has a cost-basis checkpoint; `unrealizedPnl` and `unrealizedRoe` additionally require a mark price, and `unrealizedRoe` additionally requires a non-zero cost basis

  **Example:**

  ```typescript theme={null}
  const { assets, totalSpotEquity } = await sdk.portfolio.getAssets({ period: "1d" });
  for (const a of assets) {
    if (a.pricingStatus === "priced") console.log(a.symbol, a.value, a.allocationPct);
  }
  ```
</ResponseField>

<ResponseField name="getMargin" type="(params?: PortfolioMarginParams) => Promise<PortfolioMargin>">
  Return the current margin picture: parent-account totals and per-risk-bucket projections, kept
  separate. Parent free collateral does not include risk-bucket free collateral. This method is
  current-only and takes no period. By default it describes the default margin account; pass
  `marginAccountId` to describe one of your sub-accounts instead, and `includeAccounts` to add the
  owner rollup across every open margin account.

  Requires authentication.

  **Parameters:** `PortfolioMarginParams` (optional):

  * `marginAccountId?`: string — one of your margin accounts (the default one or a sub-account) to describe instead of the default one; `total`, `riskBuckets` and the `positions` counts are then that account's own. Without it, `total` and `riskBuckets` describe the default margin account while the `positions` counts stay owner-wide across every margin account
  * `includeAccounts?`: boolean — also return `accounts` and `ownerTotal`

  **Returns:** `PortfolioMargin`:

  * `asOf`: string — snapshot timestamp (ISO 8601)
  * `marginAccountId?`: string — the margin account `total` and `riskBuckets` describe (and the `positions` counts too, when the request names `marginAccountId`; otherwise those stay owner-wide); absent before your first margin account exists
  * `accounts?`: `PortfolioMarginAccount[]` — with `includeAccounts`: every open margin account, the default one first, then sub-accounts by key; each `{ marginAccountId, subaccountKey, total }`, where `subaccountKey` is `"default"` for the default margin account and `total` is a `PortfolioMarginTotal` whose `withdrawableCollateral` is the persisted reconstruction of the engine's bound
  * `ownerTotal?`: `PortfolioMarginTotal` — with `includeAccounts`: the sum of `accounts`' totals
  * `total`: `PortfolioMarginTotal` — `equity`, `collateralBalance`, `parentAvailableCollateral` (parent-only free collateral), `withdrawableCollateral`, `initialMarginRequired`, `maintenanceMarginRequired`, `unrealizedPnl` (all strings)
  * `positions`: `{ total, cross, isolated }` — open-position counts
  * `riskBuckets`: `PortfolioRiskBucket[]` — each `{ riskBucketId, marginMode, tradingPairId?, collateral, freeCollateral, withdrawableCollateral, maxRemovableCollateral?, availableOrderCollateral?, initialMarginRequired, maintenanceMarginRequired, unrealizedPnl, positionCount, copyFollowId?, copyLeaderHandle? }`. `availableOrderCollateral` is the engine-served capacity for **new** orders in that bucket (parent auto top-up included, not additive across rows — see the margin-accounts docs for the full semantics); it is present only on live-engine reads, so handle `undefined`. `maxRemovableCollateral` is the largest removal the engine accepts when it may also lower the posted margin of an isolated bucket's one open position (equal to `withdrawableCollateral` otherwise), also live-engine only. `marginMode` (`"ISOLATED"` / `"CROSS"`) is always present and lets you attribute bucket margin to cross or isolated without a second call; `tradingPairId` is present only on `ISOLATED` rows, since a cross bucket spans the pairs selected into it and is scoped to none individually (read that membership from the margin-accounts `selectedTradingPairIds`). `copyFollowId` and `copyLeaderHandle` are present only on a copy risk bucket row — the follow that funds it and the followed leader's custom TraderCode handle, when still held — and omitted on every other row.

  <Warning>
    `marginMode` does **not** reconcile with the `positions.cross` / `positions.isolated` counts, and grouping `riskBuckets` by mode will not reproduce them. Without `marginAccountId`, those counts are computed by a separate query over **every** margin account for your user/application pair, while `riskBuckets` comes from the single oldest (parent) account, and they attribute a legacy position carrying no risk-bucket reference to cross whether or not a cross bucket row exists. Treat them as account-wide totals, not as a checksum over the rows. For the same reason these labels are not a join key against the margin-accounts surface — that surface emits rows this one has no counterpart for, and the two disagree on legacy-position attribution on persisted-fallback reads.
  </Warning>

  `parentAvailableCollateral` is an **equity** figure: it credits settled realized PnL, accrued
  funding, and unrealized gains, none of which are principal a transfer can move. The bound the
  engine actually enforces on transfers-out and withdrawals — and, because allocating collateral to
  a risk bucket passes the same principal-gated door, the effective ceiling on funding a new
  isolated or first-use position — is `total.withdrawableCollateral`. The two can differ without
  limit (e.g. a large banked realized PnL lifts `parentAvailableCollateral` far above what any
  transfer would accept). Size transfers, withdrawals, and new isolated/first-use positions from
  `withdrawableCollateral`; treat `parentAvailableCollateral`/`freeCollateral` as trading-margin
  headroom for existing cross exposure.

  **Example:**

  ```typescript theme={null}
  const margin = await sdk.portfolio.getMargin();
  console.log(margin.total.withdrawableCollateral); // what can actually move
  for (const b of margin.riskBuckets) console.log(b.riskBucketId, b.withdrawableCollateral);

  // One sub-account's own view, plus the rollup across every open margin account:
  const rollup = await sdk.portfolio.getMargin({ marginAccountId: hedgeMarginAccountId, includeAccounts: true });
  for (const a of rollup.accounts ?? []) console.log(a.subaccountKey, a.total.equity);
  console.log(rollup.ownerTotal?.equity);
  ```

  **Throws:** `400` when `marginAccountId` is not a UUID; `404` when it is not one of your margin
  accounts; `401` without authentication; `429` past the read budget; `503` when portfolio data is
  unavailable; `500` on an internal error.
</ResponseField>

## Aggregate stats and chart (`sdk.profile`)

Two earlier portfolio methods live under the `sdk.profile` namespace. They return a single aggregate snapshot and a simple time-bucketed series, and remain supported. Prefer the authoritative `sdk.portfolio` methods above for richer breakdowns and explicit history coverage. The v1.0.21 fee-total corrections apply to both methods below.

```typescript theme={null}
// Aggregate stats over a period
await sdk.profile.getPortfolioStats("30d");

// Time-bucketed chart series for a metric
await sdk.profile.getPortfolioChart("7d", "pnl");
```

<ResponseField name="getPortfolioStats" type="(period: PortfolioPeriod) => Promise<PortfolioStats>">
  Fetch aggregate trading statistics for the authenticated user over a given time period.

  **Parameters:**

  * `period`: `"24h"` | `"7d"` | `"30d"` | `"all"`

  **Returns:**

  * `pnl`: Average-cost realized PnL over the period (quote asset, normalized) — net of fees, across spot and perps incl. funding; excludes unrealized PnL. Spot withdrawals are booked as fair-value disposals `(last trade price − avg cost) × qty`; deposits and withdrawals are priced at the last observed trade price in event order; transfers that predate any trade on the pair are PnL-neutral; short-entry fees reduce short profits
  * `volume`: Total trading volume (quote asset, normalized)
  * `totalTrades`: Total number of trades executed
  * `totalOrders`: Total number of orders placed
  * `feesPaid`: Total fees paid (quote asset, normalized)
  * `totalEquity`: Total equity (quote asset, normalized) — spot balances plus perpetuals (margin) account equity
  * `spotEquity`: Spot account equity (quote asset, normalized)
  * `perpsEquity`: Perpetuals (margin) account equity (quote asset, normalized)
  * `winLossRatio`: Position-level win rate — winning positions over winning + losing positions, break-even excluded (`number`); each position (one flat→open→flat round-trip) is classified once by its net realized PnL after all fees, not per fill; `null` when the period has no closed positions
  * `maxDrawdown`: Peak-to-trough drawdown on running PnL; `null` when the period has no closed trades
</ResponseField>

<ResponseField name="getPortfolioChart" type="(period: PortfolioPeriod, metric: PortfolioMetric) => Promise<PortfolioChartResponse>">
  Fetch a time-bucketed chart series for a given period and metric.

  **Parameters:**

  * `period`: `"24h"` | `"7d"` | `"30d"` | `"all"`
  * `metric`: `"volume"` | `"pnl"`

  **Returns:**

  * `metric`: The metric returned
  * `period`: The period requested
  * `data`: Array of `{ timestamp: string, value: string }` data points (ISO 8601 bucket start times)
</ResponseField>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.