Skip to content

Commit 157f822

Browse files
committed
fix: show realized PnL on closed perp feed rows
1 parent c59a71b commit 157f822

2 files changed

Lines changed: 56 additions & 12 deletions

File tree

app/components/Views/SocialLeaderboard/FeedView/utils/mapFeedItem.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,40 @@ describe('mapFeedItem', () => {
280280
expect(result?.valueLabel).toContain('4,659');
281281
});
282282

283+
it('shows realized PnL for a closed perp exit when marginUsd is still non-zero', () => {
284+
const result = mapFeedItem(
285+
mockPerpFeedItem({
286+
tokenSymbol: 'BTC',
287+
currentValueUSD: undefined,
288+
pnlValueUsd: undefined,
289+
pnlPercent: undefined,
290+
realizedPnl: 12_500,
291+
marginUsd: 151_400,
292+
positionAmount: 2.5,
293+
perpPositionType: 'short',
294+
perpLeverage: 9,
295+
trades: [
296+
{
297+
direction: 'sell',
298+
intent: 'exit',
299+
tokenAmount: -2.5,
300+
usdCost: -151_400,
301+
timestamp: 1_700_000_500,
302+
transactionHash: '0xhash',
303+
classification: 'perp',
304+
perpPositionType: 'short',
305+
perpLeverage: 9,
306+
},
307+
],
308+
}),
309+
);
310+
311+
expect(result?.action).toBe('closed');
312+
expect(result?.hasValueData).toBe(true);
313+
expect(result?.hasPnlData).toBe(true);
314+
expect(result?.valueLabel).toContain('12,500');
315+
});
316+
283317
it('marks negative PnL as not positive', () => {
284318
const result = mapFeedItem(
285319
mockPerpFeedItem({ pnlValueUsd: -500, realizedPnl: -500 }),

app/components/Views/SocialLeaderboard/FeedView/utils/mapFeedItem.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,30 @@ function findTriggeringTrade(
6868
}
6969

7070
/**
71-
* Resolves the action verb for a feed row. Perp fills read as "opened"/"closed",
72-
* spot as "bought"/"sold" (mirrors `TradeRow`). When the position has no trade
73-
* to key off, falls back to the position's open/closed state.
71+
* Feed rows are keyed off a triggering trade when one exists. Exit fills must
72+
* use the closed presentation path (realized PnL) even when
73+
* {@link isClosedPosition} misclassifies a perp that still carries stale
74+
* non-zero margin in the Clicker payload.
7475
*/
75-
function resolveAction(
76+
function isFeedItemClosed(
77+
coreItem: CoreFeedItem,
7678
trade: Trade | undefined,
77-
isPerp: boolean,
78-
isClosed: boolean,
79-
): FeedAction {
80-
const isExit = trade ? trade.intent === 'exit' : isClosed;
79+
): boolean {
80+
if (trade?.intent === 'exit') {
81+
return true;
82+
}
83+
return isClosedPosition(coreItem);
84+
}
85+
86+
/**
87+
* Resolves the action verb for a feed row. Perp fills read as "opened"/"closed",
88+
* spot as "bought"/"sold" (mirrors `TradeRow`).
89+
*/
90+
function resolveAction(isPerp: boolean, isClosed: boolean): FeedAction {
8191
if (isPerp) {
82-
return isExit ? 'closed' : 'opened';
92+
return isClosed ? 'closed' : 'opened';
8393
}
84-
return isExit ? 'sold' : 'bought';
94+
return isClosed ? 'sold' : 'bought';
8595
}
8696

8797
/**
@@ -286,9 +296,9 @@ export function mapFeedItem(coreItem: CoreFeedItem): FeedItem | null {
286296

287297
const timestampMs = toMs(timestamp);
288298
const isPerp = isPerpPosition(coreItem);
289-
const isClosed = isClosedPosition(coreItem);
290299
const trade = findTriggeringTrade(trades ?? [], timestampMs);
291-
const action = resolveAction(trade, isPerp, isClosed);
300+
const isClosed = isFeedItemClosed(coreItem, trade);
301+
const action = resolveAction(isPerp, isClosed);
292302
const subHeader = buildSubHeader(trade);
293303
const presentation = buildFeedItemPresentation(coreItem, isClosed);
294304

0 commit comments

Comments
 (0)