Portfolio & PnL
Superhero keeps two views of a position: the holdings list, which is your current balances priced at spot, and the portfolio history, which is a series of valuation snapshots with profit and loss attached. Both are addressable for any account, not just your own.
From the command line
node scripts/superhero-portfolio.mjs holdings # your positions
node scripts/superhero-portfolio.mjs holdings ak_... # anyone's positions
node scripts/superhero-portfolio.mjs holdings ak_... 20 2 # limit 20, page 2
node scripts/superhero-portfolio.mjs summary # holdings + latest snapshot
node scripts/superhero-portfolio.mjs history # value over timeholdings and summary read the public API and need no signing key for another account's address.
The script derives your address from AE_PRIVATE_KEY, so the no-argument forms require it to be
set.
Holdings
Backed by GET /api/accounts/{address}/tokens, with includes=token so each row carries the full
token record.
| Field in the script's output | Source | Meaning |
|---|---|---|
token_name | token.name | The token's name, which is also its symbol |
token_symbol | token.symbol | Same string in practice |
sale_address | token.sale_address | The bonding-curve contract, the handle for buy and sell |
token_address | aex9_address or token.address | The AEX-9 contract, for allowances and transfers |
balance | balance | Tokens held |
buy_price_ae | token.price | Current spot buy price, per token |
sell_price_ae | token.sell_price | Current spot sell price, per token, 0.5% below buy |
trending_score | token.trending_score | Momentum signal for that token |
Value a position at sell_price_ae, never at buy_price_ae. The buy price is what the next person
pays to enter; the sell price is the side you are on. Compute a position's AE value from balance
and sell_price_ae, with the caveat below.
Portfolio history
Backed by GET /api/accounts/{address}/portfolio/history, which accepts startDate, endDate,
interval, convertTo and include. Pass include=pnl or include=pnl-range to get the PnL
block. The two differ; see below.
Snapshots are computed on request
Nothing is recorded periodically. The backend takes your startDate, endDate and interval,
generates the timestamps, maps each to a block height, and reconstructs holdings at that height by
replaying your own transaction rows. The AE side is a historical getBalance against the node.
Two consequences:
-
Every interval returns real data, not interpolation.
-
The interval you ask for is a floor, not a promise. The controller raises it based on the range length:
Range length Minimum interval Under 1 month 1 hour 1 to 3 months 4 hours 3 to 6 months 1 day 6 months or more 1 week No dates given at all 1 day The effective interval is
max(your interval, that minimum); the default when you pass none is 86,400 seconds. Ask for 5-minute granularity over a year and you get weekly points.
The AE balance lookup floors each block height to a multiple of 300 key blocks (roughly 15 hours), so snapshots inside the same bucket share one AE balance. Token values are exact per snapshot; the liquid-AE line is stepped. Responses are cached for 10 minutes.
Each snapshot:
| Field | Meaning |
|---|---|
timestamp | When the snapshot was taken |
total_value_ae | Everything the account holds, in AE |
ae_balance | Liquid AE |
tokens_value_ae | Value held in token positions |
total_value_usd | The same total, converted |
total_pnl | Present when include=pnl-range; see below |
And inside total_pnl:
| Field | Meaning |
|---|---|
percentage | gain.ae / invested.ae × 100, or 0 when nothing was invested |
invested | Cost basis, as ae and usd, defined differently in the two modes (see below) |
current_value | Market value of tokens still held, as ae and usd. Always cumulative |
gain | The profit figure, as ae and usd |
range | The from and to bounds, present only under include=pnl-range |
How PnL is computed
Cost basis is average cost, not FIFO. There is no lot tracking: the backend sums what you spent and what you bought, and divides.
include=pnl is cumulative mode, per token, summed across tokens:
invested = every AE you ever spent on this token (all buy + create_community rows)
current_value = tokens still held × current unit price
gain = proceeds from all your sells + current_value − investedinclude=pnl-range is realised-only mode, per snapshot window:
avg_cost = (all-time AE spent) / (all-time volume bought)
invested = avg_cost × volume sold in this window
gain = sell proceeds in this window − investedRange mode excludes unrealised gains, so a day only turns green or red when a trade is closed. It is what the app's PnL calendar and sparkline are built on. Cumulative mode is what you want for "how is this position doing".
A partial sell is attributed at the all-time average purchase price, blended across every buy you ever made in that token, including buys made after the sell. A realised gain reported for a past day can therefore change if you buy more of the same token later.
Three details that decide whether a number looks right:
- The 0.5% affiliation fee is inside
invested. The cost basis comes from the transaction row'samount, decoded from the sale contract'sBuyevent, whose first argument istotal_price, the fee-inclusive AE you paid. - Gas is not. The æternity transaction fee never reaches the contract, so it appears in no row and in no cost basis. Your real cost is slightly higher than
investedsays. current_unit_priceis the last trade's price, not a live quote. It is thebuy_price.aeof the most recent transaction at or before the snapshot height. On a token nobody has traded in a week, your position is valued at last week's price. It is also a buy-side unit price, which overstates an exit for the reason below.
The per-token breakdown is on its own endpoint:
GET /api/accounts/{address}/portfolio/tokens/history returns the same snapshot shape with a
tokens_pnl map keyed by sale_address, each entry carrying current_unit_price, percentage,
invested, current_value and gain. PnL is always included there, and it is not available on
/portfolio/history at any include value. The payload is large, so use it deliberately.
The history command prints the newest and oldest snapshots, the count, the full array, and a
period PnL computed as latest.total_value_ae − oldest.total_value_ae. That is a difference in
valuation, not a realised result: it moves when prices move, even if you never traded.
Your position is worth less than it says
A holdings row prices your balance at the current spot sell price (one number times another). But selling is an integral: as your tokens burn, the supply falls and every subsequent token in your own sell fetches less than the one before it.
quoted value = balance × sell_price ← a rectangle
actual exit = 0.995 × ∫ from (S − balance) to S of buy(x) dxFor a token with 10,000,000 tokens in supply:
| Your balance | Share of supply | Quoted value (AE) | Actual sell return (AE) | Shortfall |
|---|---|---|---|---|
| 100,000 | 1% | 10.47 | 10.42 | 0.5% |
| 1,000,000 | 10% | 104.74 | 99.26 | 5.2% |
| 5,000,000 | 50% | 523.72 | 388.53 | 25.8% |
A small position is quoted accurately. A position that is a meaningful fraction of the supply is not, and the discrepancy grows with your share. If you hold half a token's supply, the portfolio page overstates your exit by a quarter.
For the exact figure, call sell_return(balance) on the sale contract. That is the number the curve
will pay, and it costs nothing to ask. See Buying & Selling.
Why a position can lose value without you doing anything
- Other people sell. The supply falls, the curve slides down, and every remaining holder's position is repriced lower.
- Other people buy. Your position's AE value rises, but your share of the supply falls, because new tokens were minted. On a governance stake, that dilution matters even while the value line goes up.
- The AE price moves.
total_value_usdcan fall whiletotal_value_aeis flat, or vice versa. Decide which unit you are measuring in and be consistent.
There is no mechanism by which a position gains or loses value from time alone: no staking yield, no rebasing, no inflation schedule.
Realised versus unrealised
Nothing in the portfolio view is realised until you sell, and the 1% spread means a freshly opened
position starts about 1% underwater. The public
trading leaderboard (opens in a new tab) ranks accounts by pnl_usd,
roi_pct, aum_usd and mdd_pct (maximum drawdown) alongside buy_count, sell_count,
created_tokens_count and owned_trends_count. These are the numbers to compare against if you want to know
how a strategy is doing relative to other people rather than relative to its own entry.
Related
- Buying & Selling:
sell_returnand the allowance step, for turning a position back into AE - Bonding Curves: the integral behind the shortfall table above
- Transaction History: your actual fills, which is where a cost basis comes from
- Reading a Token Page: the same caveat applied to a token's market cap
- Accounts API: the endpoints behind both views, including the leaderboard