Skip to content

[ENG-1734] feat(comlink): add GET /v4/tradeHistory endpoint with cumulative PnL tracking (backport #3323)#3328

Merged
davidli1997 merged 2 commits into
release/indexer/v9.xfrom
mergify/bp/release/indexer/v9.x/pr-3323
Mar 3, 2026
Merged

[ENG-1734] feat(comlink): add GET /v4/tradeHistory endpoint with cumulative PnL tracking (backport #3323)#3328
davidli1997 merged 2 commits into
release/indexer/v9.xfrom
mergify/bp/release/indexer/v9.x/pr-3323

Conversation

@mergify

@mergify mergify Bot commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

Changelist

  • Extract shared buildClobPairIdToMarket() helper from fills-controller into lib/helpers.ts to avoid duplication
  • Fix flaky test helper randomInt() that caused clientId collisions in integration tests
  • Add new GET /v4/tradeHistory endpoint with two routes:
    • GET / — trade history for a single subaccount
    • GET /parentSubaccountNumber — trade history for a parent subaccount (includes child/isolated subaccounts)
  • Core algorithm in lib/trade-history.ts computes trade history rows on-the-fly from fills:
  • Detects cross-zero orders (e.g., sell 10 when long 5) and splits into CLOSE + OPEN rows
  • 6 trade types: OPEN, EXTEND, PARTIAL_CLOSE, CLOSE, LIQUIDATION_PARTIAL_CLOSE, LIQUIDATION_CLOSE

Example: User Trading Activity on BTC-USD

#  Action       Side  Size  Price
1  Buy          BUY   10    $100
2  Buy more     BUY   10    $120
3  Sell half    SELL  10    $130
4  Sell rest    SELL  10    $90
5  Short        SELL  5     $85
6  Cover short  BUY   5     $80

Trade History Cards Produced (most recent first):

#  action               side  prevSize  additionalSize  executionPrice  entryPrice  netRealizedPnl  netRealizedPnlPercent  positionSide
6  CLOSE                BUY   5         5               80              85          25              0.0588 (5.88%)         null
5  OPEN                 SELL  0         -5              85              85          0               null                   SHORT
4  CLOSE                SELL  10        -10             90              110         0               0 (0%)                 null
3  PARTIAL_CLOSE        SELL  20        -10             130             110         200             0.1818 (18.18%)        LONG
2  EXTEND               BUY   10        10              120             110         0               null                   LONG
1  OPEN                 BUY   0         10              100             100         0               null                   LONG

How the numbers work:

Row 1: Open long 10 @ $100. entryPrice = $100.
Row 2: Extend by 10 @ $120. entryPrice = weighted avg = (10010 + 12010) / 20 = $110.
Row 3: Partial close 10 @ $130. PnL = (130 - 110) * 10 = $200. Cost basis = 110 * 10 = $1,100. Percent = 200/1100 = 18.18%.
Row 4: Close remaining 10 @ $90. PnL = (90 - 110) * 10 = -$200. Cumulative = 200 + (-200) = $0. Cum cost basis = 1100 + 1100 = $2,200. Percent = 0/2200 = 0%. Lifecycle resets.
Row 5: Open short 5 @ $85. Fresh lifecycle -- netRealizedPnl = 0, netRealizedPnlPercent = null.
Row 6: Close short 5 @ $80. PnL = (85 - 80) * 5 = $25. Cost basis = 85 * 5 = $425. Percent = 25/425 = 5.88%.

Key behaviors:

  • entryPrice updates on OPEN (set) and EXTEND (weighted average), stays fixed on partial/full close
  • netRealizedPnl and netRealizedPnlPercent are cumulative within a position lifecycle, reset to 0/null when position goes flat
  • netRealizedPnlPercent = cumPnl / cumCostBasis, where cumCostBasis = sum(entryPrice * closingAmount) across all closes
  • netRealizedPnlPercent is null on OPEN/EXTEND rows that have no prior closes in the lifecycle
  • Frontend can compute per-trade % as (executionPrice - entryPrice) / entryPrice (long) or (entryPrice - executionPrice) / entryPrice (short)

Test Plan

  • staging
  • testnet
  • mainnet

Author/Reviewer Checklist

  • If this PR has changes that result in a different app state given the same prior state and transaction list, manually add the state-breaking label.
  • If the PR has breaking postgres changes to the indexer add the indexer-postgres-breaking label.
  • If this PR isn't state-breaking but has changes that modify behavior in PrepareProposal or ProcessProposal, manually add the label proposal-breaking.
  • If this PR is one of many that implement a specific feature, manually label them all feature:[feature-name].
  • If you wish to for mergify-bot to automatically create a PR to backport your change to a release branch, manually add the label backport/[branch-name].
  • Manually add any of the following labels: refactor, chore, bug.

Summary by CodeRabbit

  • New Features

    • Added /tradeHistory and /tradeHistory/parentSubaccount endpoints returning detailed trade history (actions, execution price, fees, net realized PnL, order IDs, market, margin mode). Supports market/marketType filtering, pagination, limits, and handles liquidation and cross-zero lifecycle cases.
  • Documentation

    • Added API docs and schemas with examples and response shapes for the new endpoints.
  • Tests

    • Comprehensive tests covering sorting, grouping, pagination, validation, error cases, parent/child subaccounts, and deterministic ID generation for stable results.

This is an automatic backport of pull request #3323 done by [Mergify](https://mergify.com).

…lative PnL tracking (#3323)

(cherry picked from commit 59cd776)

# Conflicts:
#	indexer/services/comlink/src/controllers/api/v4/fills-controller.ts
@mergify mergify Bot added the conflicts label Mar 3, 2026
@mergify mergify Bot requested a review from a team as a code owner March 3, 2026 18:09
@mergify

mergify Bot commented Mar 3, 2026

Copy link
Copy Markdown
Contributor Author

Cherry-pick of 59cd776 has failed:

On branch mergify/bp/release/indexer/v9.x/pr-3323
Your branch is up to date with 'origin/release/indexer/v9.x'.

You are currently cherry-picking commit 59cd776b.
  (fix conflicts and run "git cherry-pick --continue")
  (use "git cherry-pick --skip" to skip this patch)
  (use "git cherry-pick --abort" to cancel the cherry-pick operation)

Changes to be committed:
	new file:   indexer/services/comlink/__tests__/controllers/api/v4/trade-history-controller.test.ts
	modified:   indexer/services/comlink/__tests__/helpers/helpers.ts
	new file:   indexer/services/comlink/__tests__/lib/trade-history.test.ts
	modified:   indexer/services/comlink/public/api-documentation.md
	modified:   indexer/services/comlink/public/swagger.json
	modified:   indexer/services/comlink/src/controllers/api/index-v4.ts
	new file:   indexer/services/comlink/src/controllers/api/v4/trade-history-controller.ts
	modified:   indexer/services/comlink/src/lib/helpers.ts
	new file:   indexer/services/comlink/src/lib/trade-history.ts
	modified:   indexer/services/comlink/src/types.ts

Unmerged paths:
  (use "git add <file>..." to mark resolution)
	both modified:   indexer/services/comlink/src/controllers/api/v4/fills-controller.ts

To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally

@linear

linear Bot commented Mar 3, 2026

Copy link
Copy Markdown

@davidli1997 davidli1997 merged commit bb51260 into release/indexer/v9.x Mar 3, 2026
35 checks passed
@davidli1997 davidli1997 deleted the mergify/bp/release/indexer/v9.x/pr-3323 branch March 3, 2026 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants