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).
- Extract — pulls depository accounts +
ledger/availablebalances from Teller, and accounts + positions from SnapTrade. - 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.
- Load — updates cash balances in place and imports adjusting
BUY/SELLactivities so Ghostfolio's holdings converge on reality. Ghostfolio has no "set position" API, so delta activities (taggedSynced 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.
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 -dThe 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.
| 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 |
- Ghostfolio: log in → My Ghostfolio → Settings → 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, seedocker-compose.yml). - SnapTrade: sign up at snaptrade.com, grab your
clientIdandconsumerKey, and connect your brokerage through their connection portal. Personal API keys need nothing else.
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,USDColumns: 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 Company →
name/symbol and Net Value → value, 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:roNote: 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).
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
pytestThe test suite mocks all HTTP traffic (via responses) — no credentials or
network needed.
- Cash: the Teller
ledgerbalance (total funds) is written to the Ghostfolio account balance;availableis 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
YAHOOdata source. Override withSNAPTRADE_DATA_SOURCEif needed. - Credit cards and loans from Teller are currently skipped (Ghostfolio models
accounts as assets); only
depositoryaccounts sync.