A Telegram bot that acts as a companion to your Ghostfolio instance — self-hosted or cloud. Import broker CSVs, track your portfolio, and manage accounts — all from Telegram.
Works with any Ghostfolio instance. The bot connects via the Ghostfolio REST API — point it at your self-hosted server or at ghostfol.io. Primarily designed for self-hosted setups where you control your data.
- Python 3.12+
- A running Ghostfolio instance
- A Telegram bot token (see below)
uv(recommended) orpip
- Open Telegram and search for @BotFather, then send
/start. - Send
/newbotand follow the prompts — choose a display name (e.g.My Ghostfolio Bot) and a username ending inbot(e.g.mygf_bot). - BotFather replies with your bot token — a string like
123456789:ABCdef.... Copy it. - To find your Telegram user ID, send
/startto @userinfobot — it replies with your numeric ID. Add it toTELEGRAM_ALLOWED_USERSso only you can use the bot.
git clone https://github.com/YOUR_USERNAME/ghostfolio-bot.git
cd ghostfolio-bot
uv synccp .env.sample .envEdit .env:
TELEGRAM_BOT_TOKEN=your-bot-token-from-botfather
TELEGRAM_ALLOWED_USERS=123456789 # your Telegram user ID
GHOSTFOLIO_URL=http://localhost:3333 # your Ghostfolio URL
GHOSTFOLIO_ACCESS_TOKEN=your-token # Settings → Security → Access token
GHOSTFOLIO_ACCOUNT_ID=your-account-uuid # account to import activities into
IMPORT_MODE=direct # "direct" or "json"uv run ghostfolio-botUpload a broker CSV (or XLS) and the bot handles the rest:
- Auto-detects the broker by matching the file header against known signatures
- Resolves symbols via Yahoo Finance (ISIN → ticker), with an on-disk cache
- Deduplicates against existing Ghostfolio activities
- Shows a preview — new vs skipped — before you confirm
| Broker | Type | Notes |
|---|---|---|
| Revolut Stocks | CSV | BUY, SELL, DIVIDEND |
| Revolut Savings | CSV | INTEREST |
| Revolut Crypto | CSV | BUY, SELL |
| DEGIRO | CSV | Dutch & English exports; fee pairing |
| IBKR (Interactive Brokers) | CSV | Trades & Dividends exports |
| MyInvestor | XLS | Spanish broker |
| Delta | CSV | Crypto portfolio tracker |
Want to add a broker? See Adding a parser.
Set IMPORT_MODE in .env:
| Value | Behaviour |
|---|---|
direct |
Activities are posted straight to Ghostfolio via the API |
json |
Generates a JSON file you can import manually via the Ghostfolio UI |
Export your full Ghostfolio data on demand. Backups are compressed with gzip and can optionally be encrypted.
Set BACKUP_STORAGE in .env:
| Value | Behaviour |
|---|---|
telegram |
Bot sends you the backup as a .json.gz file in chat (default) |
local |
Saved to BACKUP_LOCAL_DIR on the server (persisted via Docker volume) |
Automatic backups — set a schedule so backups run without manual action:
BACKUP_SCHEDULE=daily # every day at 2am
BACKUP_SCHEDULE=weekly # every Monday at 2am
BACKUP_SCHEDULE=monthly # 1st of each month at 2am
BACKUP_SCHEDULE=quarterly # 1st of Jan, Apr, Jul, Oct at 2amEncryption — optionally encrypt backups before saving or sending. Files are saved as .json.gz.enc and can only be opened with the key.
- Generate a key:
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" - Add it to
.env:BACKUP_ENCRYPTION_KEY=your-generated-key
Keep the key somewhere safe — without it the backup cannot be decrypted.
Decrypting a backup manually:
from cryptography.fernet import Fernet
import gzip, json
BACKUP_ENCRYPTION_KEY = "your-key-from-.env"
with open("ghostfolio_backup_2026-01-01_000000.json.gz.enc", "rb") as f:
encrypted = f.read()
json_bytes = gzip.decompress(Fernet(BACKUP_ENCRYPTION_KEY.encode()).decrypt(encrypted))
data = json.loads(json_bytes)
print(json.dumps(data, indent=2))Restore Ghostfolio activities from a backup file:
- If
BACKUP_STORAGE=local, the bot lists your most recent local backups - Send the
.json.gz(or.json.gz.enc) file you want to restore - The bot decrypts and parses it, deduplicates against existing activities, and shows a preview: activities in backup, already in Ghostfolio, and new to restore
- Confirm to import — or cancel
Create a new Ghostfolio account directly from Telegram without opening the web UI.
Make sure you have a .env file configured (see Configure), then:
# Start the bot in the background
docker compose up -d
# Build the image and start (use this after code changes)
docker compose up -d --build
# View logs
docker compose logs -f
# Stop the bot
docker compose downThe container restarts automatically on failure or system reboot (restart: unless-stopped). Ghostfolio itself is external — just point GHOSTFOLIO_URL at your instance.
If your Ghostfolio runs in Docker on the same machine, set
GHOSTFOLIO_URL=http://host.docker.internal:3333so the bot can reach it.
The compose file mounts a ./backups directory from the host into the container so local backups survive container restarts and image updates:
volumes:
- ./backups:/app/backups# Install with dev dependencies
uv sync --extra dev
# Run tests
uv run pytest
# Lint (required — auto-fixable with --fix)
uv run ruff check .
# Type check (optional but appreciated)
uv run mypy bot- Create
bot/parsers/mybroker.py - Inherit from
BrokerParserand decorate with@register_parser - Set
NAME,HEADER_SIGNATURE,DELIMITER, and implementparse() - Add an import in
bot/parsers/__init__.py - Add tests in
tests/test_mybroker.py
from bot.parsers.base import BrokerParser, register_parser
@register_parser
class MyBrokerParser(BrokerParser):
NAME = "MyBroker"
HEADER_SIGNATURE = "Date,Type,Symbol,Quantity,Price,Fee,Currency"
def parse(self, csv_text: str) -> list[dict]:
...See bot/parsers/revolut/savings.py for a minimal working example.
Contributions are welcome! Please open an issue before submitting a pull request for significant changes.
- Fork the repo and create a feature branch
- Make sure
ruff check .passes (required) — runruff check . --fixto auto-fix most issues - Add or update tests for your change
- Type annotations are appreciated but not required to get a PR merged
- Open a PR with a clear description
AGPL-3.0 — any modified version deployed as a network service must also be open source.
Inspired by export-to-ghostfolio — the TypeScript reference implementation for broker CSV → Ghostfolio import.
This project is provided as-is, without any warranty of any kind. Use it at your own risk. The authors are not responsible for any data loss, corruption, or unintended changes to your Ghostfolio instance.





