@fin.cx/depot
A broker-neutral model of securities depots and the pure engines a German business's books need for them: the positions of shares, funds, bonds, warrants, options, futures and CFDs at moving average cost across depots, what each trade, coupon, corporate action and loan does to them, the realised and unrealised results with the kind of result the tax law cares about (§ 8b KStG, § 15 Abs. 4 EStG, Stillhalterprämien), the year-end valuation under § 253 HGB, the investment-fund tax ledger of the InvStG for a corporation, the reconciliation against the broker, and time- and money-weighted performance. No persistence, no network, integer cents and decimal strings.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
Install
pnpm install @fin.cx/depot
What it holds
- The model (
IDepotStatement): a statement of one depot for a period from any broker — its instruments (IInstrument, withIDerivativeTermsandIBondTerms), its events (TDepotEvent: trades, FX conversions, cash movements, corporate actions, securities transferred in or out, securities lent), its cash per currency and day (ICashBalance), its positions at the day's close (IPositionSnapshot), interest accrued and not yet posted, and what the connector can deliver (IStatementCapabilities). Amounts are integer hundredths of a currency's unit (TCents, as@fin.cx/calculationcounts them); quantities, prices and rates are decimal strings (TDecimal). A connector produces it: for Interactive Brokers,@fin.cx/depot/ibkrfrom@apiclient.xyz/ibkr's statements (below). - Statements:
statementProblemsOfnames everything that does not hold together (days, decimals, cents, unknown instruments, duplicate keys);mergeStatementsjoins overlapping statements, the later one winning;sortEventsorders events by day and time. - The securities ledger (
replayLedger): the positions the events leave and what each did, in the books' currency. - The valuation (
valuationProposalsOf,positionValuesOf): what a closing date asks of each position, and the unrealised results. - The fund tax ledger (
fundTaxLedgerOf,fundTaxScheduleOf): the InvStG beside the books. - Reconciliation (
cashBreaksOf,cashMismatchesOf,positionDifferencesOf) and performance (timeWeightedReturnOf,moneyWeightedReturnOf,annualizedReturnOf,allocationOf,incomeOf).
The securities ledger
import * as depot from '@fin.cx/depot';
const ledger = depot.replayLedger(
{ instruments: statement.instruments, events: statement.events, bookEvents },
{
// the rate the books convert a day's cash with: the euros the broker states, else the ECB reference rate
toBooks: (cents, currency, date) => (currency === 'EUR' ? cents : eurCentsAt(cents, currency, date)),
// management's decision whether a security serves the business permanently (§ 247 Abs. 2 HGB)
classificationOf: (instrumentId, depotId, date) => 'current',
decisions, // what a person decided for corporate actions and transfers
},
);
ledger.positions; // IPosition[]: units by depot, book value, cost, accrued interest, units on loan, margin held, lots
ledger.movements; // TLedgerMovement[]: what each event did, for the booking
ledger.issues; // ILedgerIssue[]: what a person must settle, and what it blocks
- Positions pool a security's units across depots (
byDepot) at their moving average: a sale takes the average book value of all units, from the depot that sells (the average method for securities in collective custody, OFD Frankfurt 6.6.2006; § 256 HGB's FIFO and LIFO are for inventories). A position is heldlong(an asset) orshort(an obligation: a written option, a security sold short), as fixed or current assets.bookCentsis its carrying amount after write-downs and write-ups,costCentsits cost (the ceiling of a write-up of an asset, the floor of an obligation). Lots keep which purchases remain, first in first out, for holding periods and the fund ledger. - Movements:
open(cost with its commissions, or the proceeds of a short),close(what the closing brought or cost, the book value released, the result, andresultClass),accrued_interest(Stückzinsen paid, a claim; set off by the coupon or the sale),split,valuation,reclassification,lending,variation_margin(with the margin the contract held before it, which tells the booking the side it lowers and the side it builds),interest_accrual,depot_transfer(units moved to another depot of the business: leaving into transit,departure, or arriving,arrival, each with its share of the position's book value and cost on its day; no result. The position is valued as one across its depots, so a purchase or a valuation while units are in transit moves the arrival's share). - Result classes (
TResultClass):security(a share's sale under § 8b KStG, a fund's under the InvStG, a bond's),termingeschaeft(an option's close-out, expiry or cash settlement, a future's, a CFD's: § 15 Abs. 4 Satz 3 EStG offsets their losses only against such gains, for a GmbH by § 8 Abs. 1 KStG, BFH 21.2.2018 I R 60/16),stillhalter(a written option's premium, earned at its expiry or assignment and kept apart from the shares an assignment delivers: BFH 18.12.2002 I R 17/02, 6.3.2013 I R 18/12),stillhalter_cash_settled(the premium earned and the settlement paid, gross),short_sale,transferred(an option exercised into its underlying) andundecided(what a person decides once, below). - Options: a bought option is an asset at its premium; exercised into its underlying, a call's book value
raises the cost of the shares and a put's lowers the proceeds (IDW RS BFA 6 Rn. 22), tax counts the option's cost
instead (
transferredCostCents, BFH 22.5.2019 XI R 44/17); expired, it is lost. A written option is an obligation at its premium; at expiry or assignment the premium is earned. A trade in the underlying names the option's trade it comes from (linkedTo). - Futures and CFDs: their notional is no value; the daily variation margin is held (paid an asset, received a liability) until the contract closes and is realised then (IDW RS BFA 5); a loss held at a closing date is written off.
- Bonds: the quantity is the nominal amount and the price a percentage of it; accrued interest paid on purchase
is a claim beside the bond, set off by the next coupon or the sale; interest accrued at a closing date
(
interest_accrual) likewise; a redemption closes the bond at what it paid. - Short sales: a sale beyond a depot's holding of shares opens a short position at its proceeds; other securities need the broker's mark.
- Securities lending (IBKR's Stock Yield Enhancement Program): the units on loan per depot, and each change with its share of the book value: title and economic ownership pass to the borrower, so the lender holds a claim to their return at book value (BFH 29.9.2021 I R 40/17). Units on loan cannot be sold.
- The books' own entries (
TBookEvent):valuation(a write-down or write-up as posted; for a future or a CFD the margin written off),reclassification(units moved between fixed and current assets at book value),interest_accrual. - What a person settles (
TLedgerDecision): a corporate action that carries the book value over (allocate: a spin-off, a merger at book value, a rights issue, a foreign stock dividend) or is a sale at value (realize), securities transferred in from elsewhere (cost), securities moved between depots of the business (own_depot), securities transferred out as a disposal (realize), an event of no consequence (ignore). Splits, reverse splits, name changes, domestic stock dividends and redemptions book themselves. OptionstermingeschaeftOf(a warrant or certificate: BFH 8.12.2021 I R 24/19 holds knock-out certificates are no Termingeschäfte; for Optionsscheine no court has said) andwriterCloseClass(a written option bought back) settle the classes that are a policy, once. - Between own depots (
own_depot): no disposal and no result. The transfer out puts its units into transit at book value (IPosition.inTransit, by the transfer out's key: part of the position's quantity and value, in no depot'sbyDepot, so a depot's reconciliation does not count them; their lots keep the depot they left and say so,ILot.transitKey). The transfer in names the transfer out it arrives from ({ kind: 'own_depot', departureKey }) and takes its units with their lots (the days they were bought, what they cost: holding periods and the fund ledger carry on unchanged) and their classification; that decision settles the transfer out as well, and units may arrive in parts. Where the brokers date or list the arrival first, the transfer out applies right before it. The arrival names a transfer out of its own security, or of the one it was renamed from while its units were in transit. A host that replays the events up to a day keeps the transfer out an included arrival names. - Issues (
TLedgerIssueCode): an unknown instrument, a missing rate or classification, a decision required, a quantity beyond the holding, an unsupported event; an event that cannot be applied changes nothing (not its movements, the positions or what an option's exercise hands over, even where it refuses at its last part) and blocks the later events of its instrument (blocked). Each issue says exactly why (refusal,TLedgerRefusal: a code with the values that say it, such as the units asked for and held, the depot, the currency and day of a missing rate, the event that blocks), for a host to word in its own language;messageis the English sentence, for logs.LEDGER_REFUSAL_ISSUE_CODESgives each refusal code its issue code.
The valuation at a closing date
const proposals = depot.valuationProposalsOf(ledger, {
closingDate: '2026-12-31',
instruments,
prices, // Map<instrumentId, IPrice> of the closing date
toBooks,
lastingOf: (positionKey) => statements.get(positionKey), // a person's statement for fixed assets
});
- Current assets at the lower market price (§ 253 Abs. 4 HGB); fixed assets written down for a decline a person states is lasting (Abs. 3 Satz 5), a Finanzanlage also for one that is not if the books choose (Satz 6); a lower value whose reason has gone written up to the cost (Abs. 5); an obligation raised to a higher market value and released no lower than what it brought (a written option's increase is a provision for an expected loss, § 249 Abs. 1 HGB, which tax law does not allow, § 5 Abs. 4a EStG; a short sale's raises the obligation itself); a future's or a CFD's margin loss written off.
taxPresumption: for listed shares and fund units a price more than 5 % below the price at purchase presumes a lasting decline (BMF 2.9.2016, BStBl I S. 995, Rn. 17, 24, 25); for bonds a fall in price alone is none below the redemption value (parCents, Rn. 21 to 23).- Bonds: a premium spread evenly to maturity where the books chose to (
bondAmortisation: 'linear'); a zero-coupon bond's interest accrued by the method a person chose (accretionOf), elsemethod_required. - Units lent out: their share of a write-down (
onLoanCents) concerns the claim to their return, whose write-down § 8b Abs. 3 Satz 3 KStG does not neutralise (BFH I R 40/17). positionValuesOfgives every position's market value and unrealised result against the book value and the cost.
The fund tax ledger (InvStG)
const fundTax = depot.fundTaxLedgerOf(
{ instruments, movements: ledger.movements, events, classifications, proofs, prices },
{ until: '2027-12-31', toBooks, priceOn },
);
const schedule = depot.fundTaxScheduleOf(fundTax, '2027-01-01', '2027-12-31');
For an investor subject to the KStG, with the legal basis on each entry:
- Classification (
IFundClassification, by day, with its evidence): Aktienfonds (more than 50 % in Kapitalbeteiligungen by the Anlagebedingungen), Mischfonds (at least 25 %), Immobilienfonds, Auslands- Immobilienfonds, other (§ 2 Abs. 6, 7, 9 InvStG); a Spezial-Investmentfonds is refused. A proof that a fund actually exceeded a quota throughout a year raises its kind for that year (§ 20 Abs. 4). - Teilfreistellung (
teilfreistellungOf, § 20): 80 % / 40 % / 60 % / 80 % / 0 % for corporate income tax, half for trade tax (§ 20 Abs. 5); 30 % / 15 % for an insurer's or an institution's Handelsbestand (Abs. 1 Satz 4). - Vorabpauschale (§ 18): the first price of the year × 70 % of the Basiszins (Rechnungszins with three decimals,
Basisertrag with four, rounded after the units: BMF 21.05.2019 Rz. 18.4), capped at the year's increase plus the
distributions, less the distributions; one twelfth less for each full month before the month of purchase; on the
units held at the year's end; deemed received on the first working day of the next year (
BASISZINSEN: the BMF's rates and days 2018 to 2026). In the tax balance sheet an aktiver Ausgleichsposten against income (Rz. 18.5); the commercial accounts book nothing. - Distributions (§ 16 Abs. 1 Nr. 1, § 2 Abs. 11): gross, with the taxes withheld by country.
- Sales (§ 19): the tax balance sheet's gain is the price less the tax book value and the units' Vorabpauschalen in full (Abs. 1 Sätze 3, 4; Rz. 19.10 to 19.12), by the average method (Rz. 19.15); a Vorabpauschale not declared is not set off (Rz. 19.9).
- Valuations: a write-down counts only in the share the Teilfreistellung leaves (§ 21 Satz 2), a write-up likewise (Rz. 20.3).
- Changes of rate (§ 22): the units deemed sold at their value and bought again, the gain held in a reserve at the old rate until they are sold.
- The schedule (
fundTaxScheduleOf): per fund and per fund kind for a fiscal year, the Vorabpauschalen, distributions, sale results and valuations, the tax balance sheet's difference from the commercial accounts, the corrections for the corporate income tax and the trade tax, the taxes withheld by country, and the Ausgleichsposten and reserves at the year's end.
Reconciliation and performance
cashBreaksOf: days whose cash opens at another balance than the day before closed with;cashMismatchesOf: days the events do not explain;positionDifferencesOf(withheldQuantitiesOf(ledger, depotId)): instruments the books and the broker hold differently.timeWeightedReturnOf(deposits and withdrawals left out),moneyWeightedReturnOf(the yearly internal rate),annualizedReturnOf,allocationOf(shares of a total by group),incomeOf(dividends, payments in lieu, taxes withheld, interest, fees and trading costs of a period).
Interactive Brokers (@fin.cx/depot/ibkr)
The subpath @fin.cx/depot/ibkr turns an Activity Flex Query's report, as @apiclient.xyz/ibkr reads it, into one
depot statement per account. It does not fetch anything; @apiclient.xyz/ibkr fetches and parses, and stays free of
this package.
import { parseFlexQueryResponse } from '@apiclient.xyz/ibkr';
import { depotStatementsOfFlex } from '@fin.cx/depot/ibkr';
import * as depot from '@fin.cx/depot';
const report = parseFlexQueryResponse(xml); // or what a FlexClient's fetchReport answers
for (const { account, statement, prices, conversionRates, netAssetValue, issues } of depotStatementsOfFlex(report)) {
depot.statementProblemsOf(statement); // [] for a statement that holds together
depot.cashMismatchesOf(statement.events, statement.cash); // days the events do not explain
issues; // IIbkrIssue[]: what could not be mapped, with the section and IBKR's reference
}
- Instruments come from Financial Instrument Information, and from a row's own fields for an instrument it does
not describe (a merger's new shares). An instrument's id is its ISIN where it has one, else
ibkr:conid:<conid>, so one security listed on several exchanges (a conid each) pools.STKis a share, or a fund where IBKR's subcategory isETF(an ETC or ETN that IBKR files as an ETF is no fund under the InvStG and needs the host's correction);FUNDa fund;BONDandBILLbonds; the options, futures, warrants (WAR), structured products (IOPT, certificates) and CFDs as named. A derivative points to its underlying where the statement describes it. - Executions (Trades,
levelOfDetailEXECUTION; closed lots and orders are left out) become trades with their commission, taxes and a bond's accrued interest, or, in the asset categoryCASH(EUR.USD), currency conversions. A cancellation (BUY (Ca.)) names the trade it takes back (cancels, fromorigTransactionID); the notes give an exercise (Ex), an assignment (A) or an expiry (Ep). A trade an exercise or assignment delivers is linked to the option's trade: the one IBKR names as related, else the one option trade of that day and origin on the same underlying. - Cash transactions by IBKR's type: dividends, payments in lieu, withholding taxes (related to the income of the
same corporate action), broker interest, bond interest (
Bond Interest Paidas accrued interest), fees, deposits and withdrawals; a type IBKR adds later isother, its type kept in the description. - Corporate actions, one per IBKR action: the security leaving (else the one the description names first, for a
spin-off or a rights issue), the one arriving, the units, the cash, and a split's ratio from its description
(
SPLIT 2 FOR 1).FS/FIsplit,RSreverse split,SDstock dividend,SOspin-off,TCmerger,ICname change,RIrights issue,BM/TMredemption; every other code isother, for a person to settle. - Transfers of securities in and out, with IBKR's market value.
- Cash: each currency's balance on each day it moved, from the Statement of Funds (the cash ledger) opening at the
Cash Report's starting cash; a balance that does not follow from the lines, or does not end at the Cash Report's
ending cash, is named, and so is a line no event explains (variation margin, which is not mapped yet, shows there),
which is also kept as data (
unexplainedFunds) for the host to hold as cash waiting for a person. - Beside the statement: the open positions at the report day's close (IBKR's summary row where the query gives one, else its lots summed), the closing prices (open and prior period positions), the interest accrued, IBKR's conversion rates and its net asset value.
- Not mapped yet, and named as issues where a statement holds them: securities lent (Stock Yield Enhancement Program) and their fees, cash moved between accounts as a transfer, a commission charged in another currency than its trade, a trade whose commission is credited.
The section, attribute and code names are IBKR's Activity Flex Query reference's and ibflex's; the tests run on a
constructed statement (test/fixtures), not yet on one IBKR sent. Until a real account's first statement is read,
three readings are documented rather than seen: that the Statement of Funds dates a trade by its trade day, that a
bond trade's net cash includes its accrued interest (and no separate Bond Interest Paid row repeats it), and how
IBKR writes a cancellation and a reverse split's new security.
What a tax advisor should confirm
The package computes what the law and the administration state; where they leave a choice or a question, it asks
instead of guessing. Before the first year-end: the fixed or current classification, average cost for securities,
the § 8b account convention for dividends of holdings under 10 %, warrants and written options bought back
(termingeschaeftOf, writerCloseClass), short sales under § 8b, a bond premium's treatment and a zero bond's
accretion, the booking frequency of securities lent and the gross or net cash collateral, the InvStG's open points
(the Ausgleichsposten and deferred taxes, a fiscal year apart from the calendar year, the § 22 treatment of earlier
Vorabpauschalen and of a deemed loss, rounding), and whether the business is a Finanzunternehmen (§ 15 Abs. 4 Satz 4
EStG).
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the license file.
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.