Skip to content
Logo

Positions & PnL

How Flint tracks a perp position, what each number in the Positions tab means, and where to find your history.

A position

You hold at most one position per perp market. It's a signed size, positive for long and negative for short, inside your trading account. Every position shares the account's collateral (cross-margin), so there's no per-position margin to add or remove. To give a position more room, deposit collateral or shrink another position. An account can hold a limited number of perp positions at once (LinearPerpPositionLimitExceeded past that), and resting orders on a market take one of those slots even while you hold no position there. A market may also cap each account's net size (max_position_lots, LinearPerpPositionSizeExceeded past it).

FieldAPI (PerpPosition)Meaning
SizequantitySigned base units.
Entryentry_priceAverage entry price of the open size.
Markmark_priceThe price PnL and margin are measured at (below).
Valuenotional_usd|size × mark| in USD.
Unrealised PnLunrealized_pnl_usdWhat closing at the mark would realise, before fees.
Realised PnLrealized_pnl_usdBooked by reductions and closes.
Fundingaccrued_funding_usdFunding accrued on this position. Positive is a credit to you, negative a debit.
Liq. priceliquidation_priceMark at which the account would reach maintenance margin (below).

Entry price

Entry is a running average. The program stores the position's size and the cash paid for it, and the entry price is the ratio.

  • Adding on the same side blends the fill into the average.
  • Reducing keeps the entry price and realises PnL on the closed part.
  • Flipping (selling more than a long, or buying more than a short) closes the old position in full and opens the remainder at the fill price.

Mark price

The mark is the underlying asset's oracle price, put on the contract's tick grid. It is not the book mid or the last trade. Unrealised PnL, margin, and liquidation all use it. The mark expires quickly, and a market with a stale mark can't trade until someone refreshes it. See Prices, sizes, units.

PnL

unrealised PnL = size × (mark − entry)

This is in the contract's PnL currency, converted to USD at that asset's oracle price. A long gains when the mark rises; a short gains when it falls.

Your position's PnL has four parts, all of which end up in your cash balance once settled:

LegWhen it's booked
Realised trading PnLOn every reduce, close, or flip.
FeesOn every fill (taker fee; makers earn a rebate). See Fees.
FundingLazily: on every fill in that market and whenever anyone runs settleFunding for the account. Margin checks always count funding accrued so far.
Unrealised PnLNot booked; it's marked to market continuously.

For margin purposes, positive unrealised PnL counts at a haircut, while losses count in full. So an open winning position adds less buying power than its PnL figure suggests.

Funding

Funding is quoted as an hourly rate and accrues between longs and shorts. A positive rate means longs pay shorts. The Funding column shows the market's current per-hour rate; FundingService.GetFundingRates returns it from the API. How the rate is set is on Funding.

Margin and liquidation price

Each position needs margin based on its notional:

  • Initial margin = notional × the market's initial margin rate. You need this to open or add.
  • Maintenance margin = notional × the maintenance rate. Fall below this and the account can be liquidated.

Both are summed across the whole account and compared with its net equity. The account snapshot's health carries net_equity, initial_margin, maintenance_margin, free_collateral, and health_ratio (net equity ÷ maintenance margin). The maximum leverage on a market is 1 ÷ its initial margin rate. See Collateral & margin.

Liquidation price is the mark at which the account's equity would fall to its maintenance margin if only this market moved. Every other position and balance is held fixed. Because margin is shared, another position's PnL or a withdrawal moves it. It's unset when the account is already below maintenance, the position isn't priced, or no positive price would trigger liquidation.

The app estimates it client-side across the account, shown with ≈, or None when no price would liquidate the position. The ticket's Liquidation price row previews it before you trade. What happens at liquidation is on Liquidations.

The Positions tab

Columns: Type, Market, Side, Size, Entry, Mark, Value, Liq. price, Unrealised PnL, Margin (initial margin for the position), Funding (per-hour rate), and a Close button. Lend and borrow legs appear in the same table with Redeem / Repay.

Adding and reducing

Trade in the same market from the order ticket. The main button says what will happen: Add to long, Reduce long, Close long, or Flip short. Reducing realises PnL on the closed part; the entry price of the rest doesn't change.

Closing

