{"id":23776063,"url":"https://github.com/rodionlim/portfolio-manager-go","last_synced_at":"2026-01-30T03:00:06.561Z","repository":{"id":158043122,"uuid":"625735083","full_name":"rodionlim/portfolio-manager-go","owner":"rodionlim","description":"Application to value a portfolio of multiple asset classes, automatically compute dividends and fetch market prices from free data sources. Exposes a MCP Server and integrates with LLMs.","archived":false,"fork":false,"pushed_at":"2026-01-22T14:21:04.000Z","size":30170,"stargazers_count":4,"open_issues_count":14,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-23T08:47:54.288Z","etag":null,"topics":["ai","dividends","gemini","golang","llm","mcp-server","nasdaq","portfolio","sgx","stock-market","swagger","yahoo-finance-api"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rodionlim.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-04-10T01:45:03.000Z","updated_at":"2026-01-22T14:20:29.000Z","dependencies_parsed_at":"2025-02-15T17:23:42.028Z","dependency_job_id":"57af6309-f355-43e9-a93b-73ea5ebb05dd","html_url":"https://github.com/rodionlim/portfolio-manager-go","commit_stats":null,"previous_names":[],"tags_count":71,"template":false,"template_full_name":null,"purl":"pkg:github/rodionlim/portfolio-manager-go","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodionlim%2Fportfolio-manager-go","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodionlim%2Fportfolio-manager-go/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodionlim%2Fportfolio-manager-go/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodionlim%2Fportfolio-manager-go/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rodionlim","download_url":"https://codeload.github.com/rodionlim/portfolio-manager-go/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodionlim%2Fportfolio-manager-go/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28897628,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-29T21:06:44.224Z","status":"online","status_checked_at":"2026-01-30T02:00:06.810Z","response_time":66,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","dividends","gemini","golang","llm","mcp-server","nasdaq","portfolio","sgx","stock-market","swagger","yahoo-finance-api"],"created_at":"2025-01-01T07:12:29.561Z","updated_at":"2026-01-30T03:00:06.538Z","avatar_url":"https://github.com/rodionlim.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Portfolio Manager\n\n[![CI](https://github.com/rodionlim/portfolio-manager-go/actions/workflows/ci.yml/badge.svg)](https://github.com/rodionlim/portfolio-manager-go/actions/workflows/ci.yml) [![Version](https://img.shields.io/badge/version-1.42.0-blue.svg)](https://github.com/rodionlim/portfolio-manager-go/blob/main/VERSION)\n\nAn application to value equities, fx, commodities, cash, bonds (corps / gov), and cryptocurrencies in your personal portfolio and use LLMs to generate trading insights.\n\n## Features\n\n- Value assets of different currencies based on current market prices\n- Fetch market data based on free data sources (Yahoo finance, Google finance, dividends.sg, ilovessb.com, mas, nasdaq), current and historical\n- Import / Export portfolio blotter data using CSV file for easy migration to other portfolio systems\n- Allow users to supply their own custom dividends metadata\n- Export ticker reference data in yaml format\n- Autoclosing expired positions\n- Infer historical fx rates for blotter trades\n- Store portfolio, reference, dividends and coupon data in leveldb for persistence\n- Display detailed information for individual and aggregated assets\n- Collect and display portfolio statistics such as IRR, MV, Price Paid\n- Integration with Gemini to derive trading insights via Analytics module\n- Portfolio analytics such as generating correlation matrix and benchmarking book performance against user-defined benchmarks\n- OpenAPI compliant for easy integration with other systems\n- UI for end users\n- Cloud and Local backup and restore (Google Drive)\n- MCP (Model Context Protocol) server for LLM integration\n\n## Installation\n\n1. Install Go version \u003cb\u003e1.24.3\u003c/b\u003e or higher.\n2. Clone the repository to your local machine.\n3. Run `make` to build and install the application\n4. Run the `portfolio-manager` binary to start the application. Pass in config flag `-config custom-config.yaml`\n\n### Environment Variables\n\nThe application supports the following environment variables:\n\n- `GEMINI_API_KEY`: API key for Google Gemini AI service (used for SGX report analysis). If set, this will override the `geminiApiKey` setting in the config file.\n\n- `ANALYTICS_SCHEDULE`: Cron expression for scheduling the collection of SGX Reports, If set, this will override the `analytics.schedule` settings in the config file.\n\n### Proxmox VE Helper Scripts\n\nFor home-labbers, helpers scripts are exposed to allow easy installation of `portfolio-manager` in lxc containers within Proxmox VE.\n\n```sh\nbash -c \"$(wget -qLO - https://github.com/rodionlim/portfolio-manager-go/raw/main/lxc/portfolio-manager.sh)\"\n```\n\n#### Setting Environment Variables in Proxmox\n\nFor Proxmox deployments, you can set environment variables directly in the systemd unit file. After installation, edit the service file:\n\n```sh\n# Edit the systemd service file\nsudo systemctl edit PortfolioManager --full\n\n# Check environment variables file in the [Service] section:\n[Service]\nEnvironmentFile=/etc/sysconfig/PortfolioManager\n\n# Create an env file with the following\nGEMINI_API_KEY=your_api_key_here\n\n# Reload and restart the service\nsudo systemctl daemon-reload\nsudo systemctl restart PortfolioManager\n```\n\n## Quickstart\n\nStart the application\n\n```sh\nmake run # only start application backend\nmake run-full # if user wants to start with the UI, run this command instead\n```\n\nFor Developers\n\n```sh\nmake run # start backend\ncd web/ui \u0026\u0026 npm run dev # start ui with hot reload on http://localhost:5173\n```\n\nBuild the application\n\n```sh\nmake\n```\n\nWipe the entire database\n\n```sh\nmake clean-db\n```\n\nTests\n\n```sh\nmake test # unit tests\nmake test-integration # integration tests\n```\n\n## CLI Commands\n\nThe portfolio-manager binary includes CLI commands for interacting with a local/remote server, database backup and restore operations:\n\n#### List all positions\n\n```sh\n./portfolio-manager position-list\n```\n\n#### Delete a specific position\n\n```sh\n./portfolio-manager position-delete \u003cbook\u003e \u003cticker\u003e\n```\n\n#### Specify custom server URL\n\n```sh\n./portfolio-manager --url=http://localhost:8080 position-list\n./portfolio-manager --url=http://localhost:8080 position-delete \"Rodion\" \"AAPL\"\n```\n\n### Database Backup\n\n**Note:** Backup and Restore commands only work for a **local** portfolio-manager server because they require direct access to the database files on disk. To backup a remote server, run these commands locally on that server (e.g., via SSH).\n\nCreate a backup of the database:\n\n```sh\n./portfolio-manager backup --source [local|gdrive|nextcloud] --uri [path|url] --user [username] --password [password] --include-data [true|false]\n```\n\n**Parameters:**\n\n- `--source`: Backup destination (local, gdrive, nextcloud)\n- `--uri`: File location or URL (required for local and nextcloud)\n- `--user`: Credentials filepath for remote sources (gdrive, nextcloud)\n- `--password`: Password for remote sources (nextcloud)\n- `--include-data`: Include the data folder (funds flow, reports etc.) in the backup (default: true)\n\n**Examples:**\n\n```sh\n# Local backup to ./backups directory\n./portfolio-manager backup --source local --uri ./backups\n\n# Local backup to specific directory\n./portfolio-manager backup --source local --uri /path/to/backup/location\n\n# Google Drive backup (personal account with OAuth2), if not uri specified, defaults to portfolio-manager-go/backups folder\n./portfolio-manager backup --source gdrive --user ./credentials.json\n\n**Note on Google Drive OAuth2:**\nIf you are using a personal Google account, use OAuth2 Desktop credentials instead of a Service Account to avoid storage quota issues.\n1. Create an **OAuth 2.0 Client ID** of type **Desktop App** in Google Cloud Console.\n2. Download the JSON file and use it with the `--user` flag.\n3. **Important**: Google has blocked the out-of-band (OOB) \"copy-paste\" flow. The application now uses a local loopback server on port **8888** for authentication.\n4. Ensure your **OAuth Client ID** in Google Console allows `http://localhost:8888` as a redirect URI (though for Desktop App types, localhost is usually allowed by default).\n5. On first run, follow the link in your terminal. After authorization, you will be redirected to the URI specified in your `credentials.json` (e.g., `http://localhost:8888` or your Tailscale domain) and the application will capture the code automatically.\n6. A `token.json` will be saved locally for future automatic backups.\n7. **Remote Servers (Proxmox/LXC)**: If you are running the app on a remote server, you have two options for the one-time authentication step:\n   - **Option A (Custom Domain)**: Use a reachable domain in your `redirect_uris` (like a Tailscale `.ts.net` address). Ensure port **8888** (or the port in your URI) is open in your server firewall. (*Note: Only works for \"Web Application\" client types, not \"Desktop App\"*).\n   - **Option B (Port Forwarding)**: Keep `localhost:8888` in your credentials and \"beam\" the traffic from your local laptop to the server using one of these commands on your **local machine**:\n     - **SSH Tunnel**: `ssh -L 8888:localhost:8888 your-server`\n     - **Socat**: `socat TCP4-LISTEN:8888,fork TCP4:your-server:8888`\n\n# Nextcloud backup (not yet implemented)\n./portfolio-manager backup --source nextcloud --uri https://your-nextcloud.com --user username --password password\n```\n\n### Database Restore\n\nRestore database from a backup:\n\n```sh\n./portfolio-manager restore-from-backup --source [local|gdrive|nextcloud] --uri [path|url] --user [username] --password [password]\n```\n\n**Important:** The application must be stopped before restoring from backup. The restore operation will completely replace the existing database.\n\n**Examples:**\n\n```sh\n# Restore from local backup file\n./portfolio-manager restore-from-backup --source local --uri ./backups/portfolio-manager-backup-20240101-120000.tar.gz\n\n# Restore from Google Drive\n# 1. Default: If no filename or folder is specified, default to portfolio-manager-go/backups\n./portfolio-manager restore-from-backup --source gdrive --user ./credentials.json\n\n# 1. Automatic: If no filename is specified (just the folder), the latest backup is selected\n./portfolio-manager restore-from-backup --source gdrive --user ./credentials.json --uri portfolio-backups\n\n# 2. Manual: Specify a specific backup file in the folder\n./portfolio-manager restore-from-backup --source gdrive --user ./credentials.json --uri portfolio-backups/portfolio-manager-backup-20240101-120000.tar.gz\n\n# Restore from Nextcloud (not yet implemented)\n./portfolio-manager restore-from-backup --source nextcloud --uri https://your-nextcloud.com --user username --password password\n```\n\n### Version Information\n\nDisplay version information:\n\n```sh\n./portfolio-manager -v\n# or\n./portfolio-manager --version\n```\n\n### Backup and Restore in Proxmox Environment\n\nFor users running portfolio-manager in a Proxmox LXC container:\n\n**Creating a Backup:**\n\n1. Stop the portfolio-manager service:\n\n   ```sh\n   sudo systemctl stop PortfolioManager\n   ```\n\n2. Create the backup:\n\n   ```sh\n   cd /opt/PortfolioManager\n   ./portfolio-manager backup --source local --uri /backup/portfolio-manager\n   ```\n\n3. Restart the service:\n   ```sh\n   sudo systemctl start PortfolioManager\n   ```\n\n**Restoring from a Backup:**\n\n1. Stop the portfolio-manager service:\n\n   ```sh\n   sudo systemctl stop PortfolioManager\n   ```\n\n2. Restore the database:\n\n   ```sh\n   cd /opt/PortfolioManager\n   ./portfolio-manager restore-from-backup --source local --uri /backup/portfolio-manager/portfolio-manager-backup-YYYYMMDD-HHMMSS.tar.gz\n   ```\n\n3. Restart the service:\n   ```sh\n   sudo systemctl start PortfolioManager\n   ```\n\n**Note:** Always ensure you have recent backups before performing restore operations. Backups from different versions may not be compatible.\n\n## Project Structure\n\n```\nportfolio-manager/\n├── cmd/\n│   └── portfolio/\n│       └── main.go\n├── docs/\n│   └── swagger.json\n├── internal/\n│   ├── analytics/\n│   ├── blotter/\n│   ├── config/\n│   ├── dal/\n│   ├── dividends/\n│   ├── fxinfer/\n│   ├── historical/\n│   ├── metrics/\n│   ├── migrations/\n│   ├── mocks/\n│   ├── portfolio/\n│   └── server/\n│       ├── server.go    # Main HTTP server\n│       └── mcp.go       # MCP server integration\n├── lxc/\n├── pkg/\n│   ├── common/\n│   ├── csvutil/\n│   ├── event/\n│   ├── logging/\n│   ├── mdata/\n│   │   └── sources/\n│   ├── rdata/\n│   ├── scheduler/\n│   └── types/\n├── templates/\n│   └── blotter_import.csv # Sample for users to reference when trying to import blotter trades via csv\n│   └── dividends_metadata_import.csv # Sample for users to reference when inserting custom dividends\n├── web/\n├── .gitignore\n├── go.mod\n└── README.md\n```\n\n## For Developers\n\n### Testing and Mocking Strategy\n\nThis project uses the `testify` framework for testing and mocking. While there are two different mocking approaches in this codebase:\n\n1. **Testify-based mocks** (preferred): Located in `internal/mocks/testify/` directory\n2. **Custom mocks**: Located in `internal/mocks/` directory\n\nWhen writing new tests or modifying existing ones, please **use the testify mocks** instead of the custom mock implementation.\n\nExample:\n\n```go\n// Import testify mocks (preferred)\nimport \"portfolio-manager/internal/mocks/testify\"\n\n// Create a mock\nmockService := new(testify.MockService)\nmockService.On(\"MethodName\", arg1, arg2).Return(expectedResult)\n\n// Later verify expectations\nmockService.AssertExpectations(t)\n```\n\n## MCP Server Integration\n\nThis project includes a **Model Context Protocol (MCP) server** that enables Large Language Models (LLMs) to interact directly with your portfolio data. This allows you to query your portfolio using natural language through compatible LLM interfaces.\n\n### Configuration\n\nEnable the MCP server by setting the following in your `config.yaml`:\n\n```yaml\nmcp:\n  enabled: true\n  port: 8081\n```\n\n### Available Tools\n\nThe MCP server provides the following tools for LLM interaction:\n\n#### 1. Query Blotter Trades (`query_blotter_trades`)\n\nQuery your trading history with flexible filtering options:\n\n- **ticker**: Filter by specific ticker symbol\n- **start_date**: Filter trades from a specific date (YYYY-MM-DD format)\n- **end_date**: Filter trades up to a specific date (YYYY-MM-DD format)\n- **trade_type**: Filter by trade type (BUY or SELL)\n- **limit**: Limit the number of results (default: 100)\n\n#### 2. Get Portfolio Positions (`get_portfolio_positions`)\n\nRetrieve current portfolio positions with market values and P\u0026L:\n\n- **book**: Filter by specific book/account (optional, returns all books if not specified)\n\n#### 3. Delete Portfolio Position (`delete_portfolio_position`)\n\nDestructive operation. The agent MUST obtain explicit user confirmation before calling with `confirm=\"yes\"`.\n\nExample tool call payload (conceptual):\n\n```jsonc\n{\n  \"name\": \"delete_portfolio_position\",\n  \"arguments\": {\n    \"book\": \"main\",\n    \"ticker\": \"AAPL\",\n    \"confirm\": \"yes\", // only after user confirms\n  },\n}\n```\n\n#### 4. Fetch Benchmark Interest Rates (`fetch_benchmark_interest_rates`)\n\nFetch benchmark interest rates for a specific country:\n\n- **country**: Country code (e.g., 'SG' for Singapore) **[Required]**\n- **points**: Number of data points to fetch (default: 100)\n\n### Usage Examples\n\nOnce configured and running, you can interact with your portfolio data through any MCP-compatible LLM interface. Example queries:\n\n- \"Show me all my Apple trades from last month\"\n- \"What are my current positions in the tech book?\"\n- \"List all sell trades for the past quarter\"\n- \"Show me my portfolio positions with current market values\"\n- \"Fetch the latest benchmark interest rates for Singapore\"\n- \"Delete portfolio position for main book and ticker SBAUG24\n\n### Potential Use Cases\n\nThe MCP server opens up numerous possibilities for LLM-powered portfolio analysis:\n\n#### **Real-time Portfolio Queries**\n\n- Ask natural language questions about your holdings\n- Get instant summaries of positions, P\u0026L, and performance\n- Query specific trades or transactions with flexible criteria\n\n#### **Investment Research \u0026 Analysis**\n\n- Analyze trading patterns and performance trends\n- Generate custom reports and summaries\n\n#### **Risk Management**\n\n- Monitor portfolio concentration and exposure\n- Identify underperforming positions or sectors\n\nThe MCP server essentially transforms your portfolio manager into an AI-queryable database, making your investment data accessible through natural language interfaces and enabling sophisticated AI-powered analysis workflows.\n\n## Built-in Scheduler (Cron-based)\n\nThis project includes a flexible, cron-based scheduler component that can be used by any package to trigger jobs at specific times or intervals. The scheduler supports standard 5-field cron expressions, enabling developers to easily schedule tasks such as data collection, reporting, or maintenance jobs.\n\n### Key Features\n\n- Schedule any Go function or job using a cron expression (minute, hour, day of month, month, day of week)\n- Reusable across the codebase for any periodic or time-based automation\n- Powered by the robust [robfig/cron](https://pkg.go.dev/github.com/robfig/cron/v3) library\n\n### Example Use Case: Metrics Collection\n\nPortfolio metrics collection is scheduled using the built-in scheduler. For example, to collect metrics every day at midnight:\n\n```\nservice.StartMetricsCollection(\"0 0 * * *\") // Every day at midnight\n```\n\nOr, to collect every 5 minutes:\n\n```\nservice.StartMetricsCollection(\"*/5 * * * *\")\n```\n\nYou can use the scheduler in your own packages to trigger any job on a schedule:\n\n```\nsched, _ := scheduler.NewCronSchedule(\"0 9 * * MON\") // Every Monday at 9:00 AM\nscheduler.ScheduleTaskFunc(myJobFunc, sched)\n```\n\n### Cron Expression Format\n\nA cron expression consists of five fields:\n\n```\n* * * * *\n| | | | |\n| | | | +----- day of week (0 - 6) (Sunday=0)\n| | | +------- month (1 - 12)\n| | +--------- day of month (1 - 31)\n| +----------- hour (0 - 23)\n+------------- minute (0 - 59)\n```\n\n#### Examples\n\n| Cron Expression | Schedule Description               |\n| --------------- | ---------------------------------- |\n| \\* \\* \\* \\* \\*  | Every minute                       |\n| 0 \\* \\* \\* \\*   | Every hour                         |\n| 0 0 \\* \\* \\*    | Every day at 12:00 AM              |\n| 0 0 \\* \\* FRI   | At 12:00 AM, only on Friday        |\n| 0 0 1 \\* \\*     | At 12:00 AM, on day 1 of the month |\n\nFor more details, see [crontab.guru](https://crontab.guru/) or the [robfig/cron](https://pkg.go.dev/github.com/robfig/cron/v3) Go library documentation.\n\n## UI\n\n### Positions\n\nUsers can get an aggregated view of all their positions via the positions component in the user interface.\n\n![Position Table](docs/Positions.png)\n\n### Blotter Table\n\nUser can add, delete and update trades via the blotter component in the user interface.\n\n![Blotter Table](docs/Blotter.png)\n\n### Dividends Table\n\nUser can view dividends history of any given ticker at a granular level by ex-date\n\n![Dividends Table](docs/Dividends.png)\n\n### Dividends Summary\n\nUser can view aggregated dividends by year with more details such as dividend yield etc.\n\n![Dividends Summary Table](docs/DividendsSummary.png)\n\n### Metrics Chart\n\nUsers can visualize portfolio performance over time with both market value and IRR (Internal Rate of Return) plotted together.\n\n![Metrics Chart](docs/MetricsChart.png)\n\n### Analytics Heat Map\n\nUsers can visualize the most commonly traded stocks using a heat map showing institutional net buy/sell values with customizable sorting methods and stock selection.\n\n![Heatmap](docs/Heatmap.png) (DEPRECATED)\n![Top10SGX](docs/Top10SGX.png)\n\n#### Sorting Methods\n\nThe heat map supports three different sorting methods:\n\n1. **Absolute**: Sorts stocks by their absolute YTD institutional net buy/sell amounts (highest absolute values first)\n\n2. **Percentage**: Sorts stocks by percentage change from the earliest to latest report period\n\n3. **Momentum**: Sorts stocks by a momentum score that combines both absolute change and consistency\n\n#### Momentum Calculation\n\nThe momentum score is calculated using the following formula:\n\n```\nMomentum Score = (Absolute Change × 0.7) + (Consistency Score × |Absolute Change| × 0.3)\n```\n\nWhere:\n\n- **Absolute Change**: Latest YTD value - Earliest YTD value\n- **Consistency Score**: Ratio of periods showing positive trend (0 to 1)\n  - Calculated as: Number of periods with positive change ÷ Total periods\n  - A stock with consistent upward movement across all periods gets a score of 1.0\n  - A stock with no positive periods gets a score of 0.0\n\n**Example**: If a stock has:\n\n- Absolute change of +50M SGD\n- Positive movement in 8 out of 10 periods (consistency = 0.8)\n\nThen: Momentum Score = (50 × 0.7) + (0.8 × 50 × 0.3) = 35 + 12 = 47\n\nThis approach prioritizes stocks with both significant absolute changes and consistent directional movement, helping identify stocks with strong institutional buying/selling momentum rather than just one-time large movements.\n\n### Settings\n\nUser can edit application wide settings, such as auto closing expired positions via the user interface\n\n![Settings](docs/Settings.png)\n\n## Backend API - Sample Curl Commands\n\nAll API calls are documented (OAS) under `http://localhost:8080/swagger/index.html`\n\n### Get Blotter Trades\n\n```sh\ncurl http://localhost:8080/api/v1/blotter/trades\n```\n\n### Add Asset to Blotter\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/blotter/trade \\\n    -H \"Content-Type: application/json\" \\\n    -d '{\n        \"ticker\": \"AAPL\",\n        \"side\": \"buy\",\n        \"broker\": \"DBS\",\n        \"book\": \"BookA\",\n        \"account\": \"CDP\",\n        \"quantity\": 10,\n        \"price\": 150.00,\n        \"fx\": 1.33,\n        \"type\": \"buy\",\n        \"tradeDate\": \"2024-12-09T00:00:00Z\"\n    }'\n```\n\n### Update Asset in Blotter\n\n```sh\ncurl -X PUT http://localhost:8080/api/v1/blotter/trade \\\n    -H \"Content-Type: application/json\" \\\n    -d '{\n        \"ticker\": \"AAPL\",\n        \"side\": \"buy\",\n        \"broker\": \"DBS\",\n        \"book\": \"BookA\",\n        \"account\": \"CDP\",\n        \"quantity\": 10,\n        \"price\": 200.00,\n        \"fx\": 1,\n        \"type\": \"buy\",\n        \"tradeDate\": \"2024-12-09T00:00:00Z\"\n    }'\n```\n\n### Delete Assets from Blotter\n\n```sh\ncurl -X DELETE http://localhost:8080/api/v1/blotter/trade \\\n    -H \"Content-Type: application/json\" \\\n    -d '[\"61570b49-2adb-4b99-be20-d14001e761a9\"]'\n```\n\n### Delete all Assets from Blotter and Positions\n\n```sh\ncurl -X DELETE http://localhost:8080/api/v1/blotter/trade/all\ncurl -X DELETE http://localhost:8080/api/v1/portfolio/positions\n```\n\n### Import Trades from CSV (for migrating into portfolio-manager)\n\nNote that FX rate here is always with respect to portfolio revaluation currency per foreign ccy, e.g. USD/SGD if SGD is portfolio revaluation currency\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/blotter/import \\\n  -F \"file=@templates/blotter_import.csv\"\n```\n\n### Export Trades to a CSV (for migrating out of portfolio-manager)\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/blotter/export\n```\n\n### Export Trades with Inferred FX Rates\n\nExport trades with FX rates automatically inferred for trades where FX rate is missing. This amends the blotter in memory as well. Users should wipe all blotter trades and reimport the amended blotter if they want it to be persisted across restarts.\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/blotter/export-with-fx\n```\n\n### Get Portfolio Positions\n\n```sh\ncurl http://localhost:8080/api/v1/portfolio/positions\n```\n\n### Get Portfolio Positions (Lite)\n\nGet positions without enrichment (no FX rates, dividends, or market values). Useful for UI components that only need ticker information and want faster response times.\n\n```sh\ncurl http://localhost:8080/api/v1/portfolio/positions/lite\n```\n\n### Delete Single Position\n\nDelete a specific position by book and ticker:\n\n```sh\ncurl -X DELETE \"http://localhost:8080/api/v1/portfolio/position?book=Rodion\u0026ticker=SBAUG24\"\n```\n\n### Get Current FX Rates for relevant currencies in our blotter\n\nGet current FX rates for all currencies in the blotter. Returns a JSON mapping of currencies to their current exchange rates relative to the base currency.\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/blotter/fx\n```\n\n### View Blotter Trades\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/blotter/trade\n```\n\n### View Positions\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/portfolio/positions\n```\n\n### Portfolio Metrics\n\nGet portfolio metrics (IRR, Market Value, Price Paid) for the entire portfolio:\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/metrics\n```\n\nGet portfolio metrics filtered by a specific book:\n\n```sh\ncurl -X GET \"http://localhost:8080/api/v1/metrics?book_filter=tactical\"\n```\n\nBenchmark portfolio performance against a user-specified benchmark:\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/metrics/benchmark \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"book_filter\": \"tactical\",\n    \"benchmark_cost\": {\"pct\": 0.0012, \"absolute\": 10.00},\n    \"mode\": \"match_trades\",\n    \"benchmark_tickers\": [\n      {\"ticker\": \"ES3.SI\", \"weight\": 1.0}\n    ]\n  }'\n```\n\nBenchmark portfolio performance against a two-ticker benchmark:\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/metrics/benchmark \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"book_filter\": \"tactical\",\n    \"benchmark_cost\": {\"pct\": 0.002, \"absolute\": 3.00},\n    \"mode\": \"buy_at_start\",\n    \"notional\": 100000,\n    \"benchmark_tickers\": [\n      {\"ticker\": \"SPY\", \"weight\": 0.7},\n      {\"ticker\": \"AGG\", \"weight\": 0.3}\n    ]\n  }'\n```\n\n### Fetch Asset Prices\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/mdata/price/es3.si\ncurl -X GET http://localhost:8080/api/v1/mdata/price/temb\ncurl -X GET http://localhost:8080/api/v1/mdata/price/eth-usd\ncurl -X GET http://localhost:8080/api/v1/mdata/price/usd-sgd\n```\n\n### Fetch Historical Price Data\n\n```sh\n# Get historical price data from January 1, 2024 to current date\ncurl -X GET \"http://localhost:8080/api/v1/mdata/price/historical/AAPL?start=20240101\"\n\n# Get historical price data for a specific date range\ncurl -X GET \"http://localhost:8080/api/v1/mdata/price/historical/ES3.SI?start=20240101\u0026end=20240501\"\n```\n\n### Fetch Historical FX Rates\n\nGet historical foreign exchange rates for currency pairs. This is particularly useful for retrieving FX rates for specific trade dates when adding blotter entries with foreign currency assets.\n\n```sh\n# Get USD-SGD FX rate for a specific date (YYYYMMDD format)\ncurl -X GET \"http://localhost:8080/api/v1/mdata/price/historical/USD-SGD?start=20250602\u0026end=20250602\"\n\n# Get JPY-SGD FX rate for the last month\ncurl -X GET \"http://localhost:8080/api/v1/mdata/price/historical/JPY-SGD?start=20240401\u0026end=20240430\"\n```\n\n### Calculate Historical Correlations\n\nCalculate correlation matrix for a list of tickers over a specified date range.\n\ndate_method: \"rolling\", \"expanding\" or \"in_sample\"\ninterval_frequency: Controls how often a new correlation matrix is emitted.\n\nNotes:\n\n- If `tickers` is omitted/empty, the server uses all **enabled** tickers from historical configs (`GET /api/v1/historical/config`).\n- If `to` is omitted, it defaults to today.\n- Set `resync: true` to sync stored historical data for the selected tickers before computing correlation.\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/historical/correlation \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"tickers\": [\"D05.SI\", \"C09.SI\"],\n    \"from\": \"2022-01-01\",\n    \"to\": \"2026-01-01\",\n    \"resync\": false,\n    \"options\": {\n      \"frequency\": \"D\",\n      \"is_price_series\": true,\n      \"date_method\": \"rolling\",\n      \"rollyears\": 1,\n      \"interval_frequency\": \"12M\",\n      \"using_exponent\": true,\n      \"ew_lookback\": 100,\n      \"min_periods\": 200,\n      \"floor_at_zero\": true\n    }\n  }'\n```\n\nCompute correlation across all enabled historical configs (no explicit tickers; defaults `to` to today):\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/historical/correlation \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"from\": \"2022-01-01\",\n    \"resync\": false,\n    \"options\": {\n      \"frequency\": \"D\",\n      \"is_price_series\": true,\n      \"date_method\": \"rolling\",\n      \"rollyears\": 1,\n      \"interval_frequency\": \"12M\",\n      \"using_exponent\": true,\n      \"ew_lookback\": 100,\n      \"min_periods\": 200\n    }\n  }'\n```\n\n### Fetch Dividends\n\n```sh\n# equity - refer to ticker reference for identifier\ncurl -X GET http://localhost:8080/api/v1/mdata/dividends/es3.si\ncurl -X GET http://localhost:8080/api/v1/mdata/dividends/aapl\n\n# ssb - format SBMMMYY\ncurl -X GET http://localhost:8080/api/v1/mdata/dividends/sbjul24\n\n# mas bill\ncurl -X GET http://localhost:8080/api/v1/mdata/dividends/bs24124z\n```\n\n### Delete Dividends\n\n```sh\n# Delete custom dividends for AAPL\ncurl -X DELETE \"http://localhost:8080/api/v1/mdata/dividends/AAPL?type=Custom\"\n\n# Delete official dividends\ncurl -X DELETE \"http://localhost:8080/api/v1/mdata/dividends/C2PU.SI?type=Official\"\n```\n\n### Store Custom Dividends\n\nNote that custom dividends are concatenated against official dividends. Custom dividends always take precedence over official dividends, in case official figures are wrong. If official date is wrong, we can always input a dividend with value of 0.\n\nWhen uploading custom dividends, it is grouped by ticker. As long as the ticker is present in the csv, all dividends for that ticker will be replaced with the new set of dividends provided. Tickers which are not present in the upload will remain unchanged.\n\nAlso, we store some of the custom dividends that are wrong from dividends.sg in ./templates/\\*.csv for user reference.\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/mdata/dividends/AAPL \\\n  -H \"Content-Type: application/json\" \\\n  -d '[\n    {\n      \"ExDate\": \"2024-11-10\",\n      \"Amount\": 120.00,\n      \"AmountPerShare\": 0.24,\n      \"Qty\": 500\n    },\n    {\n      \"ExDate\": \"2024-08-09\",\n      \"Amount\": 115.00,\n      \"AmountPerShare\": 0.23,\n      \"Qty\": 500\n    }\n  ]'\n```\n\n### Fetch Portfolio Dividends\n\nSingle Ticker\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/dividends/cjlu.si\n```\n\nAll Tickers\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/dividends\n```\n\n### Fetch Benchmark Interest Rates\n\nGet default number of Singapore interest rates (250 records)\n\n```sh\ncurl -X GET \"http://localhost:8080/api/v1/mdata/interest-rates/SG\"\n```\n\nGet specific number of records\n\n```sh\ncurl -X GET \"http://localhost:8080/api/v1/mdata/interest-rates/SG?points=5\"\n```\n\n### Fetch Reference Data\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/refdata\n```\n\n## Import Reference Data (YAML)\n\nYou can import reference data from a YAML file using the following endpoint:\n\n```\ncurl -X POST \\\n  -F \"file=@seed/refdata.yaml\" \\\n  http://localhost:8080/api/v1/refdata/import\n```\n\n- Replace `seed/refdata.yaml` with the path to your YAML file if different.\n- The response will indicate how many tickers were inserted or updated.\n\n### Historical Portfolio Metrics\n\nGet all historical metrics:\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/historical/metrics\n```\n\nExport historical metrics as CSV: (takes in book_filter as an optional query parameter)\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/historical/metrics/export\n```\n\nImport historical metrics from CSV file:\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/historical/metrics/import \\\n  -F \"file=@/path/to/historical_metrics_import.csv\"\n```\n\nInsert or update a historical metric:\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/historical/metrics \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"timestamp\": \"2024-05-15T00:00:00Z\",\n    \"metrics\": {\n      \"irr\": 0.12,\n      \"pricePaid\": 50000,\n      \"mv\": 55000,\n      \"totalDividends\": 1200\n    }\n  }'\n```\n\nInsert or update a historical metric for a specific book:\n\n```sh\ncurl -X POST \"http://localhost:8080/api/v1/historical/metrics?book_filter=tactical\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"timestamp\": \"2024-05-15T00:00:00Z\",\n    \"metrics\": {\n      \"irr\": 0.12,\n      \"pricePaid\": 50000,\n      \"mv\": 55000,\n      \"totalDividends\": 1200\n    }\n  }'\n```\n\nDelete historical metrics:\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/historical/metrics/delete \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"timestamps\": [\n      \"2024-05-15T00:00:00Z\",\n      \"2024-06-15T00:00:00Z\"\n    ]\n  }'\n```\n\nFetch cached previous daily prices with latest metrics (batch tickers):\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/historical/prices/cached \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"tickers\": [\"AAPL\", \"ES3.SI\", \"TSLA\"]\n  }'\n```\n\nDelete historical metrics for a specific book:\n\n```sh\ncurl -X POST \"http://localhost:8080/api/v1/historical/metrics/delete?book_filter=tactical\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"timestamps\": [\n      \"2024-05-15T00:00:00Z\",\n      \"2024-06-15T00:00:00Z\"\n    ]\n  }'\n```\n\n### Historical Metrics Jobs (Custom Scheduling)\n\nCreate a custom metrics collection job for a specific book, using the default schedule (Same as entire portfolio):\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/historical/metrics/jobs \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"cronExpr\": \"\",\n    \"bookFilter\": \"tactical\"\n  }'\n```\n\nList all custom metrics jobs:\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/historical/metrics/jobs\n```\n\nList all metrics jobs (including portfolio job for UI):\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/historical/metrics/jobs/all\n```\n\nDelete a custom metrics job:\n\n```sh\ncurl -X DELETE http://localhost:8080/api/v1/historical/metrics/jobs/tactical\n```\n\nManually trigger metrics collection for entire portfolio:\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/historical/metrics/trigger \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"bookFilter\": \"\"\n  }'\n```\n\nManually trigger metrics collection for a specific book:\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/historical/metrics/trigger \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"bookFilter\": \"tactical\"\n  }'\n```\n\n### Add Reference Data\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/refdata \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n        \"id\": \"ES3.SI\",\n        \"name\": \"STI ETF\",\n        \"underlying_ticker\": \"ES3.SI\",\n        \"yahoo_ticker\": \"ES3.SI\",\n        \"dividends_sg_ticker\": \"ES3\",\n        \"asset_class\": \"eq\",\n        \"asset_sub_class\": \"etf\",\n        \"ccy\": \"SGD\",\n        \"domicile\": \"SG\"\n      }'\n```\n\n### Delete Reference Data\n\n```sh\ncurl -X DELETE http://localhost:8080/api/v1/refdata \\\n    -H \"Content-Type: application/json\" \\\n    -d '[\"ES3.SI\"]'\n```\n\n### Export Reference Data\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/refdata/export\n```\n\n### Force a compute of dividends for a ticker across the entire blotter\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/dividends -H \"Content-Type: application/json\" -d '{\"ticker\": \"ES3.SI\"}'\n```\n\n### Autoclose expired positions\n\nTbills, SSB that has expired should have a corresponding \"sell\" blotter entry that can be infered automatically based on the reference data\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/portfolio/cleanup\n```\n\n### Get Portfolio Metrics, e.g. IRR (Internal Rate of Return), Price Paid, MV, Total Dividends\n\nGet portfolio metrics such as Internal Rate of Return (IRR) for the entire portfolio. Returns the calculated IRR and other metrics as a JSON object.\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/metrics\n```\n\n### Fetch Historical Portfolio Metrics\n\nFetch all historical portfolio metrics (date-stamped portfolio metrics).\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/historical/metrics\n```\n\nFetch historical portfolio metrics filtered by book:\n\n```sh\ncurl -X GET \"http://localhost:8080/api/v1/historical/metrics?book_filter=tactical\"\n```\n\n### Import Historical Portfolio Metrics from CSV\n\nImport historical portfolio metrics (date-stamped portfolio metrics) from a CSV file. The CSV should have the following headers:\n\n```\nDate,IRR,PricePaid,MV,TotalDividends\n```\n\nSample file: `templates/historical_metrics_import.csv`\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/historical/metrics/import \\\n  -F \"file=@templates/historical_metrics_import.csv\"\n```\n\n### Export Historical Portfolio Metrics to CSV\n\nExport all historical portfolio metrics as a CSV file.\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/historical/metrics/export -o historical_metrics_export.csv\n```\n\n### Upsert (Insert/Update) a Single Historical Portfolio Metric\n\nInsert or update a single historical portfolio metric (date-stamped portfolio metric) using POST or PUT. Update is keyed on timestamp date. The JSON should match the `TimestampedMetrics` structure:\n\n```\n{\n  \"timestamp\": \"2025-05-11T00:00:00Z\",\n  \"metrics\": {\n    \"irr\": 0.2,\n    \"pricePaid\": 10000,\n    \"mv\": 12000,\n    \"totalDividends\": 500\n  }\n}\n```\n\nSample curl (insert or update):\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/historical/metrics \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"timestamp\": \"2025-05-11T00:00:00Z\",\n    \"metrics\": {\"irr\": 0.2, \"pricePaid\": 10000, \"mv\": 12000, \"totalDividends\": 500}\n  }'\n\ncurl -X PUT http://localhost:8080/api/v1/historical/metrics \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"timestamp\": \"2025-05-11T00:00:00Z\",\n    \"metrics\": {\"irr\": 0.2, \"pricePaid\": 10000, \"mv\": 12000, \"totalDividends\": 500}\n  }'\n```\n\nSample curl (insert or update for a specific book):\n\n```sh\ncurl -X POST \"http://localhost:8080/api/v1/historical/metrics?book_filter=tactical\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"timestamp\": \"2025-05-11T00:00:00Z\",\n    \"metrics\": {\"irr\": 0.2, \"pricePaid\": 10000, \"mv\": 12000, \"totalDividends\": 500}\n  }'\n\ncurl -X PUT \"http://localhost:8080/api/v1/historical/metrics?book_filter=tactical\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"timestamp\": \"2025-05-11T00:00:00Z\",\n    \"metrics\": {\"irr\": 0.2, \"pricePaid\": 10000, \"mv\": 12000, \"totalDividends\": 500}\n  }'\n```\n\nSee also: Import/Export endpoints for batch operations.\n\n### Analytics - SGX Report Analysis\n\nFetch, download and analyze the latest SGX report of a specific type with AI Insight:\n\n```sh\ncurl -X GET \"http://localhost:8080/api/v1/analytics/latest?type=fund%20flow\"\n```\n\nList all downloaded SGX reports\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/analytics/list_files\n```\n\nAnalyze an existing file in the data directory:\n\n```sh\ncurl -X POST http://localhost:8080/api/v1/analytics/analyze \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"filePath\": \"./data/SGX_Fund_Flow_Weekly_Tracker_Week_of_26_May_2025.xlsx\"\n  }'\n```\n\nList all stored analysis results:\n\n```sh\ncurl -X GET http://localhost:8080/api/v1/analytics/list_analysis\n```\n\nDownload the latest N SGX reports:\n\n```sh\ncurl -X GET \"http://localhost:8080/api/v1/analytics/download?n=5\"\n```\n\nDownload the latest N SGX reports filtered by type:\n\n```sh\ncurl -X GET \"http://localhost:8080/api/v1/analytics/download?n=3\u0026type=fund%20flow\"\n```\n\nAnalyze the latest N SGX reports filtered by type:\n\n```sh\ncurl -X GET \"http://localhost:8080/api/v1/analytics/analyze_latest?n=3\u0026type=fund%20flow\"\n```\n\nAnalyze the latest N SGX reports with force reanalysis (This will use additional API limits unnecessarily):\n\n```sh\ncurl -X GET \"http://localhost:8080/api/v1/analytics/analyze_latest?n=3\u0026type=fund%20flow\u0026force=true\"\n```\n\nExtract 100 Most Traded Stocks data from latest SGX Fund Flow Weekly Tracker reports:\n\n```sh\n# Get all available reports\ncurl -X GET http://localhost:8080/api/v1/analytics/most_traded_stocks # DEPRECATED\ncurl -X GET http://localhost:8080/api/v1/analytics/top10_stocks\n\n# Get only the latest 2 reports\ncurl -X GET \"http://localhost:8080/api/v1/analytics/most_traded_stocks?n=2\"\ncurl -X GET http://localhost:8080/api/v1/analytics/top10_stocks?n=2\n```\n\nExtract Institutional sector funds flow data from latest SGX Fund Flow Weekly Tracker reports:\n\n```sh\n# Get all available sector funds flow reports\ncurl -X GET http://localhost:8080/api/v1/analytics/sector_funds_flow\n\n# Get only the latest 3 reports\ncurl -X GET \"http://localhost:8080/api/v1/analytics/sector_funds_flow?n=3\"\n```\n\n### Get User Profile\n\n```sh\ncurl http://localhost:8080/api/v1/user/profile\n```\n\n### Update User Profile\n\n```sh\ncurl -X PUT http://localhost:8080/api/v1/user/profile \\\n    -H \"Content-Type: application/json\" \\\n    -d '{\n        \"username\": \"John Doe\",\n        \"email\": \"john@example.com\",\n        \"avatar\": \"https://example.com/avatar.png\"\n    }'\n```\n\n### Health Check\n\n```sh\ncurl http://localhost:8080/healthz\n```\n\n## Historical Market Data\n\nThe application now supports persisting historical market data to reduce reliance on external APIs and improve performance.\n\n### Features\n\n- **Data Persistence**: Configure tickers to sync and store data locally.\n- **Backfilling**: Automatically fetching up to 30 years of historical data.\n- **Visualization**: View historical price trends with paginated tables and date filtering.\n\n### How Backfilling Works\n\nThe application uses an efficient incremental sync strategy to maintain the historical database:\n\n1.  **Initial Sync**: When a ticker is first added, the system uses the configured `lookback_years` (defaulting to 5 years) to calculate the starting point and retrieves the full history up to the current date.\n2.  **Incremental Updates**: For subsequent syncs, the system retrieves only the \"delta\"—the data between the `last_sync` timestamp and the current time. This minimizes API requests and processing load.\n3.  **Data Filtering**: During ingestion, records with missing or zero prices are automatically excluded to ensure high data quality.\n4.  **Full Cleanup**: Deleting a configuration removes both the metadata and all associated historical price records from the LevelDB persistence.\n\n### API Endpoints\n\n- `GET /api/v1/historical/config`: List all configured assets.\n- `POST /api/v1/historical/config`: Add or update an asset configuration.\n- `DELETE /api/v1/historical/config/{ticker}`: Remove an asset configuration.\n- `POST /api/v1/historical/sync`: Trigger an immediate sync for a ticker.\n- `GET /api/v1/historical/data/{ticker}`: Retrieve historical data with pagination and date filtering using `from` and `to` query parameters (Unix timestamp).\n\n## Configurations\n\nSample configurations\n\n```yaml\nverboseLogging: true\nlogFilePath: ./portfolio-manager.log\nhost: localhost\nport: 8080\nbaseCcy: SGD\ndb: leveldb\ndbPath: ./portfolio-manager.db\nrefDataSeedPath: \"./seed/refdata.yaml\"\nmcp:\n  enabled: false # Enable/disable MCP server\n  port: 8081 # MCP server port\ndividends:\n  divWitholdingTaxSG: 0\n  divWitholdingTaxUS: 0.3\n  divWitholdingTaxHK: 0\n  divWitholdingTaxIE: 0.15\nmetrics:\n  schedule: \"10 17 * * 1-5\" # daily at 5:10pm, Mon-Fri (excludes weekends)\nmarketData:\n  rateLimitMs: 750 # Minimum milliseconds between Yahoo Finance requests (increased from default 500ms to avoid rate limiting)\nanalytics:\n  geminiApiKey: \"\" # Get from Google AI Studio - required for analytics features\n  geminiModel: \"gemini-2.0-flash-lite\" # Gemini model to use (docs: https://ai.google.dev/gemini-api/docs/models)\n  dataDir: \"./data\" # Directory to store downloaded SGX reports\n  schedule: \"0 17 * * 1\" # Weekly on Mondays at 5:00 PM\n```\n\n## Roadmap\n\nSee https://github.com/rodionlim/portfolio-manager-go/milestones\n\n## Contributing\n\nContributions are always welcome! If you have any suggestions or find a bug, please open an issue or submit a pull request.\n\n## License\n\nThis project is licensed under the MIT License - see the [license](./LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frodionlim%2Fportfolio-manager-go","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frodionlim%2Fportfolio-manager-go","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frodionlim%2Fportfolio-manager-go/lists"}