The format, field by field
The format into which we bring every transaction history: what an event is, the fields it carries, the calculation conventions and why those ones, and what validation refuses.
This text is what stands: it is what the reports, the acknowledgement and the other pages quote, word for word. It changes only with a date.
The format
What we measure, and how each row is read.
An event is a movement, not an order
We do not store buy and sell orders. We store dated economic movements: what went out, what came in, what it cost.
That is a design decision, and it has a direct consequence for you: an exchange on a platform and an exchange in your personal wallet use the same shape. The first is a special case of the second, not the other way around.
The direction of the operation and its price are therefore never stored — they are calculated from the two legs of the movement. When both legs are cryptocurrencies and neither one is a reference currency, your statement carries no price: we then take the price of the leg sent, at the minute of the operation, from the series we have deposited and frozen. If that leg has no series, we take the leg received, at the same minute. If neither has one, there is no price, and we say so rather than invent one.
The fields
Required
| Field | Content |
|---|---|
ts | Date and time, UTC, explicit timezone, to the second. Never local time. |
kind | Movement type: exchange · deposit · withdrawal · internal transfer · reward · fees only · unknown. |
sent_amount / sent_asset | What went out, and in which asset. Empty for a pure deposit. |
recv_amount / recv_asset | What came in. Empty for a pure withdrawal. If both legs are empty, the row is rejected. |
event_id | Reproducible identifier, computed from the source and the content. |
unknown is a legitimate value. A movement we cannot classify must remain visible and be counted as such, not disappear from the file.
Fees — and the field missing everywhere else
| Field | Content |
|---|---|
fee_amount / fee_asset | The fees actually paid, in the currency where they were charged. Never converted on import. |
fee_scope | Are the fees already deducted from the amounts, or added on top? |
This last field does not exist in any export format we have reviewed. Yet it is the field that decides whether fees are counted once, twice, or not at all — and the error is invisible in the final result. When the source does not let us decide, we keep the most cautious assumption, the one that understates your performance, and the report says so.
Provenance
| Field | Content |
|---|---|
source_id | The name of a format, not of a platform: kraken.trades, ledgerlive.csv, manual.<fingerprint>. One operator can produce several incompatible exports; the format is what we verify. |
source_status | certified or accepted — see part 02. |
account_ref | A local pseudonym, generated on import. It is neither an address nor an account ID. |
kraken.tradesis the name of something we have read, not of something you need to own.No platform is required to use Sextant.
The conventions, and why they are what they are
| Topic | What we do |
|---|---|
| Numbers | Exact decimal math, never floating point. No rounding before output. The only rounding is the one at output: to the nearest value, and on an exact tie to the even digit — €0.125 gives €0.12, €0.135 gives €0.14. That mode is called round half to even (ROUND_HALF_EVEN) and it leans neither way. The other common mode pushes every half-cent away from zero: on a report whose headline figure is a gain, that is a systematic bias, tiny, and in the direction that suits us. We took the one that does not suit us. Display: 2 decimals in euros, 8 in crypto. We publish the mode because without it you cannot land on our figure to the cent. Over three years of history, rounding drift becomes visible — a checker cannot have that. |
| Timezone | Everything is brought back to UTC — Coordinated Universal Time, the worldwide reference independent of local clocks. The original timezone of each source is recorded in its sheet. One hour of drift is enough to reverse the order of two operations, and therefore to break the matching — the pairing of a buy with the sell that closes it. |
| Duplicates | Two overlapping exports are the normal case: some platforms limit each statement to one year. We deduplicate — and the number of duplicates removed is a line in the report. Deduplicating in silence would be an omission. |
| Transfers between your own accounts | We propose, you confirm. Never applied silently: reading them as sells would fabricate a gain that does not exist, ignoring them would erase fees that were paid. If you go past this step, the report says the multi-account scope is not reliable. What your file declares itself counts as confirmation: a row your export says is an internal transfer is not guessed, and we do not put it back to you. The rule targets inference, not declaration — a match that we establish between an outflow and an inflow, or a movement type that would come from our header mapping table rather than from your file. |
| Conversion to euros | European Central Bank reference rate, on the day of each operation, with the source cited. Never an average rate, never the rate on the report date. Consequence to know: the euro total is not the conversion of the dollar total. USDT and USDC are treated like the dollar — two tokens meant to track the dollar, which sometimes drift from it by a few tenths of a percent. It is an approximation, the only one we make on a currency, and it stops at those two: no other stablecoin qualifies until we have written it here. We write it rather than assume it — an unstated approximation is indistinguishable from a correct calculation. |
| A day with no ECB rate | The European Central Bank publishes no rate on Saturdays, Sundays and TARGET closing days; crypto, for its part, trades seven days a week. When an operation falls on one of those days, we take the last rate published before it — Friday’s for a Saturday or a Sunday — and the report cites the date of the rate used, not only the rate. The two other possible readings are ruled out for the same reason: the next business day and interpolation both use information dated after the operation, and a measurement engine never looks into the future. |
| Where our prices come from | A report you cannot redo is not a verification. Our price series are therefore deposited by hand in our repository, and frozen — never queried live. The difference is not theoretical: the same question asked tomorrow of a live source does not necessarily give the same answer, and no one can say afterwards which of the two was used. Here, every report cites the series it read by its sha256 fingerprint — the unique identifier of a file, which changes if a single byte does — and by the date range it covers. Two reports carrying the same fingerprint read exactly the same figures. In ten years too. The euro/dollar rates come from the European Central Bank's historical series. Cryptocurrency prices come from the venue where the operation was executed — for a Binance file, Binance's public market data (data.binance.vision): dated monthly files whose fingerprint Binance publishes, which we cite alongside ours. The price retained is the close of the one-minute candle containing the operation, in UTC — not a daily price. The reason is measured, not assumed: on our first real file, the price actually obtained for the same asset varied within a single day by 1.5% in median, and up to 37% on one row; a daily price would have multiplied that gap across hundreds of rows. When no trade took place during the operation's minute, we retain the last known close within the preceding sixty minutes — never after the operation — and the row says so; beyond that, the operation leaves the measured scope and is counted. The final basket is valued at the last UTC minute of the measurement day. A history with no execution venue — a wallet — has no rule yet: it will be written here, dated, when a real file raises it, and not before. |
| Crypto-to-crypto swap | Valued at the price of the sent leg at the minute of the operation — what actually left your pocket. Failing a series for it, at the price of the received leg at the same minute. Failing both, out of scope and counted. |
| Assets received for nothing in return | Any asset received without giving anything in exchange is valued at the price of the minute you received it — a staking reward (locking cryptocurrencies in exchange for rewards), interest from a savings product, a free token distribution, a promotional payout. Only their later variation is credited or debited to your decisions. It is the absence of anything given in return that decides, never the label your platform puts on the row: a definition keyed to commercial labels expires at the first interface change, and already does not hold from one platform to another. A consequence, and it does not work in our favour: these assets also enter the basket that compares you to doing nothing. They improve the scenario where you had done nothing — so they harden the judgement passed on your decisions. Excluding them would have made your trading in and out look better. This is a measurement convention — it is not tax treatment, and this page provides none. |
| Cost basis | When a position is sold only in part, the cost of what is sold is calculated at the asset's weighted average price — never first-in-first-out. Two reasons: the weighted average does not depend on row order, and an export's order is not always guaranteed; and the other methods are tax conventions, which vary from one country to another. This is a measurement convention, and this page provides no tax treatment. If your accounting uses a different method, the discrepancy is normal — and you can redo it, since ours is written here. |
| Buying on credit (margin) | Margin — borrowing in order to buy more than you deposited. This is where a percentage easily becomes flattering: relating a gain to your own contribution alone, while saying nothing of the borrowed money that produced it, inflates the result without a single figure being false. We do not do that. The money you borrow counts as money at work: it enters the capital compared at the moment you commit it, on the same footing as your own, and the high it causes stays — a loan repaid two hours later raises your compared capital thereafter. That is deliberately the reading that favours you least. Borrowing interest is a line of its own, separate from fees: you see separately what trading cost you and what borrowing cost you. Two different decisions, two different bills. ⚠️ A loan is almost never declared as such in an export, and its interest never is. When we deduce them — the loan from the row labels, the interest from the gap between borrowed and repaid — that is an inference, the report says so, and it is never dressed up as something you declared. An export that does not name its loans will let them go unnoticed — and the report says that too. A liquidation is named when the export declares one; if nothing there tells the sales it forced from the ones you decided, the report counts the former as decisions and says so. |
| Position matching | A purchase closed by several sales forms a single position, closed on the date of the last sale that closes it. Counting each sale separately would mechanically inflate the win rate whenever a winning exit is split — exactly the kind of effect we look for elsewhere. |
⚠️ “Our prices” and “the sources” are not the same thing. The list in part 02 records the statement formats whose handling we have verified — what you send us. The series above are our price references — what we value with. The two are independent, and deliberately so: where our prices come from asks nothing of you and says nothing about where you bought. No platform is required to use Sextant.
What we do not measure, and refuse to approximate
| Case | What we do with it |
|---|---|
| Liquidity provided to a protocol | Removed from the measured scope, and counted as such. |
| Moving from one chain to another | Undetectable unless you tell us. If undeclared, it appears as an outflow and then an inflow — so it is counted twice. The report flags it. |
| Failed transaction with fees paid | Counted in cumulative cost, never in per-position result. |
| Asset with no available price series | Removed from scope, and the report says how many assets are affected. |
Each of these cases can be “handled” by an approximation that produces a seemingly normal number. None of those approximations can be checked by you. Approximating in silence would be exactly what we criticize the market for. So we count what we cannot measure, and we show it.
Validation
One rejection applies to the file, then three levels apply row by row.
File rejection — unknown format version. A file is read only if its columns match the contract published above. If they do not, we reject the entire file and tell you which columns are missing: we do not half-read a format we do not recognize. This rejection is not about provenance. A file from a source absent from the part 02 list is perfectly readable — it simply carries the “uncertified source” label. Not having tested an export and not being able to read it are two different things, and we do not conflate them.
Then, row by row, three levels:
- Reject — missing or unreadable date, both legs empty, fees without currency.
- Warning — fee scope not stated, unclassified movement. These warnings do not block anything: they feed part 03 — The limits.
- Information — duplicate removed, row outside the period.
The period of an audit is the one you request, and it is written on the report. If you request none, the period is that of your file — from the first to the last movement — and then no row is outside the period. A row excluded for this reason is not a flaw in your export: the report says how many rows were set aside, and for which window. Not to be confused with the window where your data and our reference prices overlap: that one is a limit of our prices, and only concerns the buy & hold comparison.
A warned row stays counted. It enters the count of measured events and the measurements — that is the direct consequence of warnings not blocking anything. In return, the report states how many of the retained events carried a warning: a figure computed on warned data without saying so would be exactly the flaw this page exists to make visible.
A partially readable file produces a partial report. Never a silent failure.
↑ Contents