Open Source Wealth Management Software
Ghostfol.io | Live Demo | Ghostfolio Premium | FAQ | Blog | LinkedIn | Slack | X
Ghostfolio is an open source wealth management software built with web technology. The application empowers busy people to keep track of stocks, ETFs or cryptocurrencies and make solid, data-driven investment decisions. The software is designed for personal use in continuous operation.
Our official Ghostfolio Premium cloud offering is the easiest way to get started. Due to the time it saves, this will be the best option for most people. Revenue is used to cover operational costs for the hosting infrastructure and professional data providers, and to fund ongoing development.
If you prefer to run Ghostfolio on your own infrastructure, please find further instructions in the Self-hosting section.
Ghostfolio is for you if you are...
- πΌ trading stocks, ETFs or cryptocurrencies on multiple platforms
- π¦ pursuing a buy & hold strategy
- π― interested in getting insights of your portfolio composition
- π» valuing privacy and data ownership
- π§ into minimalism
- π§Ί caring about diversifying your financial resources
- π interested in financial independence
- π saying no to spreadsheets
- π still reading this list
- β Create, update and delete transactions
- β Multi account management
- β
Portfolio performance: Return on Average Investment (ROAI) for
Today,WTD,MTD,YTD,1Y,5Y,Max - β Various charts
- β Static analysis to identify potential risks in your portfolio
- β Import and export transactions
- β Dark Mode
- β Zen Mode
- β Progressive Web App (PWA) with a mobile-first design
Ghostfolio is a modern web application written in TypeScript and organized as an Nx workspace.
The backend is based on NestJS using PostgreSQL as a database together with Prisma and Redis for caching.
The frontend is built with Angular and uses Angular Material with utility classes from Bootstrap.
We provide official container images hosted on Docker Hub for linux/amd64, linux/arm/v7 and linux/arm64.
| Name | Type | Default Value | Description |
|---|---|---|---|
ACCESS_TOKEN_SALT |
string |
A random string used as salt for access tokens | |
API_KEY_COINGECKO_DEMO |
string (optional) |
Β | The CoinGecko Demo API key |
API_KEY_COINGECKO_PRO |
string (optional) |
The CoinGecko Pro API key | |
DATABASE_URL |
string |
The database connection URL, e.g. postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?sslmode=prefer |
|
ENABLE_FEATURE_AUTH_TOKEN |
boolean (optional) |
true |
Enables authentication via security token |
HOST |
string (optional) |
0.0.0.0 |
The host where the Ghostfolio application will run on |
JWT_SECRET_KEY |
string |
A random string used for JSON Web Tokens (JWT) | |
LOG_LEVELS |
string[] (optional) |
The logging levels for the Ghostfolio application, e.g. ["debug","error","log","warn"] |
|
PORT |
number (optional) |
3333 |
The port where the Ghostfolio application will run on |
POSTGRES_DB |
string |
The name of the PostgreSQL database | |
POSTGRES_PASSWORD |
string |
The password of the PostgreSQL database | |
POSTGRES_USER |
string |
The user of the PostgreSQL database | |
REDIS_DB |
number (optional) |
0 |
The database index of Redis |
REDIS_HOST |
string |
The host where Redis is running | |
REDIS_PASSWORD |
string |
The password of Redis | |
REDIS_PORT |
number |
The port where Redis is running | |
REQUEST_TIMEOUT |
number (optional) |
2000 |
The timeout of network requests to data providers in milliseconds |
ROOT_URL |
string (optional) |
http://0.0.0.0:3333 |
The root URL of the Ghostfolio application, used for generating callback URLs and external links. |
| Name | Type | Default Value | Description |
|---|---|---|---|
ENABLE_FEATURE_AUTH_OIDC |
boolean (optional) |
false |
Enables authentication via OpenID Connect |
OIDC_AUTHORIZATION_URL |
string (optional) |
Manual override for the OIDC authorization endpoint (falls back to the discovery from the issuer) | |
OIDC_CALLBACK_URL |
string (optional) |
${ROOT_URL}/api/auth/oidc/callback |
The OIDC callback URL |
OIDC_CLIENT_ID |
string |
The OIDC client ID | |
OIDC_CLIENT_SECRET |
string |
The OIDC client secret | |
OIDC_ISSUER |
string |
The OIDC issuer URL, used to discover the OIDC configuration via /.well-known/openid-configuration |
|
OIDC_SCOPE |
string[] (optional) |
["openid"] |
The OIDC scope to request, e.g. ["email","openid","profile"] |
OIDC_TOKEN_URL |
string (optional) |
Manual override for the OIDC token endpoint (falls back to the discovery from the issuer) | |
OIDC_USER_INFO_URL |
string (optional) |
Manual override for the OIDC user info endpoint (falls back to the discovery from the issuer) |
- Basic knowledge of Docker
- Installation of Docker
- Create a local copy of this Git repository (clone)
- Copy the file
.env.exampleto.envand populate it with your data (cp .env.example .env)
Run the following command to start the Docker images from Docker Hub:
docker compose -f docker/docker-compose.yml up -dRun the following commands to build and start the Docker images:
docker compose -f docker/docker-compose.build.yml build
docker compose -f docker/docker-compose.build.yml up -d- Open http://localhost:3333 in your browser
- Create a new user via Get Started (this first user will get the role
ADMIN)
-
Update the Ghostfolio Docker image
- Increase the version of the
ghostfolio/ghostfolioDocker image indocker/docker-compose.yml - Run the following command if
ghostfolio:latestis set:docker compose -f docker/docker-compose.yml pull
- Increase the version of the
-
Run the following command to start the new Docker image:
docker compose -f docker/docker-compose.yml up -d
The container will automatically apply any required database schema migrations during startup.
Ghostfolio is available for various home server systems, including CasaOS, Home Assistant, Runtipi, TrueCharts, Umbrel, and Unraid.
For detailed information on the environment setup and development process, please refer to DEVELOPMENT.md.
Set the header for each request as follows:
"Authorization": "Bearer eyJh..."
You can get the Bearer Token via POST http://localhost:3333/api/v1/auth/anonymous (Body: { "accessToken": "<INSERT_SECURITY_TOKEN_OF_ACCOUNT>" })
Deprecated: GET http://localhost:3333/api/v1/auth/anonymous/<INSERT_SECURITY_TOKEN_OF_ACCOUNT> or curl -s http://localhost:3333/api/v1/auth/anonymous/<INSERT_SECURITY_TOKEN_OF_ACCOUNT>.
GET http://localhost:3333/api/v1/health
Info: No Bearer Token is required for health check
200 OK
{
"status": "OK"
}
Bearer Token for authorization
POST http://localhost:3333/api/v1/import
{
"activities": [
{
"currency": "USD",
"dataSource": "YAHOO",
"date": "2021-09-15T00:00:00.000Z",
"fee": 19,
"quantity": 5,
"symbol": "MSFT",
"type": "BUY",
"unitPrice": 298.58
}
]
}
| Field | Type | Description |
|---|---|---|
accountId |
string (optional) |
Id of the account |
comment |
string (optional) |
Comment of the activity |
currency |
string |
CHF | EUR | USD etc. |
dataSource |
string |
COINGECKO | GHOSTFOLIO 1 | MANUAL | YAHOO |
date |
string |
Date in the format ISO-8601 |
fee |
number |
Fee of the activity |
quantity |
number |
Quantity of the activity |
symbol |
string |
Symbol of the activity (suitable for dataSource) |
type |
string |
BUY | DIVIDEND | FEE | INTEREST | LIABILITY | SELL |
unitPrice |
number |
Price per unit of the activity |
201 Created
400 Bad Request
{
"error": "Bad Request",
"message": [
"activities.1 is a duplicate activity"
]
}
Grant access of type Public in the Access tab of My Ghostfolio.
GET http://localhost:3333/api/v1/public/<INSERT_ACCESS_ID>/portfolio
Info: No Bearer Token is required for authorization
{
"performance": {
"1d": {
"relativeChange": 0 // normalized from -1 to 1
};
"ytd": {
"relativeChange": 0 // normalized from -1 to 1
},
"max": {
"relativeChange": 0 // normalized from -1 to 1
}
}
}
Discover a variety of community projects for Ghostfolio: https://github.com/topics/ghostfolio
Are you building your own project? Add the ghostfolio topic to your GitHub repository to get listed as well. Learn more β
Ghostfolio is 100% free and open source. We encourage and support an active and healthy community that accepts contributions from the public - including you.
Not sure what to work on? We have some ideas, even for newcomers. Please join the Ghostfolio Slack channel or post to @ghostfolio_ on X. We would love to hear from you.
If you like to support this project, become a Sponsor, get Ghostfolio Premium or Buy me a coffee.
Built by @Theesamkos as part of the Gauntlet AI AgentForge program.
This project includes a production-grade implementation of a Unified Real Estate Ledger for Ghostfolio, submitted as a bounty contribution to the Gauntlet AI AgentForge program.
For graders and reviewers: Please see BOUNTY.md for the full submission writeup, including the problem statement, feature description, technical architecture, and impact.
- Database: Two new Prisma models (
RealEstateAsset,RentalIncomeRecord) with cascading deletes and indexed foreign keys - Backend: A complete
RealEstateModule(NestJS) with 8 REST endpoints under/api/v1/real-estate, powered by the RentCast AVM API - AI Agent: 6 new async tool functions in Python (
get_real_estate_summary,get_real_estate_holdings,add_property,update_property_valuation,get_rental_income_history,add_rental_income)
The agent can now answer: "What percentage of my net worth is real estate vs. equities?" β for the first time, with a real answer.
β Read the full bounty submission: BOUNTY.md
β Bounty Repository: github.com/Theesamkos/ghostfolio-bounty
AI-powered financial assistant built on the Ghostfolio open-source wealth management platform. It exposes a streaming chat API that reasons about your portfolio using a dual-LLM pipeline and a five-layer verification system.
Full architecture reference β
docs/AGENT_ARCHITECTURE.md
| Service | URL |
|---|---|
| Backend API (Railway) | https://ghostfolio-production-c6dd.up.railway.app |
| Frontend (Vercel) | https://ghostfolio-chi-sage.vercel.app |
| API Docs (Swagger) | https://ghostfolio-production-c6dd.up.railway.app/docs |
| Health Check | https://ghostfolio-production-c6dd.up.railway.app/health |
| # | Requirement | Status |
|---|---|---|
| 1 | Agent responds to natural language queries in chosen domain (finance / portfolio management) | β |
| 2 | At least 3 functional tools the agent can invoke | β 10+ tools (portfolio, performance, risk, market, transaction, execution, and more) |
| 3 | Tool calls execute successfully and return structured results | β |
| 4 | Agent synthesises tool results into coherent, domain-specific responses | β Claude Sonnet 4.6 streaming synthesis |
| 5 | Conversation history maintained across turns | β Supabase-backed persistence (in-process fallback) |
| 6 | Basic error handling β graceful failure, not crashes | β Typed exception handlers for timeout, connection, and unexpected errors |
| 7 | At least one domain-specific verification check | β Five-layer verification (hallucination, rate limit, trade floor, sandbox, PII sanitisation) |
| 8 | Simple evaluation: 5+ test cases with expected outcomes | β 56 test cases across portfolio, transaction, and general categories |
| 9 | Deployed and publicly accessible | β Railway + Vercel (URLs above) |
Every agent message that invokes a tool shows a collapsible "Show tool call" toggle in the chat UI. Clicking it expands a syntax-highlighted JSON panel displaying the exact tool name and parameters sent to the backend. This provides direct, per-message verification that tool calls are being made.
| Feature | Detail |
|---|---|
| Dual-LLM reasoning | Claude Sonnet 4.6 for synthesis Β· GPT-4o Mini for parameter normalisation |
| Streaming responses | Server-Sent Events (SSE) β token-by-token typewriter effect |
| Tool call debug view | Collapsible JSON panel in chat showing each tool invocation |
| Data visualisation | Interactive doughnut charts for portfolio allocation queries |
| Five-layer security | Hallucination check Β· rate limiting Β· trade floor Β· sandbox execution Β· PII sanitisation |
| Human-in-the-loop | Countdown confirmation modal before any trade is submitted |
| Conversation memory | Supabase-backed persistence; falls back to in-process store if unconfigured |
| Cost tracking | Per-call and cumulative token spend logged to LangSmith |
| React frontend | Vite + TypeScript Β· dark theme Β· syntax-highlighted code blocks |
The eval suite lives in evals/ and contains 56 test cases across three categories.
| Category | Count | Eval methods |
|---|---|---|
| Portfolio | 22 | keyword_match, json_schema_match, llm_as_judge |
| Transaction | 22 | keyword_match, json_schema_match, llm_as_judge |
| General | 12 | keyword_match, llm_as_judge |
| Method | Description |
|---|---|
keyword_match |
All response_contains strings must appear in the response (case-insensitive) |
json_schema_match |
Stricter variant: symbols, numbers, and action words must all be present |
llm_as_judge |
Claude Sonnet 4.6 scores the response 0β1 against a human-written rubric; pass β₯ 0.70 |
# Install dependencies (from agent/)
pip install -r requirements.txt
# Run all 56 tests against the live Railway deployment
python3 evals/eval_runner.py \
--agent-url https://ghostfolio-production-c6dd.up.railway.app
# Run against a local agent
python3 evals/eval_runner.py
# Only portfolio tests, save to a custom report path
python3 evals/eval_runner.py --category portfolio \
--output results/portfolio_$(date +%Y%m%d).md
# Skip LangSmith tracing
python3 evals/eval_runner.py --no-langsmithThe runner produces a Markdown evaluation report at evals/evaluation_report.md (overridable with --output).
evals/test_cases.json β 56 structured test cases, each with:
idβ unique identifier (e.g.P-001,T-012,G-005)categoryβportfolio|transaction|generaldescriptionβ human-readable test nameinputβ the natural-language query sent to the agenteval_methodβ one of the three methods aboveexpected_outputβresponse_containskeywords orjudge_criteriarubric
Set LANGSMITH_API_KEY and LANGSMITH_PROJECT to have each eval run recorded as an individual trace with pass/fail feedback scores in LangSmith.
- Python 3.11+
- Node 18+ (frontend)
- A running Ghostfolio instance (or use the demo fallback)
git clone https://github.com/Theesamkos/ghostfolio
cd ghostfolio/agent
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtcp ../.env.example .envRequired variables:
# LLMs
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
# Ghostfolio API
GHOSTFOLIO_API_URL=http://localhost:3333
GHOSTFOLIO_SECRET_TOKEN=your-ghostfolio-secret-token
# Optional: LangSmith tracing
LANGSMITH_API_KEY=ls-...
LANGSMITH_PROJECT=ghostfolio-agent
# Optional: Supabase persistent memory
SUPABASE_URL=https://xyz.supabase.co
SUPABASE_ANON_KEY=eyJ...
# Optional: Redis rate limiting (falls back to in-memory if unset)
REDIS_URL=redis://localhost:6379uvicorn src.main:app --host 0.0.0.0 --port 8000 --reloadThe API is now available at http://localhost:8000. Interactive docs: http://localhost:8000/docs.
cd frontend
npm install
npm run devOpen http://localhost:5173 in your browser.
By default the agent uses an in-process conversation store that resets on restart. To enable cross-restart, cross-instance persistence:
- Open your Supabase project β SQL Editor and run the migration:
# File: agent/migrations/001_create_conversations.sql - Set two environment variables in Railway (or your
.env):SUPABASE_URL=https://your-project.supabase.co SUPABASE_ANON_KEY=eyJ...
- Redeploy β the backend will auto-detect the variables and switch to
SupabaseMemory.
If either variable is absent the server falls back to ConversationMemory silently.
# From the repo root
docker compose up agentThe Dockerfile in this directory builds the FastAPI backend. The frontend is served separately by Vite (dev) or Nginx (prod).
agent/
βββ src/
β βββ main.py # FastAPI app β SSE streaming endpoint
β βββ agent.py # Dual-LLM pipeline + streaming generator
β βββ llm.py # Anthropic / OpenAI client initialisation
β βββ memory.py # ConversationMemory + SupabaseMemory backends
β βββ cost_tracker.py # Token spend tracking + LangSmith integration
β βββ verification.py # Five-layer verification system
βββ tools/
β βββ portfolio.py # Portfolio analysis + chart data generation
β βββ performance.py # Returns, benchmark comparison
β βββ risk.py # Volatility, Sharpe ratio, VaR
β βββ market.py # Live market data
β βββ transaction.py # Transaction history
β βββ execution.py # Trade execution (HITL gated)
βββ migrations/
β βββ 001_create_conversations.sql # Supabase schema migration
βββ frontend/ # React + Vite + TypeScript chat UI
β βββ src/
β βββ components/
β βββ ChatInterface.tsx # SSE streaming fetch + state
β βββ Message.tsx # Markdown + chart + debug view
β βββ TradeConfirmationModal.tsx
β βββ charts/
β βββ PieChart.tsx # Chart.js doughnut component
βββ evals/ # Evaluation harness (56 test cases)
β βββ eval_runner.py # Runner β SSE stream, 3 eval methods, MD report
β βββ test_cases.json # 56 test cases (portfolio / transaction / general)
βββ tests/ # Pytest unit tests
βββ requirements.txt
βββ setup.py
βββ Dockerfile
Streams the agent's response as Server-Sent Events.
Request body
{
"message": "What is my portfolio allocation?",
"session_id": "abc-123"
}SSE event stream
data: {"type": "token", "content": "Your "}
data: {"type": "token", "content": "portfolio "}
data: {"type": "tool_call", "tool_name": "get_portfolio_allocation", "parameters": {"currency": "USD"}}
...
data: {"type": "done", "session_id": "abc-123", "trade_preview": null}
| Event type | Fields | Description |
|---|---|---|
token |
content: str |
Incremental response chunk |
tool_call |
tool_name: str, parameters: object |
Tool invocation details (shown in debug panel) |
done |
session_id, trade_preview |
Stream complete; trade preview present if a trade was prepared |
Returns server status with a UTC timestamp:
{"status": "ok", "timestamp": "2025-01-15T12:34:56.789012+00:00"}Returns the conversation history for a session.
Clears the history for a session.
Swagger UI for all endpoints.
Every agent response passes through five gates before tokens reach the client:
- Hallucination detection β cross-checks tool output against response text
- Rate limiting β per-session request throttling (Redis-backed, in-memory fallback)
- Trade floor enforcement β rejects orders below configurable minimum value
- Sandbox execution β tool calls run in a restricted async context
- Output sanitisation β strips PII patterns before streaming
See docs/AGENT_ARCHITECTURE.md for full details.
When you ask about portfolio allocation, the agent automatically embeds a Chart.js-compatible JSON block in its response. The frontend (Message.tsx) detects ```chart code fences and renders them as interactive doughnut charts via PieChart.tsx.
Example query: "Show me my portfolio allocation"
# Unit tests
pytest tests/ -v
# Evaluation harness β all 56 cases against local agent
python3 evals/eval_runner.py
# Evaluation harness β all 56 cases against Railway deployment
python3 evals/eval_runner.py \
--agent-url https://ghostfolio-production-c6dd.up.railway.appUser message
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β FastAPI /agent/query β
β (SSE StreamingResponse) β
ββββββββββββββββββββ¬βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β Five-Layer Verification β
ββββββββββββββββββββ¬βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β Step 1 β Claude Sonnet 4.6 β
β Reason Β· Select tool Β· Build params β
ββββββββββββββββββββ¬βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β Step 2 β GPT-4o Mini β
β Normalise & validate tool params β
ββββββββββββββββββββ¬βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β Step 3 β Tool Execution β
β portfolio / performance / risk / ... β
β β³ emits tool_call SSE event β
ββββββββββββββββββββ¬βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β Step 4 β Claude Sonnet 4.6 β
β Synthesise Β· Stream tokens to client β
ββββββββββββββββββββ¬βββββββββββββββββββββββ
β
token events βββΊ browser
tool_call event βΊ debug panel
done event βββΊ browser
Full diagram with HITL trade flow β docs/AGENT_ARCHITECTURE.md.
- Fork the repo and create a feature branch
- Run
pytest tests/ -vto verify existing tests pass - Add tests for new behaviour in
tests/ - Open a pull request against
main
MIT β see LICENSE in the repository root.
Β© 2021 - 2026 Ghostfolio
Licensed under the AGPLv3 License.
Footnotes
-
Available with Ghostfolio Premium. β©


