Skip to content

Repository files navigation

ghostfolio-data-bridge

A sidecar for Ghostfolio that keeps your dashboard in sync with real accounts — automatically.

  • Cash balances from Teller (checking/savings at 5,000+ US banks)
  • Brokerage holdings from SnapTrade (tickers + share counts)
  • Private/illiquid holdings from a CSV you maintain — AngelList investments, SAFEs, fund positions, anything without an API

It runs as a small Docker container next to your Ghostfolio instance and syncs on a schedule (default: every 24 hours).

How it works

  1. Extract — pulls depository accounts + ledger/available balances from Teller, and accounts + positions from SnapTrade.
  2. Transform — maps each external account to a Ghostfolio account (tagged via the account comment, so renaming in the UI is safe), and diffs broker share counts against what Ghostfolio currently holds.
  3. Load — updates cash balances in place and imports adjusting BUY/SELL activities so Ghostfolio's holdings converge on reality. Ghostfolio has no "set position" API, so delta activities (tagged Synced by ghostfolio-data-bridge) are the idempotent way in — re-running a sync with no changes imports nothing.

All providers are optional: configure any combination of Teller, SnapTrade, and the manual CSV. Failures (provider outage, Ghostfolio down, 4xx/5xx) are logged and the container just waits for the next scheduled run — it never crashes.

Quick start

git clone https://github.com/nagaral/ghostfolio-data-bridge.git
cd ghostfolio-data-bridge
cp .env.example .env   # fill in your tokens
docker compose up -d

The bundled docker-compose.yml includes a full Ghostfolio stack (Ghostfolio + Postgres + Redis). Already running Ghostfolio? Delete those three services, keep data-bridge, and point GHOST_URL at your instance.

Recommended first run: set DRY_RUN=true and check docker compose logs -f data-bridge to see exactly what would be written.

Configuration

Variable Required Default Description
GHOST_URL yes Ghostfolio base URL, e.g. http://ghostfolio:3333
GHOST_TOKEN yes The Security Token from Ghostfolio user settings
TELLER_TOKEN one of Teller access token (from Teller Connect)
TELLER_CERT_PATH / TELLER_KEY_PATH prod only mTLS client cert issued by Teller; required for non-sandbox tokens
SNAPTRADE_CLIENT_ID one of SnapTrade client ID
SNAPTRADE_CONSUMER_KEY with above SnapTrade consumer key (used to sign requests)
SNAPTRADE_USER_ID / SNAPTRADE_USER_SECRET no Only for commercial (multi-user) SnapTrade keys; omit for personal keys
MANUAL_CSV_PATH one of Path (inside the container) to a manual holdings CSV
SYNC_INTERVAL_HOURS no 24 Hours between syncs (fractions allowed)
GHOST_CURRENCY no USD Currency for created accounts/activities
SNAPTRADE_DATA_SOURCE no YAHOO Ghostfolio data source for imported symbols
DRY_RUN no false Log intended writes without touching Ghostfolio
RUN_ON_START no true Sync immediately on container start
LOG_LEVEL no INFO Python logging level

Getting the tokens

  • Ghostfolio: log in → My GhostfolioSettings → copy the Security Token (the same one you use to log in — not a JWT).
  • Teller: create an application at teller.io, run Teller Connect once to link your bank, and save the resulting access_token. Sandbox tokens (test_token_...) work without certificates; production tokens require the cert/key pair from your Teller dashboard (mount into the container, see docker-compose.yml).
  • SnapTrade: sign up at snaptrade.com, grab your clientId and consumerKey, and connect your brokerage through their connection portal. Personal API keys need nothing else.

Manual CSV: AngelList & other private holdings

AngelList (and most private-market platforms) has no API for LP holdings, so the bridge syncs them from a CSV you keep up to date. Positions land in Ghostfolio as MANUAL data-source assets: the bridge creates the symbol, writes today's mark as the market price, and imports a one-time BUY per position. On later runs only the marks refresh — no duplicate activities.

account,symbol,name,quantity,value,currency
AngelList,ANGEL-ACME,Acme Robotics (Seed),1,25000,USD
AngelList,ANGEL-ZETA,Zeta AI (Series A),1,10000,USD

Columns: account and symbol are required, plus value (current mark) or price (per-unit). Optional: name, quantity (default 1), currency. Use any stable symbol scheme you like (ANGEL-ACME); Ghostfolio treats it as an opaque manual asset. For AngelList, map the portfolio export's Companyname/symbol and Net Valuevalue, leaving quantity at 1 — update the values when AngelList reports new marks.

Then set (see examples/manual-holdings.csv and docker-compose.yml):

MANUAL_CSV_PATH: /data/holdings.csv
volumes:
  - ./data:/data:ro

Note: creating MANUAL symbols uses Ghostfolio admin endpoints, so GHOST_TOKEN must belong to an admin user (the first user of a self-hosted instance is one).

Development

python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
pytest

The test suite mocks all HTTP traffic (via responses) — no credentials or network needed.

Design notes & limitations

  • Cash: the Teller ledger balance (total funds) is written to the Ghostfolio account balance; available is logged for visibility.
  • Holdings: synced as point-in-time adjustments, not full trade history. Your Ghostfolio performance numbers will reflect position values, not the true cost basis of trades made at the broker. For full historical fidelity, import your broker's trade CSV once, then let the bridge keep it current.
  • Symbols: SnapTrade tickers are assumed to resolve in Ghostfolio's YAHOO data source. Override with SNAPTRADE_DATA_SOURCE if needed.
  • Credit cards and loans from Teller are currently skipped (Ghostfolio models accounts as assets); only depository accounts sync.

License

MIT

About

Sidecar for Ghostfolio: auto-sync bank balances (Teller) and brokerage holdings (SnapTrade)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages