PearPear
API IntegrationTrade Activity

Portfolio

Read account-level performance — overview metrics, interval buckets, risk ratios, and a daily series.

Portfolio answers "how is this account doing", not "how did this trade do". Every other read in Trade Activity is scoped to one object: a fill, an execution, a position. These two endpoints aggregate all of them.

EndpointSDKAnswers
GET /portfoliosdk.core.portfolio.overviewThe scoreboard. Volume, open interest, realized and unrealized PnL, fees, win and loss counts, plus prebuilt chart buckets.
GET /portfolio/analyticssdk.core.portfolio.analyticsThe breakdown. Risk ratios, per-asset performance, a per-basket ranking, and a daily series.

Use Closed Position when you want one trade. Use these when you want the book.

Both endpoints need two headers:

HeaderValue
x-api-keyYour API key. A read key is enough — both endpoints are GET. A session or bearer token also works.
x-trade-account-idThe trade account to report on. Required.

Neither endpoint pages. There is no limit, no cursor, and no nextCursor. Each returns the whole aggregate in one response. Neither accepts connector either; the venue comes from the trade account.

Every number on this page is a JSON number, not a decimal string. Positions, fills, and closed trades return decimals as strings such as "+41.72". Portfolio returns 41.72. If you feed both into the same parser, portfolio is the one that breaks it. A JSON number is a double, so a very large volume loses precision. Treat these figures as reporting values, not as accounting inputs.

GET /portfolio

curl "https://pro-gateway.pearprotocol.io/portfolio?startDate=2026-01-01&endDate=2026-01-15" \
  -H "x-api-key: $PEAR_API_KEY" \
  -H "x-trade-account-id: $TRADE_ACCOUNT_ID"
{
  "intervals": null,
  "overall": {
    "totalWinningTradesCount": 38,
    "totalLosingTradesCount": 21,
    "totalWinningUsd": 4210.77,
    "totalLosingUsd": 1830.42,
    "currentOpenInterest": 8420.15,
    "currentTotalVolume": 1284500.5,
    "unrealizedPnl": -112.38,
    "realizedPnl": 2380.35,
    "totalFees": 318.94,
    "totalTrades": 59
  }
}

Query parameters

ParameterValues
startDateLower bound on the window. Absent means unbounded.
endDateUpper bound. Absent means now.
sourceall (default) or pear. pear counts only fills Pear placed, so volume excludes trades you made directly on the exchange. It changes volume only — PnL, fees, and trade counts are unaffected.

Both dates accept an ISO 8601 string or an epoch-millisecond number. An unparseable date is not a 400. It is silently dropped, and the request behaves as if you never sent it.

overall

FieldMeaning
totalTradesClosed trades in the window.
totalWinningTradesCountTrades with positive net PnL.
totalLosingTradesCountTrades with negative net PnL.
totalWinningUsdSummed net PnL of the winners.
totalLosingUsdSummed net PnL of the losers, as a positive number. It is an absolute value, not a signed loss.
realizedPnlNet realized PnL over the window, after fees.
totalFeesFees on those closed trades.
currentTotalVolumeFill notional in the window. Honours source.
currentOpenInterestMark-price notional of everything open right now.
unrealizedPnlUnrealized PnL of everything open right now.

Two things to read carefully:

  • currentOpenInterest and unrealizedPnl ignore the date window. They are a live snapshot taken when the request arrives. The other eight fields are window-scoped. A query for last March still returns today's open book.
  • totalWinningTradesCount + totalLosingTradesCount can be less than totalTrades. A trade that closed at exactly zero net PnL is neither a win nor a loss, but it is still a trade.

intervals

intervals holds prebuilt chart buckets so a dashboard can draw five sparklines from one request.

intervals is null whenever startDate or endDate resolves to a real date. This is not "null when there is nothing to bucket". A custom window returns overall for that exact range and no buckets at all, even on a busy account. Only a bare GET /portfolio — or one whose dates were unparseable and therefore dropped — returns buckets.

When present, intervals carries five arrays of identical buckets:

KeyBuckets
oneDay24 hourly buckets, ending with the current UTC hour.
oneWeek7 daily buckets, ending today (UTC).
oneMonth30 daily buckets, ending today (UTC).
oneYear12 monthly buckets, ending with the current month.
all10 equal buckets spanning your first confirmed fill to today. The last one can end in the future.

Every bucket has the same shape:

{
  "periodStart": "2026-01-15T09:00:00.000Z",
  "periodEnd": "2026-01-15T10:00:00.000Z",
  "volume": 12500,
  "openInterest": 8420.15,
  "winningTradesCount": 1,
  "winningTradesUsd": 42.5,
  "losingTradesCount": 0,
  "losingTradesUsd": 0
}