Close on a position row opens Close position:

  • Choose 25%, 50%, 75%, or Max.
  • Review Closing, Mark, Exit value, Realised PnL (est.), and Fee (est.).
  • Press Close position (or Reduce {n}%).

It sends a reduce-only FOK order at your Max slippage from the current mark. The selected size is rounded down to whole lots and must fill in full or nothing changes. If it cannot fill, review the available liquidity, close size, and price bound before trying again. There's no "close all" button; close each position separately.

Settlement

Realised PnL, fees, and funding stay on the position until they're settled into a cash balance in the contract's settlement asset. The Settlements tab lists Unsettled PnL per market, with Settle and Settle all buttons. Each sends settleRealisedPnl, signed by your wallet.

  • A net loss is paid from your idle balance of that asset, and borrowed if the balance runs short.
  • A net gain is paid from the contract's PnL pool, up to what the pool can pay in whole ticks. An oversized gain can settle partially; the unpaid amount stays on the position. PnlSettlePoolExhausted means no payable ticks remain. SettlementBelowMinimum means the payable amount is below the caller's minSettleTicks.

settleRealisedPnl is permissionless. Anyone can settle any account, and it reverts with NoRealisedPnlToSettle if there's nothing to do. Settle before withdrawing profits.

History

In the app's account drawer:

TabShows
HistoryFills plus deposits, withdrawals, lending, and borrowing in one table: time, market / asset, action, amount, price, value, realised PnL, fee.
PerformanceAccount value, Account PnL, Health, and Perp PnL charts over 24h / 7d / 30d. Perp PnL → Outstanding breaks unsettled PnL into trading, funding, and fees.

From the API (all public, keyed by trader id):

RPCReturns
AccountService.GetFillsYour fills, newest first: price, quantity, side, maker flag, fee and rebate in USD. Paginated (page_size up to 1000).
AccountService.SubscribeFillsLive fills. On DATA_LOSS, reconcile with GetFills.
AccountService.GetAccountHistoryAccount value (nav_usd), PnL, debt, and health over time. With include_perp_pnl, also total, realised trading, fee, and funding PnL.
AccountService.GetActivityDeposits, withdrawals, lend, redeem, borrow, repay.
FundingService.GetFundingRatesCurrent funding rate per market.
use flint_margin_user_client::{Client, DEVNET_PUBLIC_ENDPOINT};
use flint_margin_user_client::proto::{GetAccountHistoryRequest, GetFillsRequest, TraderId};
 
let client = Client::connect(DEVNET_PUBLIC_ENDPOINT).await?;
 
let fills = client
    .account()
    .get_fills(GetFillsRequest {
        trader_id: Some(TraderId { id: 42 }),
        perp_ids: vec![],
        start: None,
        end: None,
        page_size: 100,
        page_token: String::new(),
    })
    .await?
    .into_inner();
 
let history = client
    .account()
    .get_account_history(GetAccountHistoryRequest {
        trader_id: Some(TraderId { id: 42 }),
        start: None,
        end: None,
        resolution_seconds: 0,
        include_perp_pnl: true,
    })
    .await?
    .into_inner();

All money fields are decimal strings. Parse them with rust_decimal or bignumber.js, never floats. Unset start / end default to the last 24 hours for GetAccountHistory.

Leaderboard

The app's Leaderboard page ranks accounts by PnL, PnL %, Volume, Account value, Open interest, Closest to liquidation, Largest liquidation, or Sharpe ratio. It covers 24h, 7d, 30d, or All time, with an optional minimum account value and the top 20, 50, or 100.

The same data is public through AccountService.GetRankedAccounts:

MetricDefinition
PNLChange in account value minus deposits and withdrawals.
PNL_PERCENTLinked hourly Modified Dietz returns, in percent.
VOLUMEPerp notional traded, as taker or maker.
SHARPE_RATIOAnnualised hourly Sharpe, zero risk-free rate; needs 24 hourly returns.
LARGEST_LIQUIDATIONLargest single liquidation in the window.
ACCOUNT_VALUE, OPEN_INTEREST, CLOSEST_TO_LIQUIDATIONCurrent state; the timeframe is ignored.

Windows are rolling 24 hours, 7 days, or 30 days of completed hours. Every metric sorts descending except CLOSEST_TO_LIQUIDATION. Results are capped at 100 and aren't paginated. history_complete is always false while the archive may have gaps.