NakedPnL

The public registry of verified investment performance. Every return sourced from SEC filings, exchange APIs, or platform data.

Registry

  • Registry
  • Market Context
  • How It Works
  • Community

Verification

  • Get Verified
  • Connect Exchange

Legal

  • Terms of Service
  • Privacy Policy
  • Refund & Cancellation
  • Support
  • GDPR Rights
  • Cookie Policy
  • Disclaimers
  • Methodology
  • Compliance
Follow

NakedPnL is a publisher of verified performance data. Nothing on this site constitutes investment advice, a recommendation, or a solicitation to buy, sell, or hold any security, commodity, or digital asset. Past performance does not indicate future results. Trading carries a high risk of total capital loss.

© 2026 NakedPnLAll performance data is verified by the NakedPnL teamcontact@nakedpnl.com
NakedPnL
RegistryPricingHow It WorksCommunitySupport
NakedPnL/Guides/Cash Flow Detection in NAV-Only Data — How NakedPnL Handles Deposits and Withdrawals
Methodology guide

Cash Flow Detection in NAV-Only Data — How NakedPnL Handles Deposits and Withdrawals

Most exchange APIs report NAV but not cash flow history at usable granularity. This guide walks through how NakedPnL detects deposits and withdrawals and why the trade-offs matter.

By NakedPnL Research·May 7, 2026·15 min read
TL;DR
  • Time-weighted return depends on cleanly identifying every deposit and withdrawal so they can be excluded from the return calculation.
  • Most exchange balance APIs report total NAV but do not surface deposit/withdrawal history at the granularity TWR requires.
  • NakedPnL detects probable cash flows from large day-over-day NAV jumps, with venue-tuned thresholds.
  • Detection is intentionally biased toward false positives over false negatives — over-flagging is correctable, under-flagging silently inflates returns.
  • Confirmed cash flows are persisted in the append-only CashFlow table with provenance, source venue, and reviewer attribution.
On this page
  1. Why NAV alone is not enough
  2. What exchange APIs actually expose
  3. The detection algorithm
  4. Reconciliation against the venue ledger
  5. Worked example — a misclassified flow
  6. The CashFlow table as append-only ledger
  7. How TWR consumes confirmed flows
  8. What ends up on the verified track record
  9. Common edge cases
  10. Summary
  11. Frequently asked questions

Time-weighted return is only honest if every external cash flow into and out of the account is cleanly identified and excluded from the return calculation. A 50,000 USD deposit on a 100,000 USD account is not a 50% return — it is a 50% subscription. A 50,000 USD withdrawal on a 100,000 USD account is not a 50% loss. The TWR engine has to know about every flow to chain-link the sub-period returns correctly.

This would be straightforward if exchange APIs reliably reported deposits and withdrawals at usable granularity. They do not. The balance and equity endpoints are first-class; the cash-flow ledger is often paginated, rate-limited, locked behind separate scopes, or simply incomplete for older periods. NakedPnL has to detect probable cash flows from the NAV path itself and reconcile with whatever venue-side ledger data is available. This guide walks through how that detection works, what its failure modes look like, and how the CashFlow table records confirmed flows for verification.

Why NAV alone is not enough

Consider three days of NAV samples for a single account: 100,000 USD, 105,000 USD, 155,000 USD. The day 1 to day 2 change is +5%. The day 2 to day 3 change appears to be +47.6%. Did the trader make 47% in 24 hours, or did they deposit 50,000 USD overnight and earn nothing? With NAV alone the question cannot be answered.

If NakedPnL silently treated the day 3 NAV as pure return the published TWR would be wildly wrong, and the chain hash would be locked in around that wrong number. Conversely, if NakedPnL marked every large move as a deposit by default, traders who do produce occasional large gains (a single high-conviction trade, a payout from a prediction market resolving in their favor) would have their genuine returns hidden as cash flows.

The asymmetric error
Missing a real cash flow is worse than over-flagging one. A missed deposit makes returns look better than they were and the error compounds in the published track record. An over-flagged return is corrected by the human-review queue and the trader can dispute it with evidence from the venue's deposit history. NakedPnL's defaults are tuned to make the cheaper error.

What exchange APIs actually expose