periodStart is inclusive and periodEnd is exclusive. volume is the fill notional in the bucket, and the trade counts and sums follow the same rules as overall.

openInterest is the same value in every bucket. It is today's open interest repeated, not the open interest as it stood in that period. Do not plot it as a time series.

GET /portfolio/analytics

curl "https://pro-gateway.pearprotocol.io/portfolio/analytics?interval=1m&rollingWindowDays=14" \
  -H "x-api-key: $PEAR_API_KEY" \
  -H "x-trade-account-id: $TRADE_ACCOUNT_ID"
{
  "config": {
    "rollingWindowDays": 14,
    "startDate": "2025-12-17T00:00:00.000Z",
    "endDate": "2026-01-15T11:04:22.881Z",
    "fundingSynced": true
  },
  "portfolio": {
    "realizedOnly": {
      "pnlAbsolute": 2380.35,
      "pnlPercent": 0.0412,
      "hitRate": 0.6441,
      "profitFactor": 2.3005,
      "avgWinSize": 110.81,
      "avgLossSize": -87.16,
      "avgReturnPerWin": 0.0231,
      "avgReturnPerLoss": -0.0164,
      "maxDrawdown": -0.1842,
      "sharpeRatio": 1.62,
      "sortinoRatio": 2.08
    },
    "realizedPlusUnrealized": {
      "pnlAbsolute": 2267.97,
      "pnlPercent": 0.0374,
      "hitRate": 0.6441,
      "profitFactor": 2.3005,
      "avgWinSize": 110.81,
      "avgLossSize": -87.16,
      "avgReturnPerWin": 0.0231,
      "avgReturnPerLoss": -0.0164,
      "maxDrawdown": null,
      "sharpeRatio": null,
      "sortinoRatio": null
    },
    "realizedHitRate": 0.6441,
    "profitFactor": 2.3005,
    "avgWinSize": 110.81,
    "avgLossSize": -87.16,
    "breakevenHitRate": 0.4403,
    "cushion": 0.2038
  },
  "assets": [
    {
      "asset": "0",
      "realizedOnly": {
        "pnlAbsolute": 1620.4,
        "pnlPercent": 0.0521,
        "hitRate": 0.6552,
        "profitFactor": 2.51,
        "avgWinSize": 96.4,
        "avgLossSize": -71.2,
        "avgReturnPerWin": 0.0248,
        "avgReturnPerLoss": -0.0177,
        "maxDrawdown": null,
        "sharpeRatio": null,
        "sortinoRatio": null
      },
      "realizedPlusUnrealized": {
        "pnlAbsolute": 1508.02,
        "pnlPercent": 0.0446,
        "hitRate": 0.6552,
        "profitFactor": 2.51,
        "avgWinSize": 96.4,
        "avgLossSize": -71.2,
        "avgReturnPerWin": 0.0248,
        "avgReturnPerLoss": -0.0177,
        "maxDrawdown": null,
        "sharpeRatio": null,
        "sortinoRatio": null
      },
      "unrealizedReturnPerDay": -9.36
    }
  ],
  "pairs": [
    {
      "key": "L:0|S:1",
      "longAssets": ["0"],
      "shortAssets": ["1"],
      "trades": 24,
      "wins": 16,
      "losses": 8,
      "hitRate": 0.6667,
      "avgWinSize": 128.4,
      "avgLossSize": -94.2,
      "expectancy": 54.2,
      "totalPnl": 1300.8,
      "bookPercent": 0.5465
    }
  ],
  "series": [
    {
      "date": "2026-01-14",
      "trades": 3,
      "wins": 2,
      "losses": 1,
      "dayPnl": 84.2,
      "dayVolume": 42500,
      "dayFunding": -3.12,
      "dayNetPnl": 81.08,
      "hitRate": 0.6667,
      "winLossRatio": 2.14,
      "rollingHitRate": 0.6129,
      "rollingWinLossRatio": 1.87,
      "dailyReturn": 0.0084,
      "cumulativeReturn": 0.0398,
      "equity": 1.0398,
      "drawdown": 0,
      "rollingSharpe": 1.44,
      "rollingSortino": 1.91,
      "rollingVolatility": 0.0112
    },
    {
      "date": "2026-01-15",
      "trades": 0,
      "wins": 0,
      "losses": 0,
      "dayPnl": 0,
      "dayVolume": 0,
      "dayFunding": -1.04,
      "dayNetPnl": -1.04,
      "hitRate": null,
      "winLossRatio": null,
      "rollingHitRate": 0.6,
      "rollingWinLossRatio": 1.82,
      "dailyReturn": 0,
      "cumulativeReturn": 0.0398,
      "equity": 1.0398,
      "drawdown": 0,
      "rollingSharpe": 1.39,
      "rollingSortino": 1.86,
      "rollingVolatility": 0.0109
    }
  ]
}

