How tax is calculated
From transactions to the Tax Overview
A step-by-step explanation of how Portfolio Manager turns imported transactions into the tax estimates shown in the Tax Overview.
The Tax Overview does not conjure its numbers from thin air. Behind every estimated tax figure is a pipeline that takes your raw transaction data, classifies it under Danish tax law, and applies the relevant rates. This article walks through each stage of that pipeline.
Overview: the four stages
| Stage | What happens |
|---|---|
| 1. Import | Transactions arrive from Saxo Bank, Nordnet, Bitstamp, or manual entry and are stored with all fields needed for tax. |
| 2. Tax lots and events | TaxTrackingService processes each transaction and creates structured records: tax lots (for open positions) and tax events (for taxable occurrences). |
| 3. Bucket classification | Every account+instrument pair is assigned to a tax bucket, which determines the income type, tax principle, and cost basis method. |
| 4. Overview computation | TaxOverviewService groups events by income type, applies carry-forward losses, applies Danish tax rates, and produces the figures shown in the Tax Overview. |
The sections below cover each stage in turn.
Stage 1 — Transaction import
Transactions are stored in a common format regardless of where they came from. The fields that matter most for tax calculations are:
| Field | Purpose |
|---|---|
| Posting date | The booking date used as the fiscal date for all tax calculations. This determines which tax year an event belongs to. |
| Transaction type | A typed classification: BUY, SELL, DEPOT_TRANSFER_IN, DEPOT_TRANSFER_OUT, SPLIT, MERGER_IN, MERGER_OUT, and several others. |
| Quantity | Number of units bought or sold. Positive means buying, negative means selling. Zero for cash-only rows. |
| Text | The free-text description from the broker. Used for keyword classification of dividends, interest, fees, and taxes (see Stage 2). |
| costBasisDkk | The DKK cost basis locked in at import time. This field captures the historical exchange rate so that gain/loss calculations remain accurate regardless of when they are computed. |
How costBasisDkk is calculated per source
- Saxo Bank — the absolute booked amount in the account currency, converted to DKK at the trade-date FX rate.
- Nordnet — taken from the
beloebfield, orindkoebsvaerdi × vekslingskurswhen the account is not in DKK. - Bitstamp (crypto) — the DKK market value of the net fiat amount received or delivered at the time of the trade.
- Manual — entered by the user. If you purchase in a foreign currency, you must supply the DKK equivalent yourself, because there is no automatic FX conversion for manual entries.
Within-day ordering
Before processing, transactions on the same posting date are sorted so that purchases come before sales. This ensures that a same-day buy-then-sell creates a lot first and then consumes it — rather than triggering a short-sale error.
Stage 2 — Tax lots and tax events
Once transactions are in the database, TaxTrackingService walks through them in date order and produces two types of records.
Tax lots
A tax lot represents a specific parcel of an asset acquired at a known date and cost. Lots are created when the app sees:
- a BUY transaction (positive quantity on an instrument)
- a DEPOT_TRANSFER_IN (an instrument moved in from another broker)
- a crypto purchase (positive fiat change on a crypto account)
Each lot records the acquisition date, the original and remaining quantity, the total cost, and the cost per unit. When a position is later sold, lots are consumed in FIFO order (oldest first) — which is the standard Danish method for speculative positions and crypto.
For a depot transfer where the original acquisition cost is unknown, costBasisDkk is set to zero and the app raises an issue for you to resolve. The acquisition date used is the original purchase date from the source broker, not the transfer date — FIFO and holding-period calculations depend on this.
Tax events
Every taxable occurrence is recorded as a tax event. There are seven types:
| Event type | When it is created | Key fields |
|---|---|---|
| RealizedGainLoss | On every sale — one event per lot consumed. | proceeds, costBasis, fees, gainLoss, acquisitionDate |
| Dividend | When transaction text matches “dividend” or “udbytte”. | grossAmount, netAmount, withholdingTax, instrumentId |
| Interest | When text matches “interest” or “rente” (and is not also a dividend). | amount (at account level, not per instrument) |
| StakingReward | When text mentions “staking”, “staked”, or “stake reward”. | quantity, marketValue (DKK value at time of receipt) |
| TaxPaid | When text contains “tax”, “skat”, or “withholding”. | taxType (WITHHOLDING or TRANSACTION), amount |
| Fee | When text matches “fee”, “gebyr”, “custody”, or “depot”. | feeType, amount |
| LagerAssessment | At year-end for lager-taxed positions (see the lager principle below). | yearStartValueDkk, yearEndValueDkk, gainLossDkk |
The lager principle
Some accounts and instruments are taxed on unrealised gains annually — this is lagerbeskatning (mark-to-market taxation). Rather than waiting for a sale, you pay tax on the gain or loss as if you sold everything on 31 December and bought it back on 1 January.
The app creates a LagerAssessment event for each lager-taxed position each year:
yearStartValueDkk— market value on 1 January (or acquisition cost if the position was opened during the year)yearEndValueDkk— market value on 31 December (or disposal proceeds if the position was closed during the year)gainLossDkk= yearEnd − yearStart
Lager taxation applies to: all ASK (Aktiesparekonto) holdings, all ETFs and investment funds in regular accounts, and all pension accounts. Individual stocks in regular accounts are taxed on realisation only — no annual assessment.
For completed past years, LagerAssessment events are created by a background job in early January. For the current year, the calculation is done on the fly using current market prices, giving you a live view of where you stand.
Stage 3 — Tax bucket classification
Every account+instrument combination is assigned to a tax bucket. The bucket determines three things: which income type the gains belong to, whether lager or realisation taxation applies, and which cost basis method is used.
The buckets
| Bucket | Account/instrument | Income type | Tax principle | Cost basis |
|---|---|---|---|---|
ASK | Aktiesparekonto (any instrument) | Aktieindkomst | Lager | Weighted average |
FREE_STOCK | Regular account, individual stocks | Aktieindkomst | Realisation | Weighted average |
FREE_STOCK_POSITIVLIST | Regular account, ETF/fund on positivliste | Aktieindkomst | Lager | Weighted average |
FREE_STOCK_NOT_POSITIVLIST | Regular account, ETF/fund not on positivliste | Kapitalindkomst | Lager | Weighted average |
FREE_CAPITAL | Regular account, bonds/cash/derivatives | Kapitalindkomst | Realisation | Weighted average |
FREE_SPECULATIVE | Crypto account, crypto currencies | Personlig indkomst | Realisation | FIFO |
PENSION | Ratepension, Aldersopsparing | Pensionsbeskatning | Lager | Not applicable |
How the account type drives the mapping
- Aktiesparekonto — always
ASK, regardless of what is held. All gains are aktieindkomst taxed at the special flat ASK rate (17%) using lager. - Ratepension / Aldersopsparing — always
PENSION, taxed under PAL rules. - Crypto accounts — fiat/cash positions map to
FREE_CAPITAL; crypto currency positions map toFREE_SPECULATIVE. - Regular accounts — the instrument type drives the bucket: individual stocks →
FREE_STOCK; ETFs and funds → depends on the SKAT positivliste (see below); bonds, ETCs/ETNs, and cash →FREE_CAPITAL.
The SKAT positivliste
The positivliste (positive list) is a list of investment funds and ETFs published annually by SKAT. Funds and ETFs on the list are taxed as aktieindkomst (share income); those not on the list are taxed as kapitalindkomst (capital income). Individual stocks are never subject to this check — they are always aktieindkomst.
Portfolio Manager downloads the official SKAT Excel file and stores it per year. When classifying an ETF or fund, the app looks up the instrument’s ISIN in the stored list for the relevant year. If the ISIN is found → FREE_STOCK_POSITIVLIST (aktieindkomst, lager). If not found → FREE_STOCK_NOT_POSITIVLIST (kapitalindkomst, lager). Note that both ETF buckets use lager taxation — the positivliste check only determines which income type the gains fall under.
Stage 4 — The Tax Overview computation
With tax events classified into buckets, TaxOverviewService groups everything by owner and income type, applies carry-forward losses, applies rates, and produces the cached figures shown in the Tax Overview.
The four income types
| Income type | Danish name | What falls here |
|---|---|---|
| AKTIE | Aktieindkomst | Individual stocks, ASK holdings, ETFs on the positivliste |
| KAPITAL | Kapitalindkomst | Bonds, ETFs not on the positivliste, interest, cash |
| PERSONLIG | Personlig indkomst | Crypto trading gains, staking rewards |
| PENSION | Pensionsbeskatning | Pension account returns (PAL) |
The calculation chain per income type
For each income type, the computation follows these steps:
- Gross income — sum of all gains, dividends, interest, staking rewards, and lager gains for the year.
- Losses and fees — sum of all realised losses, lager losses, and fees.
- Net income = gross income − losses and fees.
- Carry-forward — any unused losses brought forward from prior years are subtracted from net income.
- Withholding tax — recorded separately and credited against the estimated tax; it does not reduce the net income figure.
- Taxable income — net income after carry-forward.
- Estimated tax — taxable income multiplied by the applicable rate(s), minus withholding tax credit.
- Carry-forward to next year — any remaining losses that could not be offset are passed to the following year.
Aktieindkomst: the two-rate structure
Aktieindkomst is taxed progressively. Gains up to the annual threshold are taxed at the low rate (27%); gains above the threshold are taxed at the high rate (42%).
| Year | Threshold | Low rate | High rate |
|---|---|---|---|
| 2023 | 58,900 DKK | 27% | 42% |
| 2024 | 61,000 DKK | 27% | 42% |
| 2025 | 67,500 DKK | 27% | 42% |
| 2026 | 79,400 DKK | 27% | 42% |
The 2026 threshold increase is tied to the top-topskat reform. The threshold is per person, not per household.
The Tax Overview shows a progress bar with your current aktieindkomst, the threshold, remaining headroom, and the estimated tax split across both rates.
ASK sub-bucket: ASK gains are taxed at a flat 17% (the special ASK rate), not at 27/42%. The ASK calculation is done separately within the AKTIE income type and then added to the total AKTIE estimated tax.
Carry-forward losses
Unused losses do not disappear — they are carried forward and applied to reduce taxable income in future years. The chain is recursive: computing year N requires computing year N−1 (which may require N−2, and so on).
Two buckets use isolated carry-forward pools:
- ASK — losses inside an aktiesparekonto can only offset future ASK gains. They cannot flow into the regular aktieindkomst pool.
- FREE_SPECULATIVE — crypto and speculative losses can only offset future speculative gains. They cannot reduce other personal income.
All other buckets within the same income type share a pooled carry-forward. For example, a loss from a stock sale (FREE_STOCK) and a loss from an ETF on the positivliste (FREE_STOCK_POSITIVLIST) are pooled and can offset any AKTIE gains in future years.
Kapitalindkomst carry-forward is not automatically chained. Whether a kapitalindkomst loss can be deducted depends on the taxpayer’s total personal income picture, which is outside what Portfolio Manager can determine. The app accumulates the carry-forward balance but does not automatically apply it year to year. You must check your prior-year tax return and enter any usable carry-forward manually.
PAL pension tax
Pension account returns are estimated at 15.3% (the statutory PAL rate, unchanged since 2012), applied to the annual lager gain/loss for the account. The actual PAL settlement is handled by your pension provider and may differ — for example, because of within-year contributions, withdrawals, or deductible costs inside the pension scheme. The figure shown in Portfolio Manager is indicative only. Always rely on your pension provider’s annual PAL statement for the actual figure.
Tax rates and your settings
The estimates for Kapitalindkomst and Personlig indkomst use rates you configure yourself in Tax Settings. For Aktieindkomst, you can also choose between using the low rate (27%) as your estimate or the more conservative high rate (42%).
To review or adjust your rates, go to Settings and open the Tax Settings panel. For a full walkthrough of the Tax Overview page and how to read each section, see Tax Overview.