VenueEndpointGranularityLookbackQuality
Binance/sapi/v1/capital/deposit/hisrec, /withdraw/historyPer-transaction180 days default, 90 days reliableGood for recent dates, gappy beyond 6 months
Bybit/v5/asset/deposit-record, /withdraw-recordPer-transaction180 daysGood but rate-limited, paginated
OKX/api/v5/asset/deposit-history, /withdraw-historyPer-transaction90 daysAcceptable, requires reading scope expansion
IBKRFlex Web Service — DepositsWithdrawals statementPer-event with timestampUp to inceptionExcellent — IBKR's Flex queries are designed for audit
PolymarketSubgraph + on-chain ERC20 transfersPer-transfer at block timeFull historyExcellent — every flow is on Polygon and queryable
Kalshi/v3/portfolio/positions does not include flowsNone nativeN/ADetection-only; flows inferred from NAV jumps
Cash-flow data quality by venue, as observed by the NakedPnL pipeline.

IBKR and Polymarket are the well-instrumented end of the spectrum. The Flex Web Service for IBKR returns a complete DepositsWithdrawals statement and the timestamps are second-precision. Polymarket every flow is an on-chain ERC20 transfer that can be reconstructed from a Polygon archive node or the official subgraph. For these venues NakedPnL imports the venue ledger directly and detection is reconciliation, not inference.

The crypto centralized-exchange APIs (Binance, Bybit, OKX) are the noisy end. Recent deposits and withdrawals are reliable; data older than three to six months becomes incomplete. For older periods NakedPnL falls back to detection-from-NAV-jumps and asks the trader to reconcile. Kalshi has no native cash-flow endpoint at all, so detection is the only mechanism.

The detection algorithm

The detector compares each daily NAV sample to its predecessor and flags any move that exceeds a venue-specific threshold expressed as a percent of NAV. The threshold is calibrated per venue based on the empirical distribution of daily returns observed across the active trader population. The thresholds in production are conservative — they are deliberately tighter than the 99.5th percentile of daily returns for the venue.

VenueDefault threshold99.5th-percentile daily return
Binance (spot)+/- 25%approx +/- 12%
Binance (futures)+/- 40%approx +/- 22%
Bybit+/- 35%approx +/- 18%
OKX+/- 35%approx +/- 18%
IBKR+/- 15%approx +/- 5%
Polymarket+/- 50%approx +/- 35%
Kalshi+/- 45%approx +/- 25%
Production cash-flow detection thresholds by venue. Thresholds are absolute day-over-day NAV change as a percent.