The real response is longer. assets holds one entry per instrument you traded or still hold, pairs one per distinct basket, and series one per UTC day in the window — 30 points for the request above. Only the first entry of each is shown here.

asset, longAssets, and shortAssets hold instrument IDs, so "0" is BTC and "1" is ETH on Hyperliquid. See Instrument ID.

Query parameters

ParameterValues
startDate, endDateAs on GET /portfolio. endDate defaults to now.
interval1d, 1w, 1m, 1y, or all. Sets the start of the window when you do not send startDate.
rollingWindowDaysInteger. Window for every rolling statistic. Default 7.
includeSeriestrue or false. Default true.
sourceall (default) or pear. Affects series[].dayVolume only. Funding is an account-level cash flow and ignores it.

interval never appears in the response. It only picks a start date, and startDate overrides it:

intervalWindow starts at
1dThe start of the end day, UTC.
1w6 days before the end day.
1m29 days before the end day.
1y365 days before the end day.
allYour first confirmed fill, or 2020-01-01T00:00:00.000Z if you have none.
absent365 days before the end day. Same as 1y.

Two behaviours worth planning for:

  • rollingWindowDays is clamped to 7–365, not rejected. rollingWindowDays=1 becomes 7, and rollingWindowDays=9999 becomes 365. Read config.rollingWindowDays back to learn what was applied. A non-integer such as 7.5 is a 400.
  • The series is on by default. A bare GET /portfolio/analytics returns a year of daily points. Send includeSeries=false when you only want the ratios; it also skips the volume and funding reads, so it is measurably faster.

config

config reports the window the server actually used, after defaults and clamping.

FieldMeaning
rollingWindowDaysThe clamped value applied to every rolling statistic.
startDate, endDateThe resolved window, as ISO 8601 strings.
fundingSyncedWhether funding history has ever reached Pear for this account.

fundingSynced: false means every dayFunding is unknown, not zero. Pear has no funding record for this account, so each dayFunding reads 0 and each dayNetPnl equals its dayPnl — by default, not by measurement. A chart that plots that as "no funding paid" is wrong. Check the flag before you draw funding. Funding reaches Pear through the same exchange sync that settles fills, so an account that has never been synced has no funding to report — see Fills. The flag says only that funding once arrived. It makes no claim about how recent it is.

portfolio

portfolio carries the same metrics under two lenses.

VariantCovers
realizedOnlyClosed trades in the window. This is booked performance.
realizedPlusUnrealizedClosed trades plus the mark-to-market of everything still open. This is what the account is worth today.

Both variants have the same eleven fields:

FieldMeaning
pnlAbsolutePnL in USD under this lens. Never null.
pnlPercentpnlAbsolute over the capital deployed, as a fraction. null when no capital was deployed.
hitRateWinners over winners plus losers. A breakeven trade is in neither term.
profitFactorGross profit over gross loss. null when either side is zero.
avgWinSizeMean USD PnL of the winners. Positive.
avgLossSizeMean USD PnL of the losers. Negative, not an absolute value.
avgReturnPerWinMean return of the winners, as a fraction of entry notional.
avgReturnPerLossMean return of the losers. Negative.
maxDrawdownDeepest peak-to-trough fall of the daily equity curve, as a negative fraction. 0 when it never fell.
sharpeRatioMean daily return over its sample standard deviation, annualised by √365. The risk-free rate is zero.
sortinoRatioThe same, measured against downside deviation only.

maxDrawdown, sharpeRatio, and sortinoRatio are populated in portfolio.realizedOnly and nowhere else. They need a daily return history, and only closed trades have one. In portfolio.realizedPlusUnrealized and in every entry of assets, all three are always null. That is the design, not missing data.

A ratio is null, never 0, when it is undefined. A null sortinoRatio means there was no losing day, or fewer than two days of data — not that downside risk was zero.

portfolio also carries four fields outside the two variants:

FieldMeaning
realizedHitRateThe realized hit rate. Same value as realizedOnly.hitRate.
profitFactor, avgWinSize, avgLossSizeRepeats of the realizedOnly figures, lifted to the top level for convenience.
breakevenHitRateThe hit rate you need to break even at your current average win and loss: the absolute avgLossSize divided by avgWinSize plus the absolute avgLossSize. null unless both averages exist.
cushionrealizedHitRate - breakevenHitRate. Positive means you win often enough for the sizes you win and lose. Negative means your win rate is not carrying your loss sizes.

