How PnL is Calculated
FinCobra uses a per-token cost basis model. For each token you hold, we track the cost of your current position using a zero-crossing algorithm, then compute PnL as currentValue - costBasis.
Sources without transaction history (Solana, Hyperliquid, Aster) show balance only — no PnL.
Per-Token PnL — Zero-Crossing Cost Basis Method
For sources with transaction history (Binance, Bybit, BTC xpub, and EVM wallets), FinCobra calculates PnL for each individual token. This uses a zero-crossing algorithm that isolates the cost basis of your current position.
Key Insight
Only the current position matters. If you held 1 BTC, sold it all, then bought 0.5 BTC — the cost basis only tracks the 0.5 BTC purchase. Everything before the balance hit zero is irrelevant, because that was a different position.
How it Works
The algorithm has two phases:
Phase 1 — Find the zero crossing (walk backward)
Starting from your current balance, walk backward through transaction history, undoing each transaction's effect:
- Begin with
runningBalance = currentBalance - For each transaction (newest to oldest), subtract the transaction's delta
- When
runningBalancereaches 0 or below — that's the zero crossing. This is when your current position was opened.
Phase 2 — Accumulate cost basis (walk forward)
Starting from the zero crossing, walk forward through transactions:
| Transaction | Cost Basis Effect |
|---|---|
| Deposit / Buy | costBasis += usdValue |
| Withdrawal / Sell | costBasis *= (1 - amountSold / totalHeld) — proportional reduction |
| Earn subscribe / redeem | No effect (internal move between spot and earn) |
Finally: PnL = currentValue - costBasis
Worked Example
You trade ETH on Binance. Your current balance is 2 ETH at $3,000 each ($6,000 total). Here's your transaction history:
| # | Date | Type | Amount | USD Value | Running Balance |
|---|---|---|---|---|---|
| 1 | Jan 1 | Buy | +3 ETH | $6,000 | 3 ETH |
| 2 | Jan 15 | Sell | -3 ETH | $6,600 | 0 ETH |
| 3 | Feb 1 | Buy | +1.5 ETH | $4,200 | 1.5 ETH |
| 4 | Feb 10 | Sell | -0.5 ETH | $1,500 | 1 ETH |
| 5 | Mar 1 | Buy | +1 ETH | $3,200 | 2 ETH |
Phase 1 — Walk backward from 2 ETH:
| Step | Transaction | Delta | Running Balance |
|---|---|---|---|
| Start | — | — | 2.0 |
| Undo #5 | Buy +1 ETH | -1 | 1.0 |
| Undo #4 | Sell -0.5 ETH | +0.5 | 1.5 |
| Undo #3 | Buy +1.5 ETH | -1.5 | 0.0 |
Zero crossing found at transaction #3. Everything before (transactions #1 and #2) is a previous, closed position.
Phase 2 — Walk forward from #3:
| Step | Transaction | Cost Basis | Total Held |
|---|---|---|---|
| #3 | Buy 1.5 ETH @ $4,200 | $4,200 | 1.5 |
| #4 | Sell 0.5 ETH @ $1,500 | $4,200 * (1 - 0.5/1.5) = $2,800 | 1.0 |
| #5 | Buy 1 ETH @ $3,200 | $2,800 + $3,200 = $6,000 | 2.0 |
Result:
costBasis = $6,000
currentValue = 2 ETH * $3,000 = $6,000
PnL = $6,000 - $6,000 = $0 (0%)Even though the account saw profitable trades, the current position's cost basis matches its value — $0 PnL on this position.
Portfolio PnL
Portfolio PnL is computed client-side by aggregating per-token PnL across all wallets and exchanges:
portfolioPnl = Σ(liveTokenValue - costBasis) for all tokens with PnL data
portfolioPercent = portfolioPnl / totalCostBasis * 100Sources without per-token PnL data (Solana, Hyperliquid, Aster) contribute to total portfolio value but not to PnL.
Reliability Flag
Per-token PnL rows have a reliable flag. PnL is marked as unreliable when:
- No zero crossing found — the position predates the available transaction history, so cost basis may be incomplete
- Missing USD values — any deposit or buy transaction in the cost basis path has a NULL or zero USD value
Unreliable PnL values are shown with an asterisk (*) in the dashboard. They represent a best-effort estimate rather than a precise calculation.
Wallet Per-Token PnL
EVM wallets (Ethereum, Arbitrum, BSC) and BTC xpub wallets support per-token PnL. The algorithm is the same zero-crossing method, with wallet-specific transaction type mapping:
| Wallet tx type | Delta |
|---|---|
receive | +amount (like a deposit) |
send | -amount (like a withdrawal) |
contract_interaction | direction based on sign of amount |
approval | 0 (no balance change) |
Wallet token PnL is triggered when the frontend sends token balances to the backend during each balance refresh cycle.
Wrapped Token Normalization
Wrapped native tokens are automatically normalized: WBNB balances are treated as BNB, WETH as ETH. This ensures that PnL calculations correctly merge native and wrapped positions.
BSC Native Asset
BSC chain native transactions are correctly stored as BNB (not ETH). The CHAIN_NATIVE_ASSET constant maps each chain's sourceId to its native token symbol.
Tracked Assets
Per-token PnL is computed for the following priceable assets:
BTC, ETH, SOL, BNB, XRP, DOGE, ADA, AVAX, DOT, LINK, MATIC, UNI, ATOM, NEAR, TRX, TON, HBAR, PEPE, SHIB, ARB, LTC, SUI, APT, OP, AAVE, FIL
Other tokens (random ERC20s, unlisted tokens) are excluded from per-token PnL but still count toward the account's total USD value.
Dust Threshold
Token positions with a USD value below $1 are excluded from per-token PnL calculations. This prevents noise from trivially small balances (airdrops, dust from trades, etc.).
Stablecoins
The following stablecoins are excluded from per-token PnL since their value is pegged to $1:
USDT, USDC, BUSD, FDUSD, TUSD, DAI, USDP
Stablecoin balances still count toward your account's total USD value.