Skip to content
51 changes: 49 additions & 2 deletions src/bonsai/calculators/fills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ import { weakMapMemoize } from 'reselect';

import { NUM_PARENT_SUBACCOUNTS } from '@/constants/account';
import { EMPTY_ARR } from '@/constants/objects';
import { IndexerFillType } from '@/types/indexer/indexerApiGen';
import {
IndexerFillType,
IndexerOrderSide,
IndexerPositionSide,
} from '@/types/indexer/indexerApiGen';
import { IndexerCompositeFillObject } from '@/types/indexer/indexerManual';

import { assertNever } from '@/lib/assertNever';
import { MustBigNumber } from '@/lib/numbers';
import { MustBigNumber, MustNumber } from '@/lib/numbers';

import { mergeObjects } from '../lib/mergeObjects';
import { SubaccountFill, SubaccountFillType } from '../types/summaryTypes';
Expand All @@ -30,6 +34,7 @@ const calculateFill = weakMapMemoize(
...base,
marginMode: (base.subaccountNumber ?? 0) >= NUM_PARENT_SUBACCOUNTS ? 'ISOLATED' : 'CROSS',
type: getFillType(base),
closedPnl: calculateClosedPnl(base),
})
);

Expand All @@ -53,3 +58,45 @@ function getFillType({
assertNever(type);
return SubaccountFillType.LIMIT;
}

const calculateClosedPnl = (fill: IndexerCompositeFillObject) => {
const fee = MustNumber(fill.fee ?? '0');

// Old fills are not supported so we show -- instead of 0
if (
fill.positionSideBefore === undefined ||
fill.positionSizeBefore === undefined ||
fill.entryPriceBefore === undefined
) {
return undefined;
}

// No position before = opening trade, only fees realize
if (fill.positionSizeBefore === 0) {
return -fee;
}

// Check if position is reducing (opposite side)
const isReducing =
(fill.positionSideBefore === IndexerPositionSide.LONG && fill.side === IndexerOrderSide.SELL) ||
(fill.positionSideBefore === IndexerPositionSide.SHORT && fill.side === IndexerOrderSide.BUY);

if (!isReducing) {
// Position increasing (same side), only fees realize
return -fee;
}

const size = MustNumber(fill.size ?? '0');
const price = MustNumber(fill.price ?? '0');

// Position reducing - cap closing amount to actual position size
const closingAmount = Math.min(size, fill.positionSizeBefore);

// Calculate P&L only on the closing portion
const closingPnl =
fill.positionSideBefore === IndexerPositionSide.LONG
? (price - fill.entryPriceBefore) * closingAmount
: (fill.entryPriceBefore - price) * closingAmount;

return closingPnl - fee;
};
1 change: 1 addition & 0 deletions src/bonsai/types/summaryTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ export enum SubaccountFillType {
export type SubaccountFill = Omit<IndexerCompositeFillObject, 'type'> & {
marginMode: MarginMode;
type: SubaccountFillType | undefined;
closedPnl?: number;
};

export type LiveTrade = IndexerWsTradeResponseObject;
Expand Down
1 change: 1 addition & 0 deletions src/pages/portfolio/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export const Overview = () => {
PositionsTableColumnKey.Size,
PositionsTableColumnKey.Value,
PositionsTableColumnKey.PnL,
PositionsTableColumnKey.RealizedPnL,
PositionsTableColumnKey.Margin,
PositionsTableColumnKey.AverageOpen,
PositionsTableColumnKey.Oracle,
Expand Down
1 change: 1 addition & 0 deletions src/pages/portfolio/Portfolio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ const PortfolioPage = () => {
FillsTableColumnKey.Price,
FillsTableColumnKey.Total,
FillsTableColumnKey.Fee,
FillsTableColumnKey.ClosedPnl,
FillsTableColumnKey.Liquidity,
]
}
Expand Down
1 change: 1 addition & 0 deletions src/pages/portfolio/Positions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const Positions = () => {
PositionsTableColumnKey.Size,
PositionsTableColumnKey.Value,
PositionsTableColumnKey.PnL,
PositionsTableColumnKey.RealizedPnL,
PositionsTableColumnKey.Margin,
PositionsTableColumnKey.AverageOpen,
PositionsTableColumnKey.Oracle,
Expand Down
2 changes: 2 additions & 0 deletions src/pages/trade/HorizontalPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export const HorizontalPanel = ({ isOpen = true, setIsOpen, handleStartResize }:
PositionsTableColumnKey.Size,
PositionsTableColumnKey.Value,
PositionsTableColumnKey.PnL,
PositionsTableColumnKey.RealizedPnL,
PositionsTableColumnKey.Margin,
PositionsTableColumnKey.AverageOpen,
PositionsTableColumnKey.Oracle,
Expand Down Expand Up @@ -326,6 +327,7 @@ export const HorizontalPanel = ({ isOpen = true, setIsOpen, handleStartResize }:
FillsTableColumnKey.Price,
FillsTableColumnKey.Total,
FillsTableColumnKey.Fee,
FillsTableColumnKey.ClosedPnl,
FillsTableColumnKey.Liquidity,
].filter(isTruthy)
}
Expand Down
4 changes: 4 additions & 0 deletions src/types/indexer/indexerManual.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
IndexerPerpetualMarketStatus,
IndexerPerpetualMarketType,
IndexerPerpetualPositionResponseObject,
IndexerPositionSide,
IndexerTradeResponseObject,
IndexerTransferResponseObject,
} from './indexerApiGen';
Expand Down Expand Up @@ -156,6 +157,9 @@ export interface IndexerCompositeFillObject {
clientMetadata?: string | null;
subaccountNumber?: number;
market?: string;
positionSideBefore?: IndexerPositionSide;
positionSizeBefore?: number;
entryPriceBefore?: number;
}

export interface IndexerWsParentSubaccountSubscribedResponse {
Expand Down
11 changes: 11 additions & 0 deletions src/views/tables/FillsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export enum FillsTableColumnKey {
AmountTag = 'Amount-Tag',
Total = 'Total',
Fee = 'Fee',
ClosedPnl = 'ClosedPnl',

// Tablet Only
TypeAmount = 'Type-Amount',
Expand Down Expand Up @@ -212,6 +213,16 @@ const getFillsTableColumnDef = ({
</TableCell>
),
},
[FillsTableColumnKey.ClosedPnl]: {
columnKey: 'closedPnl',
getCellValue: (row) => row.closedPnl,
label: stringGetter({ key: STRING_KEYS.CLOSED_PNL }),
renderCell: ({ closedPnl }) => (
<TableCell>
<Output type={OutputType.Fiat} value={closedPnl} />
</TableCell>
),
},
[FillsTableColumnKey.Type]: {
columnKey: 'type',
getCellValue: (row) => row.type,
Expand Down
Loading