In the example, a cushion of 0.2038 says the account wins 20 percentage points more often than its win-to-loss size ratio requires.

assets

One entry per instrument that appears in a closed trade or in an open position, sorted by instrument ID.

FieldMeaning
assetThe instrument ID.
realizedOnlyThe metric variant above, restricted to this instrument's share of each closed trade.
realizedPlusUnrealizedThe same, plus this instrument's current unrealized PnL and mark notional.
unrealizedReturnPerDayCurrent unrealized PnL divided by the position-days this instrument has been open. null when nothing is open in it.

Per-asset figures split a basket's PnL back onto its legs. The pairs breakdown below keeps each basket whole. Read Synthetic Position for why the two views differ.

pairs

One entry per distinct basket shape, sorted by absolute totalPnl, largest first. It answers "which pair actually makes me money".

FieldMeaning
keyThe basket identity: the long instrument IDs after L:, the short ones after S:, each list comma-joined and the two halves separated by a pipe. The example above is L:0|S:1. Treat it as opaque.
longAssets, shortAssetsThe legs of the basket.
tradesClosed trades of this basket in the window.
wins, lossesHow many made or lost money. wins + losses can be below trades, because a breakeven close is in neither.
hitRatewins / trades. This divides by every trade, including breakeven ones, unlike hitRate inside a metric variant.
avgWinSizeMean USD PnL of this basket's winners. Positive.
avgLossSizeMean USD PnL of its losers. Negative.
expectancytotalPnl / trades. The mean USD result of trading this basket once.
totalPnlNet realized PnL of this basket over the window.
bookPercenttotalPnl as a share of the account's total realized PnL. null when that total is zero.

bookPercent does not behave like a percentage of a whole. The denominator is net realized PnL, so a losing basket gets a negative share and a single winning basket can exceed 1 when other baskets lost money. The shares do not sum to 1 in any useful way. Use it to rank contribution, not to fill a pie chart.

series

One point per UTC calendar day from config.startDate to config.endDate, inclusive. Days with no activity are still emitted, so the array length is fixed by the window, not by how much you traded. A year window returns 366 points.

FieldMeaning
dateThe UTC day, as YYYY-MM-DD.
tradesTrades closed that day. Equals wins + losses, so a breakeven close is counted nowhere.
wins, lossesHow many of them made or lost money.
dayPnlNet realized PnL of the trades closed that day, after fees.
dayVolumeFill notional traded that day. Honours source.
dayFundingFunding accrued that day, signed. Negative means you paid. Read config.fundingSynced first.
dayNetPnldayPnl + dayFunding. The figure that reconciles with the venue's own statement.
hitRateWins over wins plus losses, that day. null on a day with no closes.
winLossRatioAverage win over the absolute average loss, that day. null without both.
rollingHitRate, rollingWinLossRatioThe same two over the trailing rollingWindowDays.
dailyReturndayPnl over the entry notional of the trades closed that day. 0 on a day with no closes.
equityDaily returns compounded, starting at 1.
cumulativeReturnequity - 1.
drawdownFall from the running peak of equity, as a negative fraction. 0 at a new high.
rollingSharpe, rollingSortino, rollingVolatilityTrailing-window statistics of dailyReturn. rollingVolatility is the sample standard deviation, not annualised.

Every rolling field is null for the first rollingWindowDays - 1 points, because the window has not filled yet. Chart them from the first non-null point rather than from the start of the series.

Trades are booked on the day they close, and funding on the day it accrues. A trade held for a week contributes to one dayPnl and to seven dayFunding values.

The equity curve compounds trade returns, not your wallet balance. Deposits and withdrawals do not move it. That is deliberate: it keeps the risk metrics comparable across accounts of different sizes.

What gets rejected

StatusCause
400x-trade-account-id missing, or not a UUID. source not all or pear. interval not one of the five values. includeSeries not true or false. rollingWindowDays not an integer.
401Missing or invalid credentials.
404A well-formed x-trade-account-id that is not yours. Pear does not distinguish "not found" from "not yours".

Three inputs are accepted and quietly ignored rather than rejected: an unparseable startDate or endDate, an interval sent alongside a startDate, and any parameter the endpoint does not define. A rollingWindowDays outside 7–365 is clamped, not refused. config is the only reliable record of what the server used, so read it back instead of assuming your parameters applied.

See Error Handling for the two error body shapes.

On this page