When a sample crosses the threshold the detector raises a CandidateCashFlow row and freezes the TWR calculation for that trader at the previous day until the candidate is resolved. Resolution happens by automated reconciliation (matching against the venue's deposit/withdrawal endpoint when available) or by human review when the venue does not expose enough information for automated matching.

// lib/cash-flow/detector.ts (simplified)
const THRESHOLDS: Record<Venue, number> = {
  BINANCE_SPOT: 0.25,
  BINANCE_FUT:  0.40,
  BYBIT:        0.35,
  OKX:          0.35,
  IBKR:         0.15,
  POLYMARKET:   0.50,
  KALSHI:       0.45,
};

export function detectCandidate(
  prev: NavSnapshot,
  curr: NavSnapshot,
  venue: Venue,
): CandidateCashFlow | null {
  if (prev.navUsd.isZero()) return null;
  const change = curr.navUsd.minus(prev.navUsd).dividedBy(prev.navUsd);
  if (change.abs().lessThan(THRESHOLDS[venue])) return null;
  return {
    traderId: curr.traderId,
    detectedOn: curr.snapshotDate,
    direction: change.isPositive() ? "INFLOW" : "OUTFLOW",
    impliedAmountUsd: curr.navUsd.minus(prev.navUsd).abs(),
    state: "PENDING_REVIEW",
  };
}
Simplified per-venue threshold detector.

Reconciliation against the venue ledger

For venues that do expose cash-flow endpoints, the reconciliation worker fetches the deposit/withdrawal history for the candidate's date window (typically the day of the candidate plus or minus one day to absorb timezone fuzz). It tries to find a venue-side flow whose USD value is within 0.5% of the impliedAmountUsd. A match resolves the candidate to CONFIRMED with the venue's transaction id stored as provenance.

If no match is found, the candidate is escalated to the human-review queue. The reviewer (NakedPnL operations or, in the trader-self-resolution flow, the trader themselves with venue-side proof) classifies the candidate as either a real flow or a real return. A real flow is recorded in the CashFlow table; a real return is dismissed and the TWR calculation resumes including the day's NAV change as performance.

Trader self-resolution
When the venue does not expose a cash-flow endpoint that NakedPnL can read directly, the trader can resolve a candidate by uploading a screenshot or signed statement from the venue. The evidence is stored alongside the CashFlow row. This is the only path where any human-supplied data enters the chain, and the provenance field makes that explicit to verifiers.

Worked example — a misclassified flow

Suppose a Binance trader with a 20,000 USD account hits a 100% return in a single day on a leveraged position. The new NAV is 40,000 USD. The detector sees a +100% day-over-day change, well above the +25% threshold, and raises a candidate. There is no matching deposit on Binance's deposit endpoint. The candidate is escalated to human review.

If the reviewer wrongly classifies it as a deposit the result is that a real return is hidden. The trader's published TWR for that day shows zero return, the NAV jump is offset by a synthetic 20,000 USD deposit in CashFlow, and the cumulative TWR is understated by approximately 100%. This is a serious error, but it is correctable: the trader sees the misclassification on their dashboard, supplies venue-side evidence (a screenshot of the trade history, an exported PnL report), and the reviewer reverses the call. A correction row is appended to the CashFlow table with reason=REVERSAL and the chain re-derives.

If the same flow is wrongly classified the other way — a real 20,000 USD deposit is treated as a 100% return — the failure is the opposite. The published TWR overstates by approximately 100% on that day and the cumulative track record is materially wrong. This is much harder to correct because the trader must also produce evidence, but a trader showing inflated returns has weaker incentive to correct the error than a trader showing deflated returns. This asymmetry of incentives is precisely why the thresholds are set tighter than the empirical distribution suggests.

OutcomeRecorded asDay TWRCumulative impact
Correct call (real return)+100% return+100%Track record honest
Correct call (real deposit)+0% return, +20k deposit+0%Track record honest
False positive (deposit, was return)+0% return, +20k deposit+0% (under)Track record understated; trader will dispute
False negative (return, was deposit)+100% return+100% (over)Track record overstated; trader unlikely to dispute
TWR impact of the two error directions on a single 100% NAV-jump day.

The CashFlow table as append-only ledger

Confirmed cash flows are written to the CashFlow table. The table is append-only: existing rows are never updated or deleted. Corrections and reversals are themselves new rows that reference the original via correctsId. This is the same append-only discipline as the NavSnapshot chain, and it serves the same purpose: any verifier reading the table can see the full audit history of how a flow was classified and (if applicable) re-classified.

model CashFlow {
  id            String        @id @default(cuid())
  traderId      String
  flowDate      DateTime      // when the flow occurred at the venue
  detectedAt    DateTime      // when the detector raised the candidate
  resolvedAt    DateTime
  direction     CashFlowDirection // INFLOW | OUTFLOW
  amountUsd     Decimal
  source        CashFlowSource    // VENUE_LEDGER | TRADER_EVIDENCE | DETECTOR_INFERENCE
  venueTxId     String?           // venue-side id when reconciled
  reviewerId    String?           // operations user when human-reviewed
  reason        CashFlowReason    // INITIAL | REVERSAL | ADJUSTMENT
  correctsId    String?           // points at the row this row corrects
  trader        CryptoTrader @relation(fields: [traderId], references: [id])
  corrects      CashFlow?    @relation("CashFlowCorrection",
                              fields: [correctsId], references: [id])
  correctedBy   CashFlow[]   @relation("CashFlowCorrection")

  @@index([traderId, flowDate])
}
CashFlow Prisma model (simplified).

How TWR consumes confirmed flows

The TWR engine in lib/calculation/twr-engine.ts reads the CashFlow table at calculation time. For each trader, it loads all CONFIRMED flows ordered by flowDate. The day-by-day return calculation chain-links sub-period returns at the boundaries of each flow. A deposit that arrives mid-day is handled by the Modified Dietz day-weight approximation when the venue provides only a date, or by an exact intra-day weight when the venue provides a timestamp.

function subPeriodReturn(
  navStart: Decimal,
  navEnd: Decimal,
  flow: CashFlow | null,
): Decimal {
  if (!flow) return navEnd.dividedBy(navStart).minus(1);

  // INFLOW between navStart and navEnd
  if (flow.direction === "INFLOW") {
    return navEnd.minus(flow.amountUsd).dividedBy(navStart).minus(1);
  }
  // OUTFLOW between navStart and navEnd
  return navEnd.plus(flow.amountUsd).dividedBy(navStart).minus(1);
}
TWR sub-period return with cash-flow boundary.

What ends up on the verified track record

  • The CashFlow rows are publicly visible (when the trader's user.isPublic flag is true) at /verify/chain/[handle].json under a flows array.
  • Each row carries provenance: venue-side transaction id when available, reviewer id when human-confirmed, detector inference when neither is available.
  • The TWR engine logs which CashFlow rows it consumed for each sub-period, so a verifier can re-derive the published TWR exactly.
  • Reversals appear as separate rows pointing at the original via correctsId. The chain shows the full history.
  • A trader cannot retroactively delete a wrongly-classified flow; they can only append a reversal that explains and corrects it.

Common edge cases

There are a handful of recurring edge cases worth flagging. The first is multiple flows in a single day. Most venues report each flow individually, but the day-over-day NAV jump nets them. The detector raises one candidate, reconciliation finds two ledger entries, and the CashFlow table records both with the same flowDate but different timestamps and venueTxIds.

The second is rebates and dust. Some venues credit small affiliate rebates or VIP rebates that look like flows but are properly returns. NakedPnL classifies these as returns by default with a 1 USD floor on flow detection — anything below that threshold is presumed return regardless of detector signal.

The third is venue token airdrops. When an exchange distributes a token to all users, the user's NAV jumps without a corresponding deposit. NakedPnL treats venue airdrops as flows because they are external to the trader's strategy. The provenance is DETECTOR_INFERENCE with a venueAirdrop=true flag for transparency.

Verified, not advisory
The cash-flow detector and the published CashFlow table are part of the verification system. They establish data quality, not investment merit. Use the registry alongside operational and counterparty diligence, not as an investment recommendation.

Summary

  • Time-weighted return requires clean cash-flow attribution. Most exchange APIs do not provide it in usable form.
  • NakedPnL detects probable cash flows from large day-over-day NAV jumps with venue-tuned thresholds.
  • Detection is biased toward false positives over false negatives because under-flagging silently inflates returns.
  • Reconciliation against venue ledgers (where available) confirms or rejects each candidate. Otherwise human review with trader-supplied evidence resolves the case.
  • The CashFlow table is append-only. Reversals are new rows that reference the original; existing rows are never edited.
  • Verifiers can see every confirmed flow, its provenance, and any subsequent corrections directly from the public chain JSON.

Frequently asked questions

Why does NakedPnL not just import the deposit history from the exchange?
Where the venue exposes a reliable deposit/withdrawal endpoint, NakedPnL does import directly — IBKR, Polymarket, and Bybit are good examples. For other venues (Binance, OKX, Kalshi) the cash-flow endpoint is incomplete, rate-limited, or absent entirely, so detection-from-NAV-jumps is the fallback. Imported flows and detected flows are both stored in the same CashFlow table with provenance recorded.
What if the detector misclassifies a real trading gain as a deposit?
The trader sees the misclassification on their dashboard and can dispute it with venue-side evidence (a trade history screenshot, an exported PnL CSV). Operations reviews the evidence and writes a reversal row in the CashFlow table. The original row is preserved; the reversal references it via correctsId. The TWR re-derives once the reversal is committed.
Why bias toward false positives instead of false negatives?
False negatives — missing a real cash flow — silently inflate returns. The trader has weak incentive to dispute an inflated number. False positives — flagging a real return as a flow — deflate returns and the trader has strong incentive to dispute. The asymmetry of who-will-correct-the-error means false positives are cheaper for the system and the trader population.
Are the venue-specific thresholds public?
Yes. They are documented in this guide and in the methodology section of /docs/verification. The thresholds are reviewed quarterly against the updated empirical distribution of daily returns observed across the active trader population, and any changes are announced with at least 14 days notice.
What about venue token airdrops?
Venue airdrops are treated as inflows because they are external to the trader's strategy. The provenance is DETECTOR_INFERENCE with a venueAirdrop=true flag, which is exposed in the public CashFlow data so verifiers can see the classification. Some users prefer to count airdrops as returns; that is a defensible position but not the one NakedPnL takes.
If a trader reverses a flow classification many days later, does the chain change?
The NavSnapshot chain rows themselves are append-only and never change. The reversal is a new CashFlow row, and the TWR engine recomputes the published return statistics by re-reading the (now-augmented) flow history. The published numbers update; the underlying cryptographic chain does not.
Does this method work for futures accounts where unrealized PnL fluctuates wildly?
Yes, with a higher threshold (40% for Binance futures versus 25% for spot, for example). Futures NAV is volatile because mark-to-market unrealized PnL is included, and the empirical distribution of daily moves is wider. The detector adapts by venue but the algorithm is the same.

References

  • CFA Institute — GIPS guidance on cash-flow handling
  • Modified Dietz method — definition and worked example
  • Binance — capital deposit/withdraw history endpoint
  • IBKR Flex Web Service — DepositsWithdrawals query reference
  • NakedPnL TWR methodology and verification reference
NakedPnL is a publisher of verified investment performance data. We are not an investment adviser, broker, dealer, or asset manager, and nothing on this page constitutes investment advice or a recommendation. See the compliance page for our full regulatory posture.