A robust, standalone TypeScript library for parsing transaction CSV exports from various stock brokers. This library normalizes transaction data into a common format, handling currency conversions, transaction types, and ISIN extraction.
- Multi-Broker Support: Automatically detects and parses CSVs from supported brokers.
- Normalization: Unifies transaction types (BUY, SELL, DIVIDEND, etc.) across brokers.
- Currency Handling: Extracts account currency, native currency, and exchange rates.
- ISIN Extraction: Reliably finds ISIN codes for accurate instrument identification.
- Export Support: Convert parsed transactions into formats like Yahoo Finance CSV.
- CLI Support: Command-line interface for bulk processing and exporting without writing code.
- Data Enrichment: Helper utilities to resolve Tickers (e.g. from ISIN) before export.
- Type Safe: Written in TypeScript with full type definitions.
- Avanza (Sweden)
- Nordnet (Sweden/Nordics)
- (More can be added by implementing the
BrokerParserinterface)
To install from GitHub Packages, you need to configure your .npmrc file:
echo "@logkat:registry=https://npm.pkg.github.com" >> .npmrcThen install the package:
npm install @logkat/broker-parser
# or
pnpm add @logkat/broker-parserThe library provides a powerful CLI for bulk processing and ticker resolution.
# Basic export (defaults to Yahoo resolution)
broker-parser export input.csv -o output.csv
# Specific resolvers (stacked in order)
broker-parser export input.csv --ticker-file custom.json --yahoo
# Control over resolution strategies
broker-parser export input.csv --yahoo-isin --yahoo-name| Flag | Description |
|---|---|
--yahoo |
Use both ISIN and Name search (Default: true) |
--yahoo-isin |
Trigger only ISIN-based search |
--yahoo-name |
Trigger only Name-based search (fuzzy matching) |
--ticker-file |
Use a local JSON/CSV mapping file (priority) |
--no-yahoo |
Disable all automatic lookups |
--cache <path> |
Path to resolution cache (Default: .ticker-cache.json) |
import { parseTransaction } from '@logkat/broker-parser';
const transaction = parseTransaction(row);The library and CLI support "stacked" resolvers. You can prioritize local data and then fall back to various cloud providers.
When using --ticker-file or FileTickerResolver, you can use JSON or CSV files.
JSON Format (Object or Array):
// Simple object (Key can be ISIN or Security Name)
{
"US0378331005": "AAPL",
"Meta Platforms A": "META"
}
// Or an array of objects
[
{ "isin": "US0378331005", "ticker": "AAPL" },
{ "name": "Microsoft", "ticker": "MSFT" }
]CSV Format:
isin,name,ticker
US0378331005,,AAPL
,Microsoft,MSFT
import {
enrichTransactions,
YahooISINResolver,
YahooNameResolver,
FileTickerResolver,
LocalFileTickerCache,
} from '@logkat/broker-parser';
const enriched = await enrichTransactions(transactions, {
resolvers: [
new FileTickerResolver('./manual-mapping.json'),
new YahooISINResolver(),
new YahooNameResolver(),
],
cache: new LocalFileTickerCache('./cache.json'),
});The library automatically detects the format based on unique headers (e.g., "Typ av transaktion" for Avanza vs "Transaktionstyp" for Nordnet). You can also force a format:
// Force Avanza parser
const txn = parseTransaction(row, 'Avanza');If you are parsing a large CSV with multiple accounts, you can extract unique account identifiers:
import { identifyAccounts } from '@logkat/broker-parser';
const accounts = identifyAccounts(allRows);
// Returns: [{ id: '12345', name: 'My ISK', count: 50 }, ...]You can export normalized transactions to various formats (e.g., for importing into other tools).
import { YahooFinanceExporter } from '@logkat/broker-parser';
// Convert transactions to Yahoo Finance CSV
const result = YahooFinanceExporter.export(parsedTransactions);
console.log(result.content); // CSV stringBrokers outputs (Avanza/Nordnet) often lack the actual Ticker Symbol required by Yahoo Finance (they provide ISIN or Name instead).
To fix this, you can use enrichTransactions with a resolver.
import {
enrichTransactions,
YahooFinanceExporter,
} from '@logkat/broker-parser';
// 1. Define or use a built-in resolver
const myResolver = {
name: 'My Custom Resolver',
resolve: async (isin: string, name: string) => {
if (isin === 'US0378331005') return { ticker: 'AAPL' };
return { ticker: null };
},
};
// 2. Enrich
const enriched = await enrichTransactions(parsedTransactions, {
resolvers: [myResolver],
});
// 3. Export
const csv = YahooFinanceExporter.export(enriched);The library provides a TickerCache interface. You can implement your own (e.g., using Redis or a Database) to persist resolutions.
interface TickerCache {
get(key: string): Promise<TickerResolution | undefined>;
set(key: string, value: TickerResolution): Promise<void>;
}Parses a raw CSV row into a normalized transaction object. Returns null if the row cannot be parsed.
Scans a dataset to find all unique account IDs present in the file.
date: Date objecttype: 'BUY' | 'SELL' | 'DIVIDEND' | 'DEPOSIT' | 'WITHDRAW' | 'INTEREST' | 'TAX' | 'OTHER'name: string (Security name, e.g. "Apple Inc")ticker: string (Ticker symbol, e.g. "AAPL")quantity: numberprice: numbertotal: numbercurrency: string (Account Currency)nativeCurrency: string (Asset Currency)isin: string (optional)- ...and more.
If you are moving from an internal implementation to this library:
-
Replace Imports: Change your imports from your local file:
- import { parseTransaction } from '@/lib/parser'; + import { parseTransaction } from '@logkat/broker-parser';
-
Check Config: Ensure your project handles the
ParsedTransactionreturn type correctly, as some deprecated fields might have been cleaned up. -
Dependencies: This library has zero runtime dependencies (except standard JS/TS features).
This project follows strict naming conventions for financial terminology. See CODING_GUIDELINES.md for details.
Key terminology:
ticker: Stock ticker symbol (e.g., "AAPL", "META")name: Company/security name (e.g., "Apple Inc")symbol: DEPRECATED - do not use; treat astickerif encountered in external APIs
We welcome contributions! To add support for a new broker:
-
Create a Parser File: Create a new file (e.g.,
src/parsers/mybroker.ts) implementing theBrokerParserinterface.import { BrokerParser } from './types'; import { parseNumber, normalizeType } from './utils'; export const MyBrokerParser: BrokerParser = { name: 'MyBroker', canParse: (row) => !!(row['UniqueHeader'] && row['AnotherHeader']), parse: (row) => { // ... parsing logic mapping to ParsedTransaction return { date: new Date(row['Date']), type: normalizeType(row['Type']), // ... }; }, };
-
Register the Parser: Import and add your parser to
src/index.tsin thegetParsers()function and update theparseTransactionlogic if needed. -
Add Tests: Add a test case in
tests/parser_brokers.test.tswith a sample transaction row to verify it parses correctly.
-
Install Dependencies:
pnpm install
-
Run Quality Checks:
# Format code pnpm format # Check formatting pnpm format:check # Type check pnpm type-check # Lint pnpm lint
-
Run Tests:
# Run tests in watch mode pnpm test # Run tests with coverage pnpm test:coverage
-
Build:
pnpm build
-
Run All Checks (same as CI):
pnpm format:check && pnpm type-check && pnpm lint && pnpm build && pnpm test:coverage
MIT