Skip to content

fix(portfolio): exclude closed (size-0) positions from the dashboard Overview - #2465

Merged
dcccrypto merged 1 commit into
dcccrypto:playgroundfrom
0x-SquidSol:fix/portfolio-hide-closed-positions
Aug 1, 2026
Merged

fix(portfolio): exclude closed (size-0) positions from the dashboard Overview#2465
dcccrypto merged 1 commit into
dcccrypto:playgroundfrom
0x-SquidSol:fix/portfolio-hide-closed-positions

Conversation

@0x-SquidSol

Copy link
Copy Markdown
Contributor

Problem

A closed position still has a portfolio account in its market — positionSize == 0, rendered Flat, and often still holding idle capital — so usePortfolio().positions returns it. The dashboard Overview consumed that raw array unfiltered, so a market the user had closed kept appearing as an active position:

  • Open Positions listed a Flat row for the closed market and its count was inflated ((2) with one real position).
  • Active Positions stat showed 2.
  • Portfolio PnL read "Across 2 positions".

Repro: open a position, close it, open /portfolio → the closed market still shows under Open Positions with Size 0.

Root cause

The canonical open/flat split already exists in the codebase — PortfolioPositionsView separates open positions from idle deposits with account.positionSize !== 0n, and the site-wide PositionsBar filters the same way. The dashboard components just never applied it; they mapped/counted portfolio.positions directly.

Fix

Add a single shared helper in usePortfolio and apply it everywhere the Overview counts or lists positions:

export function isOpenPosition(pos: PortfolioPosition): boolean {
  return (pos.account?.positionSize ?? 0n) !== 0n;
}

Keyed on account.positionSize to match the existing canonical definition (PortfolioPositionsView, PositionsBar) so every open-position surface agrees — no third variant.

Applied in the four Overview components:

component before after
PositionSummary list + count over all accounts filtered to open
DashboardHeader positions.length open-only count
PnlChart "Across N positions" / empty gate over all open-only
StatsBar stats + fee readout + empty checks over all open-only (memoized)

Design notes

  • Filtering is consumer-side, not at the hook — surfaces that legitimately need every account (idle deposits, my-markets) are untouched, so there's no blast radius.
  • A funded-but-flat account (idle deposit) is correctly not counted as open; it belongs to the idle-deposits surface.
  • An all-flat book now renders the correct empty state instead of phantom positions.
  • StatsBar's win/loss already ignored flat rows via the >0/<0 PnL test; this additionally corrects the length-based empty checks and per-market fee readout, and the filtered list is memoized to stay referentially stable.

Testing

  • tsc --noEmit clean.
  • New isOpenPosition unit suite — 6/6: open long, open short, closed (size 0), funded-but-flat idle deposit, missing account, and a mixed-book filter.
  • No regressions: the only failing test in the portfolio suite (PORT-007: AtRiskBanner) reproduces identically on the untouched baseline, so it's pre-existing and unrelated to this change.
  • Note: the dashboard needs live wallet/market data to fully render, so the end-to-end visual is best confirmed on the deployed playground; the logic is covered by the unit test above and matches the already-shipped PortfolioPositionsView / PositionsBar behavior.

🤖 Generated with Claude Code

…Overview

A closed position still has a portfolio account in its market (positionSize 0,
rendered "Flat", often holding idle capital), so `usePortfolio().positions`
returns it. The dashboard Overview counted/listed that raw array unfiltered, so
a market the user had closed kept showing as an active position — e.g.
"OPEN POSITIONS (2)" and "ACTIVE POSITIONS 2" with a Flat row for a closed
market.

The canonical open/flat split already exists elsewhere: PortfolioPositionsView
separates open positions from idle deposits via `account.positionSize !== 0n`,
and the site-wide PositionsBar filters the same way. The dashboard components
simply never applied it.

Fix: add a shared `isOpenPosition(pos)` helper in usePortfolio (keyed on
`account.positionSize !== 0n`, matching the existing canonical definition so all
open-position surfaces agree) and apply it in the four Overview components that
count or list positions:
  - PositionSummary — the "Open Positions" list + count
  - DashboardHeader — the "Active Positions" stat
  - PnlChart       — the "Across N positions" caption + empty-state gate
  - StatsBar       — stats + per-market fee readout + empty-state checks
    (win/loss already ignored flat rows via the >0/<0 PnL test; this also
    corrects the length-based cases and is memoized to stay referentially stable)

Filtering is done consumer-side, not at the hook, so surfaces that legitimately
need every account (idle deposits, my-markets) are unaffected. A funded-but-flat
account (idle deposit) is correctly treated as NOT open; an all-flat book now
shows the proper empty state.

Tested: tsc clean; new isOpenPosition unit suite (6 cases: open long/short,
closed size-0, funded-but-flat idle, missing account, mixed-book filter) green;
the one pre-existing AtRiskBanner test failure reproduces identically on the
untouched baseline (unrelated to this change).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@0x-SquidSol
0x-SquidSol requested a review from dcccrypto as a code owner July 31, 2026 03:36
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

@0x-SquidSol is attempting to deploy a commit to the Khubair Nasir's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 352e8e1a-75c8-4d59-9232-36959afc1630

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@dcccrypto
dcccrypto merged commit 5d1ce38 into dcccrypto:playground Aug 1, 2026
10 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants