13 KiB
@fin.cx/opendata
@fin.cx/opendata is a TypeScript toolbox for serious data plumbing: market data, SEC fundamentals, German company registry workflows, and locally synced law corpora. It is built for programmers who want scriptable APIs, local persistence, and real workflows instead of a thin wrapper around one remote endpoint.
It currently gives you four practical lanes in one package:
StockPriceServicefor stock and crypto prices with caching, retries, failover, historical ranges, and intraday supportFundamentalsServiceandStockDataServicefor SEC fundamentals and combined price + fundamentals payloadsOpenDatafor German company ingestion plus browser-driven Handelsregister lookup and document downloadLawServicefor syncing and searching German, EU, and US law texts in a local embedded database
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.
Installation
pnpm add @fin.cx/opendata
What You Get
Market Data
StockPriceServiceFetches current, batch, historical, and intraday price data.FundamentalsServiceFetches SEC EDGAR fundamentals for US-listed companies.StockDataServiceCombines price + fundamentals and enriches fundamentals with metrics likemarketCap,priceToEarnings, andpriceToBook.MarketstackProviderAuthenticated stock market provider with current, batch, historical, and intraday support.CoinGeckoProviderCrypto market provider behind the same stock-price interface.SecEdgarProviderFundamentals provider backed by SEC EDGAR company facts.
German Company Data
OpenDataStarts a local embedded database, imports the public German company JSONL dump, and exposes ahandelsregisterhelper for company lookup and file download.
Laws
LawServiceSyncs laws into a local embedded database and lets you search them later.LawRecordThe stored law document model returned by sync and search operations.
Before You Start
- No stock or fundamentals providers are pre-registered. You must register them before calling the services.
MarketstackProviderrequires an API key.SecEdgarProviderrequires a validUser-Agentstring in the formatCompany Name email@example.com.CoinGeckoProviderworks without an API key, but a key gives you better rate limits.OpenDatarequiresnogitDir,downloadDir, andgermanBusinessDataDir.OpenData.start()is required before usingOpenData.handelsregisterorbuildInitialDb().LawServicestores data locally. It auto-starts on demand, but explicitstart()andstop()keep lifecycle handling cleaner.- EU law syncing and Handelsregister access use a headless browser under the hood.
StockDataServicedefaults to a 24 hour price cache. If you want fresher quote caching, passcache.priceTTLexplicitly or useStockPriceServicedirectly.
Quick Start
Combined Stock Data
Use StockDataService when you want one call that returns both the latest price and SEC fundamentals.
import {
MarketstackProvider,
SecEdgarProvider,
StockDataService,
} from '@fin.cx/opendata';
const stocks = new StockDataService({
cache: {
priceTTL: 60_000,
},
});
stocks.registerPriceProvider(
new MarketstackProvider(process.env.MARKETSTACK_API_KEY!)
);
stocks.registerFundamentalsProvider(
new SecEdgarProvider({
userAgent: 'Acme Inc. dev@acme.dev',
})
);
const apple = await stocks.getStockData('AAPL');
console.log({
ticker: apple.ticker,
price: apple.price.price,
provider: apple.price.provider,
companyName: apple.fundamentals?.companyName,
marketCap: apple.fundamentals?.marketCap,
priceToEarnings: apple.fundamentals?.priceToEarnings,
fetchedAt: apple.fetchedAt,
});
You can also batch this:
const batch = await stocks.getBatchStockData(['AAPL', 'MSFT', 'NVDA']);
Price-Only Stocks And Intraday Data
Use StockPriceService if you want smarter per-request caching and direct access to current, historical, and intraday request types.
import { MarketstackProvider, StockPriceService } from '@fin.cx/opendata';
const prices = new StockPriceService();
prices.register(new MarketstackProvider(process.env.MARKETSTACK_API_KEY!));
const current = await prices.getPrice({ ticker: 'AAPL' });
const historical = await prices.getData({
type: 'historical',
ticker: 'AAPL',
from: new Date('2025-01-01'),
to: new Date('2025-01-31'),
sort: 'DESC',
});
const intraday = await prices.getData({
type: 'intraday',
ticker: 'AAPL',
interval: '1hour',
limit: 24,
});
console.log({
live: current.price,
candles: intraday.length,
historicalRows: historical.length,
});
Supported stock request shapes:
{ type: 'current', ticker, exchange? }{ type: 'batch', tickers, exchange? }{ type: 'historical', ticker, from, to, exchange?, sort?, limit?, offset? }{ type: 'intraday', ticker, interval, exchange?, limit?, date? }
StockPriceService also exposes operational helpers like checkProvidersHealth(), getProviderStats(), clearCache(), and getCacheStats().
Crypto Prices With The Same Interface
CoinGeckoProvider plugs into StockPriceService, so crypto lookups use the same request model as stock lookups.
import { CoinGeckoProvider, StockPriceService } from '@fin.cx/opendata';
const prices = new StockPriceService({ ttl: 30_000 });
prices.register(new CoinGeckoProvider(process.env.COINGECKO_API_KEY));
const btc = await prices.getPrice({ ticker: 'BTC' });
const top = await prices.getPrices({ tickers: ['BTC', 'ETH', 'SOL'] });
console.log(btc.price, top.map((item) => item.ticker));
You can also pass CoinGecko ids like bitcoin or ethereum instead of ticker symbols.
Fundamentals Only
Use FundamentalsService when you only care about SEC filing data.
import { FundamentalsService, SecEdgarProvider } from '@fin.cx/opendata';
const fundamentals = new FundamentalsService();
fundamentals.register(
new SecEdgarProvider({
userAgent: 'Acme Inc. dev@acme.dev',
})
);
const apple = await fundamentals.getFundamentals('AAPL');
console.log({
companyName: apple.companyName,
revenue: apple.revenue,
netIncome: apple.netIncome,
earningsPerShareDiluted: apple.earningsPerShareDiluted,
filingDate: apple.filingDate,
});
German Company Data And Handelsregister Automation
OpenData is the local workflow layer for German company data. It creates its directories, boots an embedded SmartDB-backed database, and starts a browser automation session for Handelsregister access.
import { OpenData } from '@fin.cx/opendata';
import * as path from 'node:path';
const nogitDir = path.join(process.cwd(), '.nogit');
const openData = new OpenData({
nogitDir,
downloadDir: path.join(nogitDir, 'downloads'),
germanBusinessDataDir: path.join(nogitDir, 'germanbusinessdata'),
});
await openData.start();
const matches = await openData.handelsregister.searchCompany('Siemens AG', 20);
const company = await openData.handelsregister.getSpecificCompany({
court: 'Munich',
type: 'HRB',
number: '6684',
});
console.log({
searchHits: matches.length,
downloadedFiles: company.files.map((file) => file.path),
});
await openData.stop();
Useful OpenData workflows today:
await openData.start()await openData.buildInitialDb()to ingest the public German company JSONL datasetawait openData.handelsregister.searchCompany(name, limit)await openData.handelsregister.getSpecificCompany({ court, type, number })await openData.handelsregister.getSpecificCompanyByName(name)
The download workflow currently fetches the Handelsregister files into a temporary folder and returns the files to you as SmartFile objects.
Law Syncing And Search
LawService stores synced laws in a local embedded database and supports three jurisdictions:
- Germany via
gesetze-im-internet - EU via
EUR-Lex - US via GovInfo and Cornell LII
import { LawService } from '@fin.cx/opendata';
const laws = new LawService({
dbFolderPath: '.nogit/law-smartdb',
dbName: 'laws',
govInfoApiKey: process.env.GOVINFO_API_KEY,
});
await laws.start();
const germanLaw = await laws.syncLaw({
jurisdiction: 'de',
identifier: 'aeg',
});
const euLaw = await laws.syncLaw({
jurisdiction: 'eu',
identifier: '32024R1689',
language: 'EN',
});
const usCode = await laws.syncLaw({
jurisdiction: 'us',
identifier: '8 U.S.C. § 1226',
});
const results = await laws.searchLaws({
jurisdiction: 'eu',
query: 'Artificial Intelligence Act',
limit: 5,
});
console.log({
germany: germanLaw.title,
eu: euLaw.title,
us: usCode.title,
hits: results.length,
});
await laws.stop();
LawService also supports bulk sync:
await laws.syncLaws({
jurisdiction: 'us',
usCollection: 'PLAW',
limit: 25,
since: new Date('2025-01-01'),
});
Useful request patterns:
- Germany:
identifier: 'aeg' - EU:
identifier: '32024R1689',language: 'EN' - US public law package:
identifier: 'PLAW-119publ1',usCollection: 'PLAW' - US Code citation:
identifier: '8 U.S.C. § 1226'
API At A Glance
StockPriceService
register(provider, config?)unregister(providerName)getPrice({ ticker })getPrices({ tickers })getData(request)checkProvidersHealth()getProviderStats()clearCache()getCacheStats()
FundamentalsService
register(provider, config?)getFundamentals(ticker)getBatchFundamentals(tickers)getData(request)enrichWithPrice(fundamentals, price)checkProvidersHealth()getProviderStats()clearCache()
StockDataService
registerPriceProvider(provider, config?)registerFundamentalsProvider(provider, config?)getPrice(ticker)getPrices(tickers)getFundamentals(ticker)getBatchFundamentals(tickers)getStockData(ticker | request)getBatchStockData(tickers | request)checkProvidersHealth()getProviderStats()clearCache()
OpenData
start()stop()buildInitialDb()handelsregister.searchCompany(name, limit?)handelsregister.getSpecificCompany({ court, type, number })handelsregister.getSpecificCompanyByName(name)
LawService
start()stop()getLaw(request)syncLaw(request)syncLaws(request)searchLaws({ query, jurisdiction?, limit? })
Behavior And Caveats
- Historical market data is cached aggressively because it is treated as immutable.
- Intraday data uses interval-aware caching and tries incremental refreshes for recent data.
StockDataServiceis convenience-first.StockPriceServiceis the better low-level choice if you care about request-shape-specific cache behavior.OpenDatais a real workflow layer, not just types. It writes local data, launches a browser, and manages downloads.- Handelsregister and EUR-Lex integrations depend on current site structure. They are practical automation workflows, not guaranteed stable official APIs.
- The most useful public
OpenDatafeatures today are startup, JSONL import, and Handelsregister operations. Some extra validation/search helper methods exist in source but are still stubs. LawServicepersists synced laws locally, so repeated searches can stay offline once the data has been synced.- There is no hosted backend in this package. Everything is meant to run from your own TypeScript/Node process.
Testing
pnpm test
Some tests hit live remote services, so they may depend on network access, optional API credentials, or browser automation support.
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.