{"id":"8f2f9eed-0e7d-461a-9ab7-928f375cd710","entityType":"agent","slug":"clawhub-patello-avanza-investment-tracker","name":"avanza-investment-tracker","canonicalUrl":"https://www.xpersona.co/agent/clawhub-patello-avanza-investment-tracker","canonicalPath":"/agent/clawhub-patello-avanza-investment-tracker","generatedAt":"2026-10-09T23:42:09.236Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-09T10:52:41.344Z","emptyReason":null},"description":"Process Avanza CSV exports, calculate TWRR/Modified Dietz returns, and track portfolio performance. Use when importing stock transactions, calculating investment returns, or managing portfolio data. Reads/writes a local SQLite database, and (for live prices and risk metrics) makes outbound HTTPS requests to Avanza, Riksbanken, and Yahoo Finance. Includes irreversible deletion commands (reset --hard, delete-tx, account delete) — see Security and Data Access in SKILL.md/README. Skill: avanza-investment-tracker Owner: patello Summary: Process Avanza CSV exports, calculate TWRR/Modified Dietz returns, and track portfolio performance. Use when importing stock transactions, calculating investment returns, or managing portfolio data. Reads/writes a local SQLite database, and (for live prices and risk metrics) makes outbound HTTPS requests to Avanza, Riksbanken, and Yahoo Finance. Includes irreve","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 2.9K downloads reported by the source. Last updated 10/9/2026.","installCommand":"clawhub skill install s178t29f61bmma8whfnmacavhx83eknq:avanza-investment-tracker","sourceUrl":"https://clawhub.ai/patello/avanza-investment-tracker","homepage":"https://clawhub.ai/patello/skills/avanza-investment-tracker","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/patello/avanza-investment-tracker","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/patello/skills/avanza-investment-tracker","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":44,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Process Avanza CSV exports, calculate TWRR/Modified Dietz returns, and track portfolio performance. Use when importing stock transactions, calculating investmen"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-09T10:52:41.344Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T10:52:41.344Z","emptyReason":null},"stars":null,"forks":null,"downloads":2896,"packageName":null,"latestVersion":"2.14.1","tractionLabel":"2.9K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T10:52:41.344Z","emptyReason":null},"lastUpdatedAt":"2026-10-09T10:52:41.344Z","lastCrawledAt":"2026-10-09T10:52:41.344Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-10T10:52:41.344Z","lastVerifiedAt":null,"highlights":[{"version":"2.14.1","createdAt":"2026-08-31T12:17:42.828Z","changelog":"- Hotfix: pin requests>=2.33.0 (CVE-2026-25645 in allowed 2.32.4); declare filesystem/network permissions in SKILL.md metadata (audit findings)","fileCount":59,"zipByteSize":163675},{"version":"2.14.0","createdAt":"2026-08-31T11:30:35.413Z","changelog":"- docs + safeguards: address ClawHub security audit findings (#86)","fileCount":59,"zipByteSize":163411},{"version":"2.13.0","createdAt":"2026-08-25T21:15:14.751Z","changelog":"- Fix handle_dividend to credit capital from SEK total, not native-currency price (issue #85) (#85) - Fix currency mixup in dividend routing to virtual holders (issue #83) (#84)","fileCount":59,"zipByteSize":159980},{"version":"2.12.2","createdAt":"2026-07-25T18:31:43.140Z","changelog":"- Fix native-currency prices to convert to SEK (#81) - Fix parent-to-virtual transfers to shift deposit column (#82)","fileCount":59,"zipByteSize":158972},{"version":"2.12.1","createdAt":"2026-07-24T11:56:35.404Z","changelog":"- Add delete-tx command for targeted transaction deletion (#80) - Defer unsettled (pending nota) trades at import (#78) - Trim asset names at import and normalize existing (#79)","fileCount":58,"zipByteSize":152154},{"version":"2.12.0","createdAt":"2026-07-23T13:13:52.636Z","changelog":"- Fix stale 'virtual transfer-cash' reference in warning message - Add account delete command for clean virtual teardown (#74) - Add import --allocate-virtual, partial transfer fix, and allocate undo (#74) - Rename 'virtual' command to 'account' and fold in nicknames (#74) - Remove grafana/ dashboard artifacts (#74) - Add report command and Grafana dashboard for virtual portfolios (#74 phase 3) - Make SQL views virtual-portfolio-aware and add a rollup view (#74) - Auto-distribute imported dividends to the accounts holding the shares (#74) - Auto-route imported sells to the account holding the shares (#74) - Add virtual list and virtual close commands (#74 phase 2) - Add virtual portfolios via account reassignment (#74)","fileCount":55,"zipByteSize":143697},{"version":"2.11.1","createdAt":"2026-07-22T18:20:04.197Z","changelog":"- Use deposit-weighted start dates for APY annualization (fixes #77)","fileCount":54,"zipByteSize":115506},{"version":"2.11.0","createdAt":"2026-07-22T17:27:07.625Z","changelog":"- Merge feature/portfolio-risk-metrics - Compound monthly returns for calendar-year returns - Deduplicate HistoricalTracker and clean up risk_calculator - Update pre-first-price valuation test to expect flat-carry - Use actual transaction dates for cohort withdrawal timing - Fix double-counted withdrawals in cohort cash flows - Guard against negative Modified Dietz denominators - Fix cohort cash flow scoping for withdrawals - Address root causes of unreliable risk metrics - Inline per-cohort risk metrics and fix impossible drawdown/stddev values - Update expected Sharpe ratios in tests to align with more accurate daily-dated cash flows - Merge branch 'master' into feature/portfolio-risk-metrics - Fix cash flow query regression for full portfolio in RiskCalculator - Remove redundant Annualized Return from printed risk metrics table - Implement portfolio and cohort-specific risk metrics calculation and CLI flags","fileCount":54,"zipByteSize":112950}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s178t29f61bmma8whfnmacavhx83eknq:avanza-investment-tracker","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-patello-avanza-investment-tracker/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-patello-avanza-investment-tracker/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-patello-avanza-investment-tracker/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-patello-avanza-investment-tracker/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-patello-avanza-investment-tracker/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-patello-avanza-investment-tracker/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-09T23:42:09.231Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-patello-avanza-investment-tracker/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-patello-avanza-investment-tracker/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-patello-avanza-investment-tracker/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-patello-avanza-investment-tracker/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-09T10:52:41.344Z","emptyReason":null},"readme":"Skill: avanza-investment-tracker\n\nOwner: patello\n\nSummary: Process Avanza CSV exports, calculate TWRR/Modified Dietz returns, and track portfolio performance. Use when importing stock transactions, calculating investment returns, or managing portfolio data. Reads/writes a local SQLite database, and (for live prices and risk metrics) makes outbound HTTPS requests to Avanza, Riksbanken, and Yahoo Finance. Includes irreversible deletion commands (reset --hard, delete-tx, account delete) — see Security and Data Access in SKILL.md/README.\n\nTags: latest:2.14.1\n\nVersion history:\n\nv2.14.1 | 2026-08-31T12:17:42.828Z | user\n\n- Hotfix: pin requests>=2.33.0 (CVE-2026-25645 in allowed 2.32.4); declare filesystem/network permissions in SKILL.md metadata (audit findings)\n\nv2.14.0 | 2026-08-31T11:30:35.413Z | user\n\n- docs + safeguards: address ClawHub security audit findings (#86)\n\nv2.13.0 | 2026-08-25T21:15:14.751Z | user\n\n- Fix handle_dividend to credit capital from SEK total, not native-currency price (issue #85) (#85)\n- Fix currency mixup in dividend routing to virtual holders (issue #83) (#84)\n\nv2.12.2 | 2026-07-25T18:31:43.140Z | user\n\n- Fix native-currency prices to convert to SEK (#81)\n- Fix parent-to-virtual transfers to shift deposit column (#82)\n\nv2.12.1 | 2026-07-24T11:56:35.404Z | user\n\n- Add delete-tx command for targeted transaction deletion (#80)\n- Defer unsettled (pending nota) trades at import (#78)\n- Trim asset names at import and normalize existing (#79)\n\nv2.12.0 | 2026-07-23T13:13:52.636Z | user\n\n- Fix stale 'virtual transfer-cash' reference in warning message\n- Add account delete command for clean virtual teardown (#74)\n- Add import --allocate-virtual, partial transfer fix, and allocate undo (#74)\n- Rename 'virtual' command to 'account' and fold in nicknames (#74)\n- Remove grafana/ dashboard artifacts (#74)\n- Add report command and Grafana dashboard for virtual portfolios (#74 phase 3)\n- Make SQL views virtual-portfolio-aware and add a rollup view (#74)\n- Auto-distribute imported dividends to the accounts holding the shares (#74)\n- Auto-route imported sells to the account holding the shares (#74)\n- Add virtual list and virtual close commands (#74 phase 2)\n- Add virtual portfolios via account reassignment (#74)\n\nv2.11.1 | 2026-07-22T18:20:04.197Z | user\n\n- Use deposit-weighted start dates for APY annualization (fixes #77)\n\nv2.11.0 | 2026-07-22T17:27:07.625Z | user\n\n- Merge feature/portfolio-risk-metrics\n- Compound monthly returns for calendar-year returns\n- Deduplicate HistoricalTracker and clean up risk_calculator\n- Update pre-first-price valuation test to expect flat-carry\n- Use actual transaction dates for cohort withdrawal timing\n- Fix double-counted withdrawals in cohort cash flows\n- Guard against negative Modified Dietz denominators\n- Fix cohort cash flow scoping for withdrawals\n- Address root causes of unreliable risk metrics\n- Inline per-cohort risk metrics and fix impossible drawdown/stddev values\n- Update expected Sharpe ratios in tests to align with more accurate daily-dated cash flows\n- Merge branch 'master' into feature/portfolio-risk-metrics\n- Fix cash flow query regression for full portfolio in RiskCalculator\n- Remove redundant Annualized Return from printed risk metrics table\n- Implement portfolio and cohort-specific risk metrics calculation and CLI flags\n\nv2.10.0 | 2026-07-19T06:40:33.710Z | user\n\n- Fix APY calculation discrepancy in multi-account stats (fixes #73)\n\nv2.9.0 | 2026-07-18T19:10:32.098Z | user\n\n- Implement linear price interpolation layer for sparse historical data\n\nv2.8.0 | 2026-07-17T10:25:10.079Z | user\n\n- Release v2.8.0: Correct version bump to minor release for shorthand cohort filtering and period override\n\nv2.7.1 | 2026-07-17T10:21:56.204Z | user\n\n- Release v2.7.1: Bump version for shorthand cohort filtering and period override\n- feat: add --cohort shorthand filtering flag to stats/portfolio (#69)\n- docs: update README with double-snapshot withdrawal and label explanations\n- Use date-based comparison to determine Start Value vs Deposited cohort labels\n- Display Start Value instead of Deposited dynamically for older cohorts (Issue #67)\n- Simplify date flags: map --from/--to exclusively to valuation period (Issue #67)\n- Fix issues #65 and #66: resolve start_date shadowing and restrict price staleness check to held assets\n\nv2.7.0 | 2026-07-16T13:55:32.753Z | user\n\n- Release v2.7.0: Unify stats/portfolio command calculations, support double snapshot valuation, and integrate inline Node.js sync in workflows\n- Integrate inline Node.js documentation sync into GitHub Actions clawhub-publish workflow\n- Support printing start_val, total_with, and net_transfers in CLI summaries to balance accounting equations\n- Unify stats and portfolio command calculations and introduce precise cohort and value date filtering\n- Implement price data staleness warnings for stats and portfolio commands\n\nv2.6.0 | 2026-07-15T21:26:37.022Z | user\n\n- Release v2.6.0: Implement CSV export subcommand and resolve nicknames case-insensitively across commands\n- Resolve account display names case-insensitively across commands (Issue #60)\n- Implement portfolio holdings table and JSON output format (Issue #58)\n\nv2.5.0 | 2026-07-15T20:40:45.318Z | user\n\n- Simplify SKILL.md and document settings commands\n- Fix Issue #59 and add historical price lookup for zero-priced transactions\n\nv2.4.1 | 2026-07-15T10:47:18.750Z | user\n\n- Update documentation and skill references for portfolio APY, tag as v2.4.1\n\nv2.4.0 | 2026-07-13T19:19:36.988Z | user\n\n- Implement account-level Rate of Return (MWRR/TWRR) and portfolio APY columns\n\nv2.3.0 | 2026-07-05T10:40:59.918Z | user\n\n- Bump version to 2.3.0\n- Refactor net deposits query using new v_external_capital_flows SQL view\n- Rename Return (SEK) column to Return\n- Isolate pure market return in portfolio comparison and add Total Change column\n- Implement --format json for stats and accounts subcommands\n- Document portfolio command --account and --format arguments\n- Add portfolio subcommand for snapshotting and period-to-period comparison\n\nv2.2.1 | 2026-07-05T06:27:33.650Z | user\n\n- Update documentation in SKILL.md for --update-all and date filters\n- Restrict price updates to held assets by default and add --update-all option\n\nv2.2.0 | 2026-07-04T20:49:46.763Z | user\n\n- Implement stats --as-of historical query and date filtering\n- Fetch accurate NAV, stock, and certificate closing dates from Avanza endpoints\n- Add unit test for historical price resolution and superseding logic\n\nv2.1.0 | 2026-07-04T19:02:51.079Z | user\n\n- Configure clawhub-publish workflow to dynamically generate changelogs and inject tag versions\n- Implement historical asset price tracking table and update parser/fetcher\n- Add transaction date range to status command (#56)\n- fix: pass required version, slug, and changelog parameters to clawhub publish\n- Merge branch 'feature/native-openclaw-skill' into master (automate publishing workflow)\n- feat: automate clawhub publishing on push to master\n- chore: bump version to 2.0.3\n- feat: restructure codebase to native OpenClaw skill format\n- chore: bump version to 2.0.3\n- feat: restructure codebase to native OpenClaw skill format\n- fix: add group-level deduplication for split/combined transactions\n- test: fix test_asset_deposit_issue mock schema, database connection, and date assertions\n- fix: add encoding fallback (utf-8/cp1252) when opening CSV and JSON files\n- Merge pull request #49 from patello/fix/internal-transfer-phantom-gains\n- Remove unrelated docs/SKILL.md from PR\n- docs: add publishing and PII guidelines to SKILL.md\n- Fix phantom gains/losses from internal transfers\n- Add sessionKey to webhook payload for session grouping\n- Add PR review and merge events to webhook trigger\n- Merge pull request #47 from patello/feature/account-nicknames\n- docs: add account nicknames to README\n- Refactor: Use accounts table instead of JSON metadata for nicknames\n- Add account nicknames feature (issue #46)\n- Merge pull request #45 from patello/feature/add-transaction-types-aliases\n- Add test coverage for new transaction types\n- Add support for additional transaction types and standalone incoming transfers\n- Merge pull request #43 from patello/feature/issue-42-default-stats-period\n- docs: add default-stats-period to Usage in README.md\n- Add setting for default stats cohort period\n- Modified workflow headers\n- Adding OpenClaw integration for testing.\n- Merge pull request #39 from patello/fix-asset-deposit-cost-basis\n- Fix: JSON encoding in special_cases_template.json for Swedish characters\n- Docs: Add manual cost basis example to special cases template\n- Fix: retroactive deposit calculation when cost basis is zero for asset deposits\n- Fix missed renames on issue-34: rename month_stats to cohort_stats\n- Merge pull request #38 from patello/issue-34-rename-month-to-cohort\n- Refactor ambiguous 'month' naming to 'cohort' terminology\n- Merge pull request #36 from patello/twrr-active-base\n- Revert \"Fix TWRR APY end date for closed positions\"\n- Remove schema migration code as requested\n- Fix TWRR APY end date for closed positions\n- feat: snapshot TWRR return on position closure (closed_return)\n- feat: add --apy-mode flag (modified-dietz default) and fix 0.0% APY for closed positions\n- Implement TWRR active_base for APY calculation\n- Update asset prices during transaction parsing\n- fix: restore allocate_to_month cutoff rule accidentally removed in #35\n- Merge pull request #35 from patello/update-asset-prices-during-parsing\n- Update asset prices during transaction parsing\n- Merge pull request #33 from patello/modified-dietz-apy\n- fix(stats): revert per-cohort APY weighting to resolve yearly distortion\n- fix: correct Modified Dietz APY calculation\n- feat: implement Modified Dietz Method for APY calculation\n- feat: aggregate cohort cash flows by transaction month\n- test: mark Modified Dietz test as xfail (implementation deferred)\n- test: add scenario for Modified Dietz APY calculation\n- feat: implement Simple Dietz Method for APY approximation (#27)\n- Merge pull request #30 from patello/per-account-architecture\n- fix: aggregate all monthly cohorts for yearly statistics\n- fix: calculate account summaries across all cohorts\n- WIP: replacing the SQL GROUP BY aggregation with a Python-side iteration that tracks and forward-fills the latest known accumulated states for each requested account as it chronologically parses the periods.\n- Implement per-account statistics architecture\n- Update test variable names and example values\n- Merge pull request #26 from patello/per-account-stats-improvements\n- Format month stats as 'Jan 2026' instead of '2026-01-31'\n- Fix date formatting for month stats and clarify code removal\n- Fix year formatting consistency and APY calculation for filtered accounts\n- Disable accumulated stats for filtered accounts\n- Refactor based on feedback\n- Implement per-account stats improvements\n- Merge pull request #25 from patello/per-account-asset-tracking\n- Replace asset limitation tests with tracking tests\n- Mark test_month_assets_missing_account_column as xfail\n- Revert \"Update test file to reflect per-account asset tracking fixes\"\n- Update test file to reflect per-account asset tracking fixes\n- Add account column to month_assets table and update transaction handlers\n- Add test file demonstrating per-account asset tracking limitations\n- Merge pull request #22 from patello/cli-streamlining\n- Fix price update bug: use force=True when updating prices from stats command\n- Remove legacy CLI commands as part of CLI streamlining\n- Add missing fixes and documentation for CLI streamlining\n- Implement streamlined CLI with smart updates and state tracking\n- Merge pull request #18 from patello/per-account-tracking\n- Change data_parser logging from DEBUG to INFO level\n- Revert APY calculation fix - not related to per-account tracking\n- Update test data with realistic descriptions and add multiple transfers test\n- Remove xfail markers from tests that now pass with per-account tracking\n- Fix transfer attribution: process internal transfers in pairs, preserve FIFO month allocation\n- Add attribution tests for internal transfers (single and two account scenarios)\n- Add deferral tests for internal transfers (ordered and out-of-order)\n- Defers transfers out until there is enough capital\n- Remove transaction type reordering from all SQL queries\n- Rename month_account_data to month_data; per-account is now the only mode\n- Fix per-account mode: handle internal transfers and transaction ordering\n- Merge pull request #14 from patello/fix-python312-warnings\n- Fix Python 3.12 compatibility warnings\n- Merge pull request #13 from patello/fix-interest-remaining-amount\n- Fix interest remaining_amount tracking bug\n- Merge pull request #11 from patello/documentation-clarify-value\n- Clarify statistics output in README\n- Merge pull request #7 from patello/handle-fraction-writeoffs\n- Add generic special_cases_template.json with examples for handling Byte transactions and fund renames\n- Fix Byte transactions and handle missing prices\n- Merge pull request #6 from patello/handle-fraction-writeoffs\n- Add test case for Utbokning fraktioner (fraction write-off)\n- Merge pull request #5 from patello/cli-interface\n- Handle fraction write-offs (Utbokning fraktioner) by ignoring them\n- Fix parse_data in run-all: handle missing process attribute\n- Add unified CLI interface with subcommands\n- Merge pull request #4 from patello/api-endpoint-update\n- Merge pull request #3 from patello/csv-format-support\n- Update README: fix calculate_stats.py filename and clarify API usage\n- Remove slash-stripping logic for asset names\n- Update API URL to new endpoint\n- Add support for new Avanza CSV export format\n- Add pytest configuration for network tests\n- Add pythonpath to pytest.ini\n- Removes tracking of vscode files.\n- Update testing configuration in settings.json\n- Remove special_cases parameter from add_data since it is no longer used.\n- Delete clear_parsed_data.py and add test_data_parser__reset test case\n- Added a function to handle interest rates, added tests for interest rates and fees\n- Add method to reset processed transactions and related tables\n- Removed uneccesary creation of a new DataParser object\n- Refactor add_data into DataParser class\n- Add function to reset all tables\n- Store list of tables in database handler\n- Adds documentation for the special cases class\n- Updated database tests with new table length\n- Add licence file\n- Add readme\n- Fixed comments for get_stats functions and added complete stat printout\n- Only update prices if they are old or if force = true\n- Integrated price update in calculate_stats\n- Refactor calculate_stats into a class\n- Update average price and average purchase price in month_assets table when dping asset transfer\n- Add reset_table method to DatabaseHandler class\n- Add asset deposit handling to data parser\n- Adds docsstrings and type annotiations\n- Add test that data parsing works when data is reordered. Add comments to explain how cursor is reset to rerun lines\n- Fixed bug in dataparser that made it unable to handle listing changes\n- Fixed error introduced in test when adding header row\n- Adds header rows to datasets\n- Removed line assigning data_cur to transaction_cur to prevent shared object reference bug\n- Add private test folder to .gitignore\n- Fixed issue where SpecialCases were handling datetime while csv was reading datetime dates\n- Moved test data to a dedicated folder\n- Added tests for handling different accounts\n- Add test cases for DataParser class\n- Made data parser roll back changes on error\n- Add new functions to retrieve and single stats from database\n- Add test for data parser functionality\n- Fixed issue where parsed data was not commited\n- Add processed transactions count to database stats\n- Give more information when giving an AssetDeficit error\n- Refactor data_parser code into a class\n- Remove unnecessary database connection code\n- Add get_cursor() method to DatabaseHandler class\n- Created test to check that data is correctly added to db\n- Refactored add_data into a class and added tests\n- Update database connection settings\n- Add database handler and test cases\n- Refactor SpecialCases class to read special cases from a JSON file during intialization\n- Renamed files with proper convention\n- Add data parsing functionality and special cases handling\n- Add test cases for dataparsing\n- Add requests to requirements.txt\n- Add support for handling \"Preliminärskatt\" fees in dataparser.py\n- Makes it possible to set a cutoff date for when deposits should be allocated to previous months\n- Adds function to clear data and set transactions to unhandled\n- Adds \"Övrigt\" as a transaction type for listing change\n- Adds get function for bar chart\n- Use kwarg to pass arguments to get functions\n- Adds get functions for plotting graphs\n- Moves get functions out of print\n- Sets months and year to current day for current year/month\n- Adds extra data to accumulation statis\n- Splits stat calculation and printing into different files\n- Updates month_info to use sql\n- Changes updateprices script to use sql\n- Removes unsued dependencies\n- Removed lines used for debugging\n- Add calculation on average priceses and sold and purchased assets\n- Finalized prototype of sql dataparser\n- WIP\n- Adds funtion to print accumulated deposit and profit/loss\n- Adds APY calculations to monthinfo\n- Adds yearly aggregate to month info\n- Structures monthinfo script into function\n- Adds month info script that prints gains for a given month\n- Makes data parser save month info file\n- Adds price update function \"updateprices.py\"\n- Makes data parser store asset data in a file\n- Create summary of active assets\n- dataparser creates summary of month deposits and withdrawals\n- Adds handling for akitefondkonto transactions\n- Adds deferral logic for when a deposit is registered after purchase\n- Dataparser oldest available explicitly returns if there is a deficit\n- Broke out transaction handling into separate functions\n- Removes courtage from purchase amount since this was already included\n- Adds handling for withdrawal and keeping track of deposits\n- Fixes listing change handling for dataparser\n- Fixes handling of tax events for dataparser\n- Dataparser handles dividends\n- Prototype that handels deposits, purchase and sale\n- Initial commit\n\nv2.0.3 | 2026-06-10T10:45:24.494Z | user\n\nAutomated release of version 2.0.3\n\nv2.0.2 | 2026-04-14T20:29:10.977Z | user\n\nFix: ensure transfer_net code included in published package\n\nv2.0.1 | 2026-04-14T20:27:57.502Z | user\n\nRepublish: ensure transfer_net code is included in package\n\nv2.0.0 | 2026-04-14T20:04:43.635Z | auto\n\nVersion 2.0.0\n\n- No file changes detected in this release.\n- Skill functionality, usage, and documentation remain unchanged.\n\nv1.0.1 | 2026-04-06T13:16:36.808Z | auto\n\n- Added _meta.json file for improved metadata management.\n- Updated scripts (calculate_stats.py, cli.py, database_handler.py) with minor changes and adjustments.\n- Removed version field from SKILL.md and made other editorial updates.\n- No breaking changes; core functionality remains the same.\n\nv1.0.0 | 2026-04-06T12:18:27.225Z | user\n\nNickname support, versioning, cleanup\n\nv1.2.3 | 2026-04-06T11:14:25.713Z | auto\n\n- Internal structure updates: removed unused _meta.json metadata file.\n- scripts/calculate_stats.py, scripts/cli.py, and scripts/database_handler.py updated, likely minor code and maintenance improvements.\n- No changes to user-facing commands or documentation.\n\nv1.2.2 | 2026-04-06T11:11:34.076Z | auto\n\nNo functional or documentation changes; only metadata updated.\n\n- Internal metadata file (_meta.json) changed.\n- No user-facing code or documentation modifications.\n\nv1.2.1 | 2026-04-06T11:09:27.587Z | user\n\nAdded account nickname feature from PR #47\n\nv1.2.0 | 2026-04-06T11:07:38.208Z | auto\n\nNo major changes in this release.\n\n- Internal metadata updated in _meta.json.\n- No changes to functionality or user-facing documentation.\n\nv1.1.1 | 2026-04-06T09:48:29.178Z | user\n\nDocumentation update\n\nv1.1.0 | 2026-04-06T09:42:15.231Z | user\n\nDocumentation cleanup\n\nv0.1.0 | 2026-04-06T09:38:31.699Z | user\n\nDocumentation update with cleaner example placeholders\n\nv0.0.3 | 2026-04-06T09:29:38.794Z | user\n\nAccount nicknames feature (PR #47): set/remove/list account display names. Updated all scripts from main.\n\nv0.0.2 | 2026-03-30T09:22:33.676Z | user\n\nSynced with master branch (PR #45: Autogiroinsättning, Avkastningsskatt, Uttag av riskkostnad handlers)\n\nv0.0.1 | 2026-03-28T22:01:50.320Z | user\n\nInitial release of avanza-investment-tracker.\n\n- Import and parse Avanza CSV transaction exports.\n- Calculate portfolio performance using TWRR and Modified Dietz methods.\n- Track holdings and transactions via a local SQLite database.\n- Handle corporate actions with a customizable special cases config.\n- CLI commands for importing data, updating prices, viewing stats, and account summaries.\n\nArchive index:\n\nArchive v2.14.1: 59 files, 163675 bytes\n\nFiles: _meta.json (145b), assets/special_cases_template.json (1911b), changelog.txt (144b), LICENSE (1071b), pytest.ini (219b), README.md (32688b), references/troubleshooting.md (234b), references/workflows.md (638b), requirements.txt (17b), scripts/calculate_stats.py (72055b), scripts/cli.py (142634b), scripts/data_parser.py (65416b), scripts/database_handler.py (36743b), scripts/risk_calculator.py (29854b), skill-card.md (2546b), SKILL.md (10121b), test/conftest.py (1091b), test/data/asset_deposit.csv (311b), test/data/fraction_writeoff.csv (352b), test/data/interest_fees.csv (601b), test/data/listing_change.csv (610b), test/data/multiple_transfers_same_day.csv (565b), test/data/new_format_data.csv (537b), test/data/new_transaction_types.csv (571b), test/data/reordered_data.csv (716b), test/data/small_data_diff_accounts.csv (1258b), test/data/small_data_plus.csv (1251b), test/data/small_data_wrong_accounts.csv (1258b), test/data/small_data.csv (716b), test/data/special_cases_test.json (1024b), test/data/transfer_attribution_single_account.csv (359b), test/data/transfer_attribution_two_accounts.csv (578b), test/data/transfer_deferral.csv (663b), test/data/transfer_proper_order.csv (663b), test/test_account_apy.py (11114b), test/test_accounts.py (57947b), test/test_add_data.py (5128b), test/test_asset_deposit_issue.py (4602b), test/test_database_handler.py (4623b), test/test_delete_tx.py (7139b), test/test_export.py (4672b), test/test_fx_conversion.py (16660b), test/test_historical_prices.py (22477b), test/test_import_normalization.py (3047b), test/test_import_unsettled.py (2616b), test/test_modified_dietz.py (3608b), test/test_parse_data.py (8129b), test/test_parse_special_cases.py (2042b), test/test_per_account_asset_tracking.py (11926b), test/test_portfolio_holdings.py (6187b), test/test_price_interpolation.py (6143b), test/test_price_staleness.py (5412b), test/test_price_updates.py (2398b), test/test_process_data_attribution.py (4242b), test/test_process_data_deferral.py (3929b), test/test_retroactive_updates.py (4460b), test/test_risk_metrics.py (11534b), test/test_stats_positions.py (13316b), test/test_twrr.py (5026b)\n\nFile v2.14.1:SKILL.md\n\n---\nname: avanza-investment-tracker\ndescription: \"Process Avanza CSV exports, calculate TWRR/Modified Dietz returns, and track portfolio performance. Use when importing stock transactions, calculating investment returns, or managing portfolio data. Reads/writes a local SQLite database, and (for live prices and risk metrics) makes outbound HTTPS requests to Avanza, Riksbanken, and Yahoo Finance. Includes irreversible deletion commands (reset --hard, delete-tx, account delete) — see Security and Data Access in SKILL.md/README.\"\nmetadata:\n  openclaw:\n    requires:\n      bins:\n        - python3\n    permissions:\n      filesystem:\n        - \"read/write: user-specified local SQLite database (--database path); no other files accessed\"\n      network:\n        - \"https://www.avanza.se (price/FX/chart data for held assets; optional, disable with --update-prices never)\"\n        - \"https://api.riksbank.se (reference rates for risk metrics; optional)\"\n        - \"https://query1.finance.yahoo.com (benchmark index prices for beta/correlation; optional)\"\n---\n\n# Avanza Investment Tracker\n\nParse transaction CSVs and compute portfolio performance metrics.\n\n## Security and Data Access\n\nBe aware of what this skill does before running it:\n\n- **Local database writes:** imports, price updates, and portfolio management read and write a local SQLite database.\n- **Network access (optional but on by default):** live price/FX lookups contact Avanza's public API with the asset names in your portfolio; risk metrics (`--risk`, `--beta`) may also contact the Riksbanken API and Yahoo Finance (benchmark ticker + date range). Use `--update-prices never` to stay fully offline.\n- **Irreversible deletions:** `reset --hard`, `delete-tx`, `account allocate --undo`, and `account delete` permanently remove transactions and rebuild derived tables. There is no built-in undo. Back up your database first (e.g. `cp` or git), and prefer `delete-tx --dry-run` to preview. Avoid broad selectors like `delete-tx --since` unless you are certain of the blast radius.\n\n## Quick Start\n\nRun commands from your workspace root, specifying the paths to your database and CSV:\n\n```bash\n# 1. Import new transactions\npython path/to/cli.py --database data/asset_data.db import path/to/transactions.csv\n\n# 2. Update price cache and show statistics\npython path/to/cli.py --database data/asset_data.db stats --update-prices auto\n\n# 3. View portfolio allocation and APY\npython path/to/cli.py --database data/asset_data.db portfolio --account default\n```\n\n## Data Storage Pattern\n\n**User data lives OUTSIDE the skill directory.** Recommended structure:\n\n```\nworkspace-finance/\n├── skills/avanza-investment-tracker/   # Portable skill logic\n│   ├── SKILL.md\n│   ├── scripts/\n│   └── assets/\n└── data/avanza/                        # Private portfolio data\n    ├── transactions.csv\n    ├── special_cases.json\n    └── asset_data.db\n```\n\n## CLI Reference\n\n| Command | Description |\n| :--- | :--- |\n| `python scripts/cli.py import FILE [--allocate-virtual] [--allow-unsettled]` | Import transaction entries from Avanza CSV (auto-allocate buys to virtuals; defers unsettled/pending-nota trades — see CSV Format Support) |\n| `python scripts/cli.py stats [OPTIONS]` | Calculate and display cohort performance statistics (TWRR, deposits) |\n| `python scripts/cli.py accounts [OPTIONS]` | Display summary of all accounts with asset values and cash |\n| `python scripts/cli.py portfolio [OPTIONS]` | Show portfolio holdings, market value, allocation %, and APY (alias to `stats --positions --summary`) |\n| `python scripts/cli.py status` | Display system status (transaction counts, price dates, date range) |\n| `python scripts/cli.py settings SUBCOMMAND` | Configure defaults and account nicknames |\n| `python scripts/cli.py reset [--hard] [--yes]` | Reset database state (`--hard` deletes data; default only marks unprocessed). `--hard` prompts for confirmation (or requires `--yes` non-interactively) and writes an automatic timestamped `.bak` backup first |\n| `python scripts/cli.py delete-tx [OPTIONS]` | Delete individual transaction(s) by `--tx-id`, `--date`+`--asset`, or `--since`, then rebuild derived tables (see below). Prompts for confirmation unless `--dry-run` or `--yes` is used; writes an automatic timestamped `.bak` backup first |\n| `python scripts/cli.py account SUBCOMMAND` | Manage accounts — virtual sub-portfolios (create/allocate/transfer/list/close/delete) and nicknames (see below) |\n| `python scripts/cli.py report [OPTIONS]` | Investment report with a virtual-portfolio section and a virtual-vs-parent-vs-benchmark comparison |\n\n### Deleting transactions\n\n> **Irreversible.** `delete-tx` permanently removes the matched transactions and rebuilds\n> derived tables — there is no undo. Back up the database first and use `--dry-run` to\n> preview, especially with broad selectors like `--since`.\n\n`delete-tx` removes specific real transactions and rebuilds the derived `assets` / cohort tables, so there is no need to `reset` the whole database after a bad import (e.g. a duplicate, or a row that slipped in before an unsettled trade was deferred). Targeting is mutually exclusive:\n\n- `delete-tx --tx-id ROWID` — most precise (use `status`/`export` to find the rowid).\n- `delete-tx --date YYYY-MM-DD --asset \"Name\" [--account ACCOUNT]` — the common surgical case.\n- `delete-tx --since YYYY-MM-DD [--account ACCOUNT]` — remove everything from a date onward (e.g. undo today's import).\n\n`--cascade` widens a `--date`+`--asset` match across the account family (parent + its virtuals) so a trade and its allocated split are removed together; `--dry-run` previews the deletion; `--yes` skips the confirmation prompt (required when running non-interactively, e.g. from an agent or script — the command refuses with exit code 1 otherwise). A timestamped `<db>.pre-delete-tx.<YYYYMMDD-HHMMSS>.bak` backup is written before any rows are deleted. When an allocated buy on a virtual is deleted, its orphaned funding `Intern överföring` transfer is removed automatically (mirroring `account allocate --undo`). After every deletion all transactions are reprocessed, so the `assets`/cohort tables always reflect the remaining transactions — never a half-deleted state.\n\n### Global Options\n- `--database PATH` (default: `data/asset_data.db`)\n- `--special-cases PATH` (default: `data/special_cases.json`)\n\n### Calculation & Output Options\n- `--account ACCOUNTS`: Limit to specific accounts (e.g. `12345,67890`, `default`, or `all`). Omitting the flag (default) shows **physical accounts only** (excludes virtual portfolios); pass `all` to include virtual portfolios in aggregates.\n- `--update-prices {auto,always,never}` (stats only): Controls when to fetch latest stock/fund prices from Avanza API\n- `--update-all` (stats only): Update prices for all assets in the database, held or not\n- `--as-of DATE`: View snapshot/stats as of a historical date (`YYYY-MM-DD`)\n- `--cohorts-start DATE --cohorts-end DATE`: Filter which deposit cohorts are displayed\n- `--cohort DATE`: Shorthand to filter by a single cohort month (`YYYY-MM`) or year (`YYYY`) (e.g. `--cohort 2024` groups yearly, `--cohort 2024-12` groups monthly)\n- `--from DATE --to DATE`: Set the performance valuation window (double snapshot)\n- `--positions`, `-p` (stats only): Show positions holdings breakdown under each cohort (or summary)\n- `--summary`, `-s` (stats only): Consolidate cohort statistics into a single overview block\n- `--apy-mode {mwrr,twrr}`: APY calculation method (`mwrr` uses Modified Dietz; `twrr` uses Time-Weighted)\n- `--format {table,json}`: Output formatting (default: `table`)\n- `--quiet`, `-q`: Suppress price data staleness warnings\n- `--no-interpolation`: Disable linear interpolation for sparse historical price data (falls back to nearest prior price, which may trigger staleness warnings)\n- `--risk`: Calculate and display portfolio-level risk metrics (Annualized Standard Deviation, Sharpe Ratio, Sortino Ratio, Maximum Drawdown with peak/trough calendar months)\n- `--beta [TICKER]`: Include the portfolio Beta calculation vs the specified benchmark (e.g. `^OMXSPI`, `ACWI`). Defaults to `^OMXSPI` if the flag is passed without a ticker value. Specifying `--beta` automatically enables risk metrics.\n\n### Guidelines: When to use what date boundaries\n1. **To see how cohorts from a certain period look today:**\n   Use `--cohorts-start YYYY-MM` / `--cohorts-end YYYY-MM`\n   *Example:* `python scripts/cli.py stats --cohorts-start 2024-01`\n2. **To see all cohorts' performance over a specific valuation window:**\n   Use `--from YYYY-MM` / `--to YYYY-MM` (or `--as-of YYYY-MM`)\n   *Example:* `python scripts/cli.py stats --from 2024-01 --to 2024-12`\n3. **To see only a single cohort month or year:**\n   Use `--cohort YYYY-MM` or `--cohort YYYY`\n   *Example:* `python scripts/cli.py stats --cohort 2024-12` (sets date range to `2024-12` and default grouping to monthly)\n   *Example:* `python scripts/cli.py stats --cohort 2024` (sets date range to `2024-01` to `2024-12` and default grouping to yearly)\n\n> [!NOTE]\n> In double-snapshot mode (`--from` / `--to`), the cohort-level output displays **`Start Value`** instead of **`Deposited`** for any cohorts created before the start date. Additionally, the **`Withdrawal`** line displays withdrawals made *specifically within the selected date range*, while withdrawals made prior to the start date are already accounted for in `Start Value`.\n\n### Settings Subcommands\n- `default-accounts ACCOUNTS`: Set default accounts (comma-separated list of IDs, or `all`)\n- `default-stats-period {month,year}`: Set default period for performance reports\n\n\n## Special Cases\n\nCorporate actions (splits, spin-offs, zero-priced deposits) can be overridden by copying the template and defining rules:\n```bash\ncp assets/special_cases_template.json ../data/avanza/special_cases.json\n```\n\n\n## See Also\n\n- **Detailed workflows**: [references/workflows.md](references/workflows.md)\n- **Troubleshooting guide**: [references/troubleshooting.md](references/troubleshooting.md)\n\nFile v2.14.1:README.md\n\n# Investment Tracker\n\n## Description\n\nThis project aims to create a tool for tracking stocks and investments on investment platforms. The original idea for this project was when I was about to buy a SteamDeck. Then I thought better of it and started to wonder how much the money would grow over time if I invested it instead. I realized that it would require me to keep track of the assets that I purchase a particular month, even if I sell them and buy new ones later on. Or if I get dividends and reinvest them.\n\nWith this project, I am able to parse data from my investment platform, keep latest asset values up to date and calculate relevant statistics.\n\nThis project is a work in progress and will be updated as I go along.\n\n## Table of Contents\n\n- [Features](#features)\n- [Installation](#installation)\n- [Usage](#usage) — includes [Network access and privacy](#network-access-and-privacy)\n- [CLI Reference](#cli-reference)\n- [Virtual Portfolios](#virtual-portfolios)\n- [Special Cases](#special-cases)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Features\n\n- Parse and store data from an investment platform.\n- Keep track of where money invested each month is moved and grown over time.\n- **Per-account statistics**: Calculate gains/losses for each account separately, then merge for combined views\n- Calculate statistics for months and years:\n    - Deposit\n    - Withdrawal\n    - Current Value\n    - Total Gain/Loss\n    - Realized Gain/Loss\n    - Unrealized Gain/Loss\n    - APY (Annual Percentage Yield)\n- Two viewing modes:\n    - **Period-specific**: Track performance of investments made in each month/year\n    - **Accumulated**: See total portfolio value over time with assets carried forward\n- **Account filtering**: View statistics for any combination of accounts with full accumulated history support\n- **System status**: Check database statistics, price freshness, and transaction date range\n- **Virtual portfolios**: Track sub-portfolios (e.g. strategy sleeves) separately with allocation, transfers, and virtual-vs-parent performance comparison\n- **Risk metrics**: Optional risk/beta calculations against a benchmark (fetches policy rates and benchmark prices from Riksbanken/Yahoo Finance)\n- **Reporting**: Investment report command with a virtual-portfolio section and benchmark comparison\n- **Safety rails**: Destructive commands (`reset --hard`, `delete-tx`, `account delete`) require confirmation and write an automatic timestamped `.bak` backup first\n\n## Installation\n\n1. Clone the repository.\n2. Install the dependencies with `pip install -r requirements.txt`.\n\n## Quick Start\n\n```bash\n# 1. Import your transaction data\npython cli.py import data/your_transactions.csv\n\n# 2. Get statistics with automatic price updates\npython cli.py stats --update-prices auto --period year --deposits all\n\n# 3. Check system status anytime\npython cli.py status\n\n# 4. (Optional) Set default accounts for filtering\npython cli.py settings default-accounts \"account1,savings_account\"\n\n# 5. (Optional) Set account nicknames for readability\npython cli.py account nickname 1234567 \"Savings\"\n\n# 6. View account summaries\npython cli.py accounts --update-prices auto\n```\n\n## Usage\n\n### Network access and privacy\n\nBy default this tool is not fully offline. Be aware of outbound requests:\n\n- **Avanza public API** — live price/FX lookups send the asset names and currency pairs from your portfolio (this is how `--update-prices auto` works). Use `--update-prices never` to keep it fully offline (cached prices only).\n- **Riksbanken API and Yahoo Finance** — only contacted for risk metrics (`--risk`, `--beta`) to fetch policy rates and benchmark prices; this sends the benchmark ticker and date range, not your holdings.\n\n### Command Line Interface (CLI)\n\nA unified CLI is available via `cli.py`. It provides subcommands for all major operations:\n\n#### Modern Workflow (Recommended)\n\n```bash\n# Import CSV data and process transactions in one atomic operation\npython cli.py import data/transactions.csv\n\n# Show statistics with smart updates (auto-updates prices if stale)\npython cli.py stats --update-prices auto --period year --deposits all\n\n# Check system status (transactions, prices, metadata)\npython cli.py status\n\n# Reset database state (mark all transactions as unprocessed)\npython cli.py reset\n\n# Hard reset (delete all transactions, stats, and prices while keeping configuration)\n# WARNING: irreversible — permanently deletes all financial history in the database. Back it up first.\npython cli.py reset --hard\n```\n\n> **Irreversible operations.** `reset --hard`, `delete-tx`, `account allocate --undo`, and\n> `account delete` permanently remove data and rebuild derived tables. There is no undo.\n> Back up the database before running them, and prefer `--dry-run` where available\n> (e.g. `delete-tx --dry-run`) to preview the blast radius. Avoid broad selectors like\n> `delete-tx --since` unless you are certain what they will remove.\n>\n> Since the audit-hardening update, `reset --hard`, `delete-tx`, and `account delete`\n> additionally: (1) ask for confirmation — interactively via a y/N prompt, and in\n> non-interactive shells (agents, cron, scripts) only run when `--yes` is passed —\n> and (2) automatically copy the database to a timestamped\n> `<db>.pre-<command>.<YYYYMMDD-HHMMSS>.bak` file before making any changes.\n> Backup files are never cleaned up automatically — delete them yourself once you are\n> satisfied the operation went well (and consider adding `*.bak` to your `.gitignore` if\n> the database lives in a git repo).\n\nAll commands accept optional `--database` and `--special-cases` arguments to override default paths:\n\n```bash\npython cli.py --database path/to/db.db --special-cases path/to/special.json import data.csv\n```\n\n#### Smart Update Features\n\nThe new `stats` command includes intelligent caching and update logic:\n\n- **Price freshness**: Prices are considered \"fresh\" if updated within 1 day\n- **Price interpolation**: Historical valuations automatically interpolate missing asset prices linearly between nearest known dates (e.g. from transaction purchases or API price updates).\n  - Use `--no-interpolation` to disable this behavior and fall back to the closest prior price.\n- **Auto-update**: `--update-prices auto` updates only if prices are stale\n- **Force update**: `--update-prices always` forces price refresh\n- **Skip update**: `--update-prices never` uses cached prices\n- **Stats caching**: Statistics are recalculated only when needed (new transactions or price updates)\n- **Default period**: You can set a default stats cohort period.\n  ```bash\n  # Set default stats period\n  python cli.py settings default-stats-period year\n\n  # Use the default stats period\n  python cli.py stats\n  ```\n\n\n#### Account Nicknames\n\nAssign human-readable names to account numbers for easier identification:\n\n```bash\n# Set a nickname for an account\npython cli.py account nickname 1234567 \"Savings\"\n\n# List all nicknames\npython cli.py account nickname --list\n\n# Remove a nickname\npython cli.py account nickname --remove 1234567\n```\n\nNicknames are stored in the database and displayed in the `accounts` command output alongside account numbers.\n\n#### Account Filtering\n\nThe CLI supports filtering statistics by account with the `--account` flag. Statistics are calculated per-account for accuracy, then merged when viewing multiple accounts:\n\n```bash\n# Show stats for all accounts (default)\npython cli.py stats --account all\n\n# Show stats using default accounts set via settings\npython cli.py stats --account default\n\n# Show stats for specific accounts (comma-separated)\npython cli.py stats --account \"account1,savings_account\"\n\n# Accumulated statistics now work with any account combination\npython cli.py stats --account \"account1\" --accumulated\n```\n\nSet default accounts for filtering:\n```bash\n# Set default accounts\npython cli.py settings default-accounts \"account1,savings_account\"\n\n# Reset to include all accounts\npython cli.py settings default-accounts all\n```\n\nView account summaries with cash and asset values:\n```bash\n# Show all accounts\npython cli.py accounts --update-prices auto\n\n# Filter accounts (same syntax as stats command)\npython cli.py accounts --account default\n\n# View portfolio snapshot for specific accounts\npython cli.py portfolio --account \"account1,savings_account\"\n\n# Compare portfolio values for a specific account over a period\npython cli.py portfolio --account \"account1\" --start-date 2026-06-29 --end-date 2026-07-06\n```\n\nOutput format for `accounts` command:\n```\nAccount                Cash (SEK) Assets (SEK)  Total (SEK)\n--------------------------------------------------------\naccount1                        0       100000       100000\nsavings_account             50000            0        50000\n--------------------------------------------------------\nTOTAL                       50000       100000       150000\n```\n\nOutput format for `portfolio` command (single account):\n```\nAccount account1 (Account Nickname)\nDeposits: 100,000 SEK\nWithdrawals: 0 SEK\nNet invested: 100,000 SEK\nCurrent value: 105,000 SEK\nTotal gain: +5,000 SEK (+5.0%)\nAPY: 10.2% (MWRR)\n\n  Holdings:\n    Fund Name              Market Value    Allocation\n    Asset A                 80,000 SEK         76.2%\n    Asset B                 25,000 SEK         23.8%\n  Total                    105,000 SEK        100.0%\n```\n\nWith `--format json`, the single-account output includes a `holdings` list:\n```json\n{\n  \"account\": \"account1\",\n  \"display_name\": \"Account Nickname\",\n  \"deposits\": 100000.0,\n  \"withdrawals\": 0.0,\n  \"net_invested\": 100000.0,\n  \"current_value\": 105000.0,\n  \"total_gain\": 5000.0,\n  \"total_gain_percent\": 5.0,\n  \"apy\": 10.2,\n  \"apy_mode\": \"mwrr\",\n  \"holdings\": [\n    {\n      \"asset\": \"Asset A\",\n      \"amount\": 80.0,\n      \"price\": 1000.0,\n      \"market_value\": 80000.0,\n      \"allocation_percent\": 76.19\n    },\n    {\n      \"asset\": \"Asset B\",\n      \"amount\": 25.0,\n      \"price\": 1000.0,\n      \"market_value\": 25000.0,\n      \"allocation_percent\": 23.81\n    }\n  ]\n}\n```\n\n\n### Understanding Statistics Output\n\nThe statistics output has two modes that serve different purposes:\n\n#### 1. Regular Statistics (Default)\n```bash\npython cli.py stats --period month\n```\n- **Shows**: Activity for each specific month/year\n- **\"Value\" column**: Current value of deposits made **during that specific period**\n- **If zero deposits in a period**: Value = 0 (by definition)\n- **If everything withdrawn**: Value = 0 (deposits fully withdrawn)\n- **Use case**: Track performance of investments made in each period\n\n#### 2. Accumulated Statistics\n```bash\npython cli.py stats --period month --accumulated\n```\n- **Shows**: Cumulative portfolio value over time\n- **\"Value\" column**: Total portfolio value at period end (all assets held)\n- **Carries forward**: Assets from earlier periods continue to appear\n- **Use case**: See total portfolio growth over time\n\n#### 3. APY Calculation Modes\n\nYou can specify the method used to calculate APY using the `--apy-mode` argument (supported by `stats` and `portfolio` commands):\n- `--apy-mode mwrr` (default): Money-Weighted Rate of Return. This is implemented using the **Modified Dietz** method. It accounts for the timing and size of all cash flows (deposits and withdrawals) in the period.\n- `--apy-mode twrr`: Time-Weighted Rate of Return. This measures pure investment performance independent of cash flow timing.\n\n#### Example\n**January 2024:**\n- Deposit: 10,000 SEK\n- Buy Asset A: 10,000 SEK\n- Current price of Asset A: 12,000 SEK\n\n**February 2024:**\n- No deposits/withdrawals\n- Asset A still held (worth 12,000 SEK)\n\n**Regular stats show:**\n- January: Value = 12,000 SEK (value of January deposit)\n- February: Value = 0 (no February deposits)\n\n**Accumulated stats show:**\n- January: Value = 12,000 SEK\n- February: Value = 12,000 SEK (asset carried forward)\n\n### CSV Format Support\n\nThe parser automatically detects whether your CSV file uses Avanza's old or new export format (the new format includes a `Transaktionsvaluta` column). The following transaction types are recognized:\n\n- Insättning (deposit)\n- Uttag (withdrawal)\n- Köp (purchase)\n- Sälj (sale)\n- Utdelning (dividend)\n- Räntor / Ränta / Inlåningsränta (interest)\n- Utländsk källskatt / Prelskatt / Preliminärskatt (taxes)\n- Byte / Övrigt (listing changes)\n- Tillgångsinsättning (asset deposit)\n\nEmpty numeric fields (like `Antal`, `Kurs`) are treated as zero.\n\n**Asset name normalization:** Instrument names are trimmed of leading/trailing whitespace on import — Avanza exports occasionally include stray trailing spaces that would otherwise break exact-name lookups (e.g. `account allocate`). Any pre-existing names are normalized automatically the next time the database is opened.\n\n**Unsettled trades (pending nota):** When you export on the same day as a non-SEK securities trade, Avanza includes preliminary rows whose brokerage (`Courtage`) and FX rate (`Valutakurs`) are not yet finalized. The importer detects these (non-SEK buy/sell with empty `Courtage` and `Valutakurs`) and defers them — it logs a warning per skipped row and leaves them out of the database. Re-export after the trade settles and re-import. Pass `import --allow-unsettled` to override and ingest them anyway.\n\n**Price fetching note:** The `stats` command (with `--update-prices auto` or `--update-prices always`) fetches current asset prices from Avanza's public search API (`www.avanza.se/_api/search/filtered-search`). This API is intended for web frontend use and may have rate limits or terms of service restrictions. Use at your own risk and consider using official APIs if available. Always review the website's terms of service before using their data.\n\n### Using the CLI (Recommended)\n\nThe unified CLI provides all functionality in a streamlined interface:\n\n1. Add transaction data in CSV format to the `data` folder.\n    - You might need to create a \"special_cases.json\" file in order to match and replace certain values in the data. See file specification in the documentation for the SpecialCases class.\n2. Import and process transactions: `python cli.py import data/your_transactions.csv`\n3. View statistics with automatic price updates: `python cli.py stats --update-prices auto`\n4. (Optional) Set default accounts for filtering: `python cli.py settings default-accounts \"account1,savings_account\"`\n5. (Optional) Set account nicknames: `python cli.py account nickname 1234567 \"Savings\"`\n6. View account summaries: `python cli.py accounts --update-prices auto`\n7. View portfolio snapshot or compare change over a period (equivalent to `stats --positions --summary`):\n    - Snapshot: `python cli.py portfolio [--as-of YYYY-MM-DD]`\n    - Period comparison: `python cli.py portfolio --start YYYY-MM-DD [--end YYYY-MM-DD]`\n8. Check system status: `python cli.py status`\n\nNote: All valuation commands (`stats`, `accounts`, `portfolio`) accept a `--format json` flag to return data in machine-readable JSON format instead of a plain-text table/block.\n\n**Advanced usage with account filtering:**\n```bash\n# Show stats for specific accounts\npython cli.py stats --account \"account1\" --update-prices auto\n\n# Show accumulated stats for any account combination\npython cli.py stats --account \"account1\" --accumulated --update-prices auto\npython cli.py stats --account \"account1,savings_account\" --accumulated --update-prices auto\n\n# Compare different account combinations\npython cli.py accounts --account \"account1\"\npython cli.py accounts --account \"savings_account\"\npython cli.py accounts --account all\n\n# Show portfolio snapshot as of a previous date\npython cli.py portfolio --as-of 2026-07-06\n\n# Compare portfolio values between two dates\npython cli.py portfolio --start 2026-06-29 --end 2026-07-06\n\n# Show portfolio snapshot with Money-Weighted Rate of Return (MWRR) APY\npython cli.py portfolio --account \"account1\"\n\n# Show portfolio snapshot using Time-Weighted Rate of Return (TWRR) APY\npython cli.py portfolio --account \"account1\" --apy-mode twrr\n\n```\n\n## CLI Reference\n\n| Command | Description |\n| :--- | :--- |\n| `python scripts/cli.py import FILE [--allocate-virtual] [--allow-unsettled]` | Import transaction entries from Avanza CSV (auto-allocate buys to virtuals; defers unsettled/pending-nota trades — see CSV Format Support) |\n| `python scripts/cli.py stats [OPTIONS]` | Calculate and display cohort performance statistics (TWRR, deposits) |\n| `python scripts/cli.py accounts [OPTIONS]` | Display summary of all accounts with asset values and cash |\n| `python scripts/cli.py portfolio [OPTIONS]` | Show portfolio holdings, market value, allocation %, and APY (alias to `stats --positions --summary`) |\n| `python scripts/cli.py status` | Display system status (transaction counts, price dates, date range) |\n| `python scripts/cli.py settings SUBCOMMAND` | Configure defaults and account nicknames |\n| `python scripts/cli.py reset [--hard] [--yes]` | Reset database state (`--hard` deletes data; default only marks unprocessed). `--hard` prompts for confirmation (or requires `--yes` non-interactively) and writes an automatic timestamped `.bak` backup first |\n| `python scripts/cli.py delete-tx [OPTIONS]` | Delete individual transaction(s) by `--tx-id`, `--date`+`--asset`, or `--since`, then rebuild derived tables (see below). Prompts for confirmation unless `--dry-run` or `--yes` is used; writes an automatic timestamped `.bak` backup first |\n| `python scripts/cli.py account SUBCOMMAND` | Manage accounts — virtual sub-portfolios (create/allocate/transfer/list/close/delete) and nicknames (see below) |\n| `python scripts/cli.py report [OPTIONS]` | Investment report with a virtual-portfolio section and a virtual-vs-parent-vs-benchmark comparison |\n\n### Deleting transactions\n\n> **Irreversible.** `delete-tx` permanently removes the matched transactions and rebuilds\n> derived tables — there is no undo. Back up the database first and use `--dry-run` to\n> preview, especially with broad selectors like `--since`.\n\n`delete-tx` removes specific real transactions and rebuilds the derived `assets` / cohort tables, so there is no need to `reset` the whole database after a bad import (e.g. a duplicate, or a row that slipped in before an unsettled trade was deferred). Targeting is mutually exclusive:\n\n- `delete-tx --tx-id ROWID` — most precise (use `status`/`export` to find the rowid).\n- `delete-tx --date YYYY-MM-DD --asset \"Name\" [--account ACCOUNT]` — the common surgical case.\n- `delete-tx --since YYYY-MM-DD [--account ACCOUNT]` — remove everything from a date onward (e.g. undo today's import).\n\n`--cascade` widens a `--date`+`--asset` match across the account family (parent + its virtuals) so a trade and its allocated split are removed together; `--dry-run` previews the deletion; `--yes` skips the confirmation prompt (required when running non-interactively, e.g. from an agent or script — the command refuses with exit code 1 otherwise). A timestamped `<db>.pre-delete-tx.<YYYYMMDD-HHMMSS>.bak` backup is written before any rows are deleted. When an allocated buy on a virtual is deleted, its orphaned funding `Intern överföring` transfer is removed automatically (mirroring `account allocate --undo`). After every deletion all transactions are reprocessed, so the `assets`/cohort tables always reflect the remaining transactions — never a half-deleted state.\n\n### Global Options\n- `--database PATH` (default: `data/asset_data.db`)\n- `--special-cases PATH` (default: `data/special_cases.json`)\n\n### Calculation & Output Options\n- `--account ACCOUNTS`: Limit to specific accounts (e.g. `12345,67890`, `default`, or `all`). Omitting the flag (default) shows **physical accounts only** (excludes virtual portfolios); pass `all` to include virtual portfolios in aggregates.\n- `--update-prices {auto,always,never}` (stats only): Controls when to fetch latest stock/fund prices from Avanza API\n- `--update-all` (stats only): Update prices for all assets in the database, held or not\n- `--as-of DATE`: View snapshot/stats as of a historical date (`YYYY-MM-DD`)\n- `--cohorts-start DATE --cohorts-end DATE`: Filter which deposit cohorts are displayed\n- `--cohort DATE`: Shorthand to filter by a single cohort month (`YYYY-MM`) or year (`YYYY`) (e.g. `--cohort 2024` groups yearly, `--cohort 2024-12` groups monthly)\n- `--from DATE --to DATE`: Set the performance valuation window (double snapshot)\n- `--positions`, `-p` (stats only): Show positions holdings breakdown under each cohort (or summary)\n- `--summary`, `-s` (stats only): Consolidate cohort statistics into a single overview block\n- `--apy-mode {mwrr,twrr}`: APY calculation method (`mwrr` uses Modified Dietz; `twrr` uses Time-Weighted)\n- `--format {table,json}`: Output formatting (default: `table`)\n- `--quiet`, `-q`: Suppress price data staleness warnings\n- `--no-interpolation`: Disable linear interpolation for sparse historical price data (falls back to nearest prior price, which may trigger staleness warnings)\n- `--risk`: Calculate and display portfolio-level risk metrics (Annualized Standard Deviation, Sharpe Ratio, Sortino Ratio, Maximum Drawdown with peak/trough calendar months)\n- `--beta [TICKER]`: Include the portfolio Beta calculation vs the specified benchmark (e.g. `^OMXSPI`, `ACWI`). Defaults to `^OMXSPI` if the flag is passed without a ticker value. Specifying `--beta` automatically enables risk metrics.\n\n### Guidelines: When to use what date boundaries\n1. **To see how cohorts from a certain period look today:**\n   Use `--cohorts-start YYYY-MM` / `--cohorts-end YYYY-MM`\n   *Example:* `python scripts/cli.py stats --cohorts-start 2024-01`\n2. **To see all cohorts' performance over a specific valuation window:**\n   Use `--from YYYY-MM` / `--to YYYY-MM` (or `--as-of YYYY-MM`)\n   *Example:* `python scripts/cli.py stats --from 2024-01 --to 2024-12`\n3. **To see only a single cohort month or year:**\n   Use `--cohort YYYY-MM` or `--cohort YYYY`\n   *Example:* `python scripts/cli.py stats --cohort 2024-12` (sets date range to `2024-12` and default grouping to monthly)\n   *Example:* `python scripts/cli.py stats --cohort 2024` (sets date range to `2024-01` to `2024-12` and default grouping to yearly)\n\n> [!NOTE]\n> In double-snapshot mode (`--from` / `--to`), the cohort-level output displays **`Start Value`** instead of **`Deposited`** for any cohorts created before the start date. Additionally, the **`Withdrawal`** line displays withdrawals made *specifically within the selected date range*, while withdrawals made prior to the start date are already accounted for in `Start Value`.\n\n### Settings Subcommands\n- `default-accounts ACCOUNTS`: Set default accounts (comma-separated list of IDs, or `all`)\n- `default-stats-period {month,year}`: Set default period for performance reports\n\n## Virtual Portfolios\n\nVirtual portfolios let you track sub-strategies (e.g. \"YOLO bets\", \"long-term holds\") *within* a single physical Avanza account. A virtual portfolio is just another account in the database (`is_virtual = 1`, linked to a parent). Because shares are **reassigned** (not copied) to the virtual account, every share and every SEK lives on exactly one account at a time — aggregates do not double count.\n\nAll account management — sub-portfolios **and** nicknames — lives under the `account` command.\n\n### Commands\n\n```bash\n# Create a virtual sub-portfolio under a physical parent (optionally fund it)\npython cli.py account create --name \"YOLO\" --parent 1234567 [--starting-cash 5000 --starting-cash-date 2026-07-19]\n\n# Allocate an imported transaction (full, or partial via --shares)\npython cli.py account allocate --tx-date 2026-07-19 --tx-asset \"Some Meme Stock\" --to \"YOLO\" [--shares 50]\n\n# Move cash between accounts\npython cli.py account transfer-cash --amount 10000 --from 1234567 --to \"YOLO\" --date 2026-07-19\n\n# Move an asset position between accounts\npython cli.py account transfer --asset \"Tesla\" --shares 50 --from \"YOLO\" --to 1234567 --date 2026-09-01\n\n# List virtual sub-portfolios with current value and APY\npython cli.py account list [--apy-mode twrr] [--format json]\n\n# Close a sub-portfolio: move all holdings + residual cash back to its parent\npython cli.py account close --name \"YOLO\" --date 2026-09-01\n\n# Account nicknames (moved here from `settings account-nickname`)\npython cli.py account nickname 1234567 \"Main\"\npython cli.py account nickname --list\npython cli.py account nickname --remove 1234567\n```\n\n### How it works\n\n- **`allocate`** moves a transaction (or splits it) onto the virtual account. Moving a buy transfers only the **shortfall** — if the virtual already has capital (e.g. from a prior sell), that cash is used and no transfer is needed. Partial splits proportionally divide `total` and `courtage`.\n- **`allocate --to <parent>`** (undo) moves a transaction back from a virtual to the parent and **deletes** the funding transfer pair that was created during the original allocation. No compensating transactions are created. Requires `--from <virtual>`. Partial undo (`--shares`) is not supported.\n- **`transfer`** (asset move) is represented internally as a sell on the source → cash transfer → rebuy on the destination (all tagged as synthetic). This composes the existing transaction handlers and is correct on every statistics path. The **source realizes its gain** up to the transfer and the **destination gets a fresh cost basis** at the transfer price — an honest \"this position left / entered the strategy\" bookkeeping.\n- **`close`** moves every holding (via the same decomposition) plus any residual cash back to the parent, then reprocesses. The virtual account row is **preserved** (kept `is_virtual = 1`) so its historical cohort/performance data remains queryable; it simply ends up empty.\n- **`delete`** is a clean teardown: reverts all real transactions back to the parent, removes every synthetic transaction tied to the virtual (including partner legs on other accounts), and deletes the account row. Unlike `close`, it leaves no trace — use it to correct a mistake rather than wind down a strategy. **Irreversible:** the virtual's historical performance data is lost on delete; use `close` instead if you want to preserve queryable history. Requires confirmation (y/N prompt, or `--yes` for non-interactive use) and writes an automatic timestamped `.bak` backup of the database before any changes.\n- After every `account` mutation the cohort tables are rebuilt automatically (same reprocessing as an import).\n\n### Viewing virtual portfolios\n\n- `accounts` shows a hierarchical tree: each physical account lists its combined value (self + its virtual children), with the children indented and marked `[V]`. The `TOTAL` row sums physical rows only (children are a breakdown, so nothing is double counted).\n- `stats` / `portfolio` default to **physical accounts only**. Pass `--account all` to include virtual portfolios, or `--account \"YOLO\"` to view a single virtual portfolio.\n\n### SQL views (for dashboards / reports)\n\nThe SQLite database exposes virtual-portfolio-aware views that Grafana panels, weekly reports, or ad-hoc queries can consume directly. All views are recreated on every connect, so they always reflect the current schema.\n\n| View | One row per | Key columns |\n| :--- | :--- | :--- |\n| `v_account_current_valuations` | account | `account`, `is_virtual`, `parent_account`, `display_name`, `cash`, `assets`, `total` |\n| `v_account_asset_holdings` | (account, asset) | `account`, `is_virtual`, `parent_account`, `asset_name`, `held_amount` |\n| `v_virtual_portfolio_rollup` | **physical** account | `parent_account`, `own_total`, `virtual_total`, `combined_total`, `virtual_count` (+ own/virtual/combined cash & assets) |\n| `v_external_capital_flows` | capital-flow transaction | `date`, `account`, `transaction_type`, `origin`, `flow_amount` |\n\n`is_virtual`/`parent_account` let you group or filter (e.g. physical-only with `WHERE is_virtual = 0`, or hierarchy with `GROUP BY parent_account`). The rollup view gives the \"main account including its virtuals\" total with no double counting — `combined_total = own_total + virtual_total`. The flows view's `origin` (`'avanza'` vs `'virtual'`) lets you exclude internal virtual transfers when computing real money in/out.\n\n```sql\n-- Physical accounts with their virtual sub-portfolios rolled in:\nSELECT parent_display_name, own_total, virtual_total, combined_total\nFROM v_virtual_portfolio_rollup;\n\n-- Current holdings grouped by parent family:\nSELECT COALESCE(parent_account, account) AS family, asset_name, SUM(held_amount)\nFROM v_account_asset_holdings GROUP BY family, asset_name;\n```\n\n### Reporting\n\n```bash\n# Full report: overview + accounts hierarchy + virtual section + comparison\npython cli.py report --update-prices auto\n\n# Add a benchmark to the performance comparison (annualized over portfolio lifetime)\npython cli.py report --benchmark ^OMXSPI\n\n# Machine-readable\npython cli.py report --format json\n```\n\nThe `report` command shows the total portfolio value (physical own + virtual),\nper-account and per-virtual APY, each virtual's share of its parent's combined\nvalue, and a comparison table of physical / virtual / benchmark returns. It\nreuses the stats engine for APY and Yahoo Finance (when `--benchmark` is given)\nfor the benchmark period return.\n\n### Limitations\n\n- **Sells and dividends are auto-routed.** When an imported sell or dividend arrives on an account that does not hold the asset (because the shares were allocated to a virtual), `import` automatically redistributes it to the account(s) that hold the shares, so reprocessing never aborts and income is attributed correctly:\n  - **Sells**: drain the sell's own account first, then the largest related virtual holder; full reassignment when the own account holds none, proportional split when it holds some but not enough. Multi-holder cases route to the largest with a warning.\n  - **Dividends**: split proportionally across every holder so each account is credited for the shares it actually holds.\n  - This runs only when virtual portfolios exist (no-op otherwise).\n- **Buys can be auto-allocated with `import --allocate-virtual`.** When a buy on a parent account cannot be funded (because cash is stranded in a virtual from a prior sell), the flag applies heuristics to assign it to the right virtual:\n  1. A virtual that **both holds the asset and can fund the buy** → allocate there (largest holder if multiple, with a warning).\n  2. A **new asset** (nobody holds it) and exactly one virtual can fund → allocate there.\n  3. Everything else → left on the parent with a warning for manual allocation.\n  - \"Can fund\" means the virtual's capital plus the parent's remaining capital covers the buy cost. The shortfall is transferred from the parent.\n  - If a heuristic allocation is wrong, undo it with `account allocate --to <parent> --from <virtual>`.\n- Funding a virtual requires the source account to hold enough capital at the transfer date.\n\n## Special Cases\n\nCorporate actions (splits, spin-offs, zero-priced deposits) can be overridden by copying the template and defining rules:\n```bash\ncp assets/special_cases_template.json ../data/avanza/special_cases.json\n```\n\n## Contributing\n\nThank you for your interest in contributing to this project! As a single-person hobby project, contributions are not expected but always welcome. If you have any ideas, bug fixes, or improvements, feel free to submit a pull request.\n\nTo contribute to this project, please follow these guidelines:\n\n1. Fork the repository and create a new branch for your contribution.\n2. Make your changes and ensure that the code is clean and well-documented.\n3. Test your changes thoroughly to ensure they do not introduce any regressions.\n4. Submit a pull request, explaining the purpose and details of your contribution.\n\nPlease note that as a hobby project, there may be limited resources available for reviewing and merging pull requests. Your patience is appreciated.\n\nThank you for your support and happy coding!\n\n## License\n\nPlease make sure that you are allowed to access information from the price source that you are using. The author of this project is not responsible for any legal issues that may arise from the use of this project. The url used in the example script is only for demonstration purposes and should not be used without permission.\n\nThe code in this project is licensed under the MIT License. See [LICENSE](LICENSE) file for details.\n\nPlease note that this project uses other libraries. The licenses for these libraries are as follows:\n\n- Libraries in `requirements.txt`:\n  - requests: Apache License 2.0\n\n\nPlease respect the licenses for these libraries when using this project.\n\nFile v2.14.1:_meta.json\n\n{\n  \"ownerId\": \"kn7cxaw1m27d1fe93wbz40vqkd8309h7\",\n  \"slug\": \"avanza-investment-tracker\",\n  \"version\": \"2.14.1\",\n  \"publishedAt\": 1788178662828\n}\n\nFile v2.14.1:references/troubleshooting.md\n\n# Troubleshooting\n\n**Database locked:** Close SQLite browsers\n**Import fails:** Check CSV is Avanza format, UTF-8\n**Missing prices:** Check internet, try without --update-prices\n**Path errors:** Run from skill root, use data/file.csv\n\nFile v2.14.1:references/workflows.md\n\n# Workflows\n\n## First-Time Setup\n\n```bash\npip install -r requirements.txt\nmkdir -p data\ncp assets/special_cases_template.json data/special_cases.json\n# Edit data/special_cases.json if you have corporate actions\npython scripts/cli.py import data/transactions.csv\npython scripts/cli.py stats --update-prices auto\n```\n\n## Adding More Data\n\n```bash\npython scripts/cli.py import data/new_transactions.csv\npython scripts/cli.py stats\n```\n\n## Reset Everything\n\n```bash\n# Soft reset: mark all transactions as unprocessed\npython scripts/cli.py reset\n\n# Hard reset: delete all transactions, stats, and prices\npython scripts/cli.py reset --hard\n```\n\nFile v2.14.1:skill-card.md\n\n## Description:\n\nProcess Avanza CSV exports, calculate TWRR/Modified Dietz returns, and track portfolio performance.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[patello](https://clawhub.ai/user/patello)\n\n### License/Terms of Use:\n\nMIT\n\n## Use Case:\n\nExternal users and developers use this skill to import Avanza transaction CSVs, maintain a local SQLite portfolio database, calculate investment performance, inspect holdings, and generate portfolio reports.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Declared file access is narrower than the code behavior, and portfolio databases or backup files may contain sensitive financial data.\n\nMitigation: Use a private data directory, review import and export paths before running commands, and protect or delete generated backup files when they are no longer needed.\n\nRisk: Portfolio asset names may be sent to Avanza by default when live prices are updated, and benchmark/date information may be sent to Riksbanken or Yahoo Finance for risk metrics.\n\nMitigation: Use --update-prices never for offline operation and run --risk or --beta only when those external requests are acceptable.\n\nRisk: Destructive commands can permanently remove transactions or account data.\n\nMitigation: Back up the database first, prefer dry-run previews where available, and avoid --yes unless the intended deletion scope has been reviewed.\n\n## Reference(s):\n\n- [ClawHub Skill Page](https://clawhub.ai/patello/skills/avanza-investment-tracker)\n- [Workflows](references/workflows.md)\n- [Troubleshooting](references/troubleshooting.md)\n- [Avanza Public API](https://www.avanza.se)\n- [Riksbanken API](https://api.riksbank.se)\n- [Yahoo Finance Query API](https://query1.finance.yahoo.com)\n\n## Skill Output:\n\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance]\n\n**Output Format:** [Markdown and terminal-oriented guidance with inline shell commands and optional JSON/table CLI output]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [May guide commands that read and write a user-specified SQLite database and optionally call external market-data APIs.]\n\n## Skill Version(s):\n\n2.14.1 (source: ClawHub release metadata and artifact/_meta.json)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nFile v2.14.1:assets/special_cases_template.json\n\n[\n    {\n        \"condition\": [\n            {\n                \"index\": 3,\n                \"value\": \"EXAMPLE FUND OLD NAME\"\n            },\n            {\n                \"index\": 0,\n                \"value\": \"2022-12-12\",\n                \"operator\": \"<=\"\n            }\n        ],\n        \"replacement\": [\n            {\n                \"index\": 3,\n                \"value\": \"EXAMPLE FUND NEW NAME\"\n            }\n        ]\n    },\n    {\n        \"condition\": [\n            {\n                \"index\": 9,\n                \"value\": \"ISIN1234567890\"\n            },\n            {\n                \"index\": 2,\n                \"value\": \"Värdepappersinsättning\"\n            }\n        ],\n        \"replacement\": [\n            {\n                \"index\": 2,\n                \"value\": \"Tillgångsinsättning\"\n            },\n            {\n                \"index\": 3,\n                \"value\": \"CORRECT ASSET NAME\"\n            }\n        ]\n    },\n    {\n        \"condition\": [\n            {\n                \"index\": 9,\n                \"value\": \"OLDISIN12345678\"\n            },\n            {\n                \"index\": 2,\n                \"value\": \"Värdepappersuttag\"\n            }\n        ],\n        \"replacement\": [\n            {\n                \"index\": 3,\n                \"value\": \"CORRECT OLD ASSET NAME\"\n            }\n        ]\n    },\n    {\n        \"condition\": [\n            {\n                \"index\": 9,\n                \"value\": \"ISIN9876543210\"\n            },\n            {\n                \"index\": 2,\n                \"value\": \"Värdepappersinsättning\"\n            }\n        ],\n        \"replacement\": [\n            {\n                \"index\": 2,\n                \"value\": \"Tillgångsinsättning\"\n            },\n            {\n                \"index\": 3,\n                \"value\": \"EXAMPLE ASSET CUSTOM COST BASIS\"\n            },\n            {\n                \"index\": 5,\n                \"value\": \"123.45\"\n            }\n        ]\n    }\n]\n\nFile v2.14.1:test/data/special_cases_test.json\n\n[\n    {\n        \"condition\": [\n            {\n            \"index\":0,\n            \"value\":\"2022-12-12\",\n            \"operator\":\"<=\"\n            },\n            {\n            \"index\":3,\n            \"value\":\"DNB SMB\"\n            }],\n        \"replacement\":[{\n            \"index\":3,\n            \"value\":\"DNB SMB A\"\n        }]\n    },\n    {\n        \"condition\": [{\n            \"index\":1,\n            \"value\":\"One Replacement\"\n            }],\n        \"replacement\":[{\n            \"index\":2,\n            \"value\":\"Replaced\"\n        }]\n    },\n    {\n        \"condition\": [{\n            \"index\":1,\n            \"value\":\"Two Replacements\"\n            }],\n        \"replacement\":[{\n            \"index\":2,\n            \"value\":\"Replaced1\"\n        },\n        {\n            \"index\":3,\n            \"value\":\"Replaced2\"\n        }]\n    },\n    {\n        \"condition\": [{\n            \"index\":3,\n            \"value\":\"COMPUTER INN\"\n            }],\n        \"replacement\":[{\n            \"index\":3,\n            \"value\":\"COMPUTER INNOVATION\"\n        }]\n    }\n]\n\nFile v2.14.1:changelog.txt\n\n- Hotfix: pin requests>=2.33.0 (CVE-2026-25645 in allowed 2.32.4); declare filesystem/network permissions in SKILL.md metadata (audit findings)\n\nFile v2.14.1:LICENSE\n\nMIT License\n\nCopyright (c) 2023 Patrik Ekenberg\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\nFile v2.14.1:pytest.ini\n\n[pytest]\nminversion = 6.0\ntestpaths =\n    test\npythonpath = .\nlog_cli = 1\nlog_cli_level = WARNING\nlog_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)\nlog_cli_date_format=%Y-%m-%d %H:%M:%S\n\nFile v2.14.1:requirements.txt\n\nrequests>=2.33.0\n\nArchive v2.14.0: 59 files, 163411 bytes\n\nFiles: _meta.json (145b), assets/special_cases_template.json (1911b), changelog.txt (67b), LICENSE (1071b), pytest.ini (219b), README.md (32688b), references/troubleshooting.md (234b), references/workflows.md (638b), requirements.txt (17b), scripts/calculate_stats.py (72055b), scripts/cli.py (142634b), scripts/data_parser.py (65416b), scripts/database_handler.py (36743b), scripts/risk_calculator.py (29854b), skill-card.md (2425b), SKILL.md (9667b), test/conftest.py (1091b), test/data/asset_deposit.csv (311b), test/data/fraction_writeoff.csv (352b), test/data/interest_fees.csv (601b), test/data/listing_change.csv (610b), test/data/multiple_transfers_same_day.csv (565b), test/data/new_format_data.csv (537b), test/data/new_transaction_types.csv (571b), test/data/reordered_data.csv (716b), test/data/small_data_diff_accounts.csv (1258b), test/data/small_data_plus.csv (1251b), test/data/small_data_wrong_accounts.csv (1258b), test/data/small_data.csv (716b), test/data/special_cases_test.json (1024b), test/data/transfer_attribution_single_account.csv (359b), test/data/transfer_attribution_two_accounts.csv (578b), test/data/transfer_deferral.csv (663b), test/data/transfer_proper_order.csv (663b), test/test_account_apy.py (11114b), test/test_accounts.py (57947b), test/test_add_data.py (5128b), test/test_asset_deposit_issue.py (4602b), test/test_database_handler.py (4623b), test/test_delete_tx.py (7139b), test/test_export.py (4672b), test/test_fx_conversion.py (16660b), test/test_historical_prices.py (22477b), test/test_import_normalization.py (3047b), test/test_import_unsettled.py (2616b), test/test_modified_dietz.py (3608b), test/test_parse_data.py (8129b), test/test_parse_special_cases.py (2042b), test/test_per_account_asset_tracking.py (11926b), test/test_portfolio_holdings.py (6187b), test/test_price_interpolation.py (6143b), test/test_price_staleness.py (5412b), test/test_price_updates.py (2398b), test/test_process_data_attribution.py (4242b), test/test_process_data_deferral.py (3929b), test/test_retroactive_updates.py (4460b), test/test_risk_metrics.py (11534b), test/test_stats_positions.py (13316b), test/test_twrr.py (5026b)\n\nFile v2.14.0:SKILL.md\n\n---\nname: avanza-investment-tracker\ndescription: \"Process Avanza CSV exports, calculate TWRR/Modified Dietz returns, and track portfolio performance. Use when importing stock transactions, calculating investment returns, or managing portfolio data. Reads/writes a local SQLite database, and (for live prices and risk metrics) makes outbound HTTPS requests to Avanza, Riksbanken, and Yahoo Finance. Includes irreversible deletion commands (reset --hard, delete-tx, account delete) — see Security and Data Access in SKILL.md/README.\"\nmetadata:\n  openclaw:\n    requires:\n      bins:\n        - python3\n---\n\n# Avanza Investment Tracker\n\nParse transaction CSVs and compute portfolio performance metrics.\n\n## Security and Data Access\n\nBe aware of what this skill does before running it:\n\n- **Local database writes:** imports, price updates, and portfolio management read and write a local SQLite database.\n- **Network access (optional but on by default):** live price/FX lookups contact Avanza's public API with the asset names in your portfolio; risk metrics (`--risk`, `--beta`) may also contact the Riksbanken API and Yahoo Finance (benchmark ticker + date range). Use `--update-prices never` to stay fully offline.\n- **Irreversible deletions:** `reset --hard`, `delete-tx`, `account allocate --undo`, and `account delete` permanently remove transactions and rebuild derived tables. There is no built-in undo. Back up your database first (e.g. `cp` or git), and prefer `delete-tx --dry-run` to preview. Avoid broad selectors like `delete-tx --since` unless you are certain of the blast radius.\n\n## Quick Start\n\nRun commands from your workspace root, specifying the paths to your database and CSV:\n\n```bash\n# 1. Import new transactions\npython path/to/cli.py --database data/asset_data.db import path/to/transactions.csv\n\n# 2. Update price cache and show statistics\npython path/to/cli.py --database data/asset_data.db stats --update-prices auto\n\n# 3. View portfolio allocation and APY\npython path/to/cli.py --database data/asset_data.db portfolio --account default\n```\n\n## Data Storage Pattern\n\n**User data lives OUTSIDE the skill directory.** Recommended structure:\n\n```\nworkspace-finance/\n├── skills/avanza-investment-tracker/   # Portable skill logic\n│   ├── SKILL.md\n│   ├── scripts/\n│   └── assets/\n└── data/avanza/                        # Private portfolio data\n    ├── transactions.csv\n    ├── special_cases.json\n    └── asset_data.db\n```\n\n## CLI Reference\n\n| Command | Description |\n| :--- | :--- |\n| `python scripts/cli.py import FILE [--allocate-virtual] [--allow-unsettled]` | Import transaction entries from Avanza CSV (auto-allocate buys to virtuals; defers unsettled/pending-nota trades — see CSV Format Support) |\n| `python scripts/cli.py stats [OPTIONS]` | Calculate and display cohort performance statistics (TWRR, deposits) |\n| `python scripts/cli.py accounts [OPTIONS]` | Display summary of all accounts with asset values and cash |\n| `python scripts/cli.py portfolio [OPTIONS]` | Show portfolio holdings, market value, allocation %, and APY (alias to `stats --positions --summary`) |\n| `python scripts/cli.py status` | Display system status (transaction counts, price dates, date range) |\n| `python scripts/cli.py settings SUBCOMMAND` | Configure defaults and account nicknames |\n| `python scripts/cli.py reset [--hard] [--yes]` | Reset database state (`--hard` deletes data; default only marks unprocessed). `--hard` prompts for confirmation (or requires `--yes` non-interactively) and writes an automatic timestamped `.bak` backup first |\n| `python scripts/cli.py delete-tx [OPTIONS]` | Delete individual transaction(s) by `--tx-id`, `--date`+`--asset`, or `--since`, then rebuild derived tables (see below). Prompts for confirmation unless `--dry-run` or `--yes` is used; writes an automatic timestamped `.bak` backup first |\n| `python scripts/cli.py account SUBCOMMAND` | Manage accounts — virtual sub-portfolios (create/allocate/transfer/list/close/delete) and nicknames (see below) |\n| `python scripts/cli.py report [OPTIONS]` | Investment report with a virtual-portfolio section and a virtual-vs-parent-vs-benchmark comparison |\n\n### Deleting transactions\n\n> **Irreversible.** `delete-tx` permanently removes the matched transactions and rebuilds\n> derived tables — there is no undo. Back up the database first and use `--dry-run` to\n> preview, especially with broad selectors like `--since`.\n\n`delete-tx` removes specific real transactions and rebuilds the derived `assets` / cohort tables, so there is no need to `reset` the whole database after a bad import (e.g. a duplicate, or a row that slipped in before an unsettled trade was deferred). Targeting is mutually exclusive:\n\n- `delete-tx --tx-id ROWID` — most precise (use `status`/`export` to find the rowid).\n- `delete-tx --date YYYY-MM-DD --asset \"Name\" [--account ACCOUNT]` — the common surgical case.\n- `delete-tx --since YYYY-MM-DD [--account ACCOUNT]` — remove everything from a date onward (e.g. undo today's import).\n\n`--cascade` widens a `--date`+`--asset` match across the account family (parent + its virtuals) so a trade and its allocated split are removed together; `--dry-run` previews the deletion; `--yes` skips the confirmation prompt (required when running non-interactively, e.g. from an agent or script — the command refuses with exit code 1 otherwise). A timestamped `<db>.pre-delete-tx.<YYYYMMDD-HHMMSS>.bak` backup is written before any rows are deleted. When an allocated buy on a virtual is deleted, its orphaned funding `Intern överföring` transfer is removed automatically (mirroring `account allocate --undo`). After every deletion all transactions are reprocessed, so the `assets`/cohort tables always reflect the remaining transactions — never a half-deleted state.\n\n### Global Options\n- `--database PATH` (default: `data/asset_data.db`)\n- `--special-cases PATH` (default: `data/special_cases.json`)\n\n### Calculation & Output Options\n- `--account ACCOUNTS`: Limit to specific accounts (e.g. `12345,67890`, `default`, or `all`). Omitting the flag (default) shows **physical accounts only** (excludes virtual portfolios); pass `all` to include virtual portfolios in aggregates.\n- `--update-prices {auto,always,never}` (stats only): Controls when to fetch latest stock/fund prices from Avanza API\n- `--update-all` (stats only): Update prices for all assets in the database, held or not\n- `--as-of DATE`: View snapshot/stats as of a historical date (`YYYY-MM-DD`)\n- `--cohorts-start DATE --cohorts-end DATE`: Filter which deposit cohorts are displayed\n- `--cohort DATE`: Shorthand to filter by a single cohort month (`YYYY-MM`) or year (`YYYY`) (e.g. `--cohort 2024` groups yearly, `--cohort 2024-12` groups monthly)\n- `--from DATE --to DATE`: Set the performance valuation window (double snapshot)\n- `--positions`, `-p` (stats only): Show positions holdings breakdown under each cohort (or summary)\n- `--summary`, `-s` (stats only): Consolidate cohort statistics into a single overview block\n- `--apy-mode {mwrr,twrr}`: APY calculation method (`mwrr` uses Modified Dietz; `twrr` uses Time-Weighted)\n- `--format {table,json}`: Output formatting (default: `table`)\n- `--quiet`, `-q`: Suppress price data staleness warnings\n- `--no-interpolation`: Disable linear interpolation for sparse historical price data (falls back to nearest prior price, which may trigger staleness warnings)\n- `--risk`: Calculate and display portfolio-level risk metrics (Annualized Standard Deviation, Sharpe Ratio, Sortino Ratio, Maximum Drawdown with peak/trough calendar months)\n- `--beta [TICKER]`: Include the portfolio Beta calculation vs the specified benchmark (e.g. `^OMXSPI`, `ACWI`). Defaults to `^OMXSPI` if the flag is passed without a ticker value. Specifying `--beta` automatically enables risk metrics.\n\n### Guidelines: When to use what date boundaries\n1. **To see how cohorts from a certain period look today:**\n   Use `--cohorts-start YYYY-MM` / `--cohorts-end YYYY-MM`\n   *Example:* `python scripts/cli.py stats --cohorts-start 2024-01`\n2. **To see all cohorts' performance over a specific valuation window:**\n   Use `--from YYYY-MM` / `--to YYYY-MM` (or `--as-of YYYY-MM`)\n   *Example:* `python scripts/cli.py stats --from 2024-01 --to 2024-12`\n3. **To see only a single cohort month or year:**\n   Use `--cohort YYYY-MM` or `--cohort YYYY`\n   *Example:* `python scripts/cli.py stats --cohort 2024-12` (sets date range to `2024-12` and default grouping to monthly)\n   *Example:* `python scripts/cli.py stats --cohort 2024` (sets date range to `2024-01` to `2024-12` and default grouping to yearly)\n\n> [!NOTE]\n> In double-snapshot mode (`--from` / `--to`), the cohort-level output displays **`Start Value`** instead of **`Deposited`** for any cohorts created before the start date. Additionally, the **`Withdrawal`** line displays withdrawals made *specifically within the selected date range*, while withdrawals made prior to the start date are already accounted for in `Start Value`.\n\n### Settings Subcommands\n- `default-accounts ACCOUNTS`: Set default accounts (comma-separated list of IDs, or `all`)\n- `default-stats-period {month,year}`: Set default period for performance reports\n\n\n## Special Cases\n\nCorporate actions (splits, spin-offs, zero-priced deposits) can be overridden by copying the template and defining rules:\n```bash\ncp assets/special_cases_template.json ../data/avanza/special_cases.json\n```\n\n\n## See Also\n\n- **Detailed workflows**: [references/workflows.md](references/workflows.md)\n- **Troubleshooting guide**: [references/troubleshooting.md](references/troubleshooting.md)\n\nFile v2.14.0:README.md\n\n# Investment Tracker\n\n## Description\n\nThis project aims to create a tool for tracking stocks and investments on investment platforms. The original idea for this project was when I was about to buy a SteamDeck. Then I thought better of it and started to wonder how much the money would grow over time if I invested it instead. I realized that it would require me to keep track of the assets that I purchase a particular month, even if I sell them and buy new ones later on. Or if I get dividends and reinvest them.\n\nWith this project, I am able to parse data from my investment platform, keep latest asset values up to date and calculate relevant statistics.\n\nThis project is a work in progress and will be updated as I go along.\n\n## Table of Contents\n\n- [Features](#features)\n- [Installation](#installation)\n- [Usage](#usage) — includes [Network access and privacy](#network-access-and-privacy)\n- [CLI Reference](#cli-reference)\n- [Virtual Portfolios](#virtual-portfolios)\n- [Special Cases](#special-cases)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Features\n\n- Parse and store data from an investment platform.\n- Keep track of where money invested each month is moved and grown over time.\n- **Per-account statistics**: Calculate gains/losses for each account separately, then merge for combined views\n- Calculate statistics for months and years:\n    - Deposit\n    - Withdrawal\n    - Current Value\n    - Total Gain/Loss\n    - Realized Gain/Loss\n    - Unrealized Gain/Loss\n    - APY (Annual Percentage Yield)\n- Two viewing modes:\n    - **Period-specific**: Track performance of investments made in each month/year\n    - **Accumulated**: See total portfolio value over time with assets carried forward\n- **Account filtering**: View statistics for any combination of accounts with full accumulated history support\n- **System status**: Check database statistics, price freshness, and transaction date range\n- **Virtual portfolios**: Track sub-portfolios (e.g. strategy sleeves) separately with allocation, transfers, and virtual-vs-parent performance comparison\n- **Risk metrics**: Optional risk/beta calculations against a benchmark (fetches policy rates and benchmark prices from Riksbanken/Yahoo Finance)\n- **Reporting**: Investment report command with a virtual-portfolio section and benchmark comparison\n- **Safety rails**: Destructive commands (`reset --hard`, `delete-tx`, `account delete`) require confirmation and write an automatic timestamped `.bak` backup first\n\n## Installation\n\n1. Clone the repository.\n2. Install the dependencies with `pip install -r requirements.txt`.\n\n## Quick Start\n\n```bash\n# 1. Import your transaction data\npython cli.py import data/your_transactions.csv\n\n# 2. Get statistics with automatic price updates\npython cli.py stats --update-prices auto --period year --deposits all\n\n# 3. Check system status anytime\npython cli.py status\n\n# 4. (Optional) Set default accounts for filtering\npython cli.py settings default-accounts \"account1,savings_account\"\n\n# 5. (Optional) Set account nicknames for readability\npython cli.py account nickname 1234567 \"Savings\"\n\n# 6. View account summaries\npython cli.py accounts --update-prices auto\n```\n\n## Usage\n\n### Network access and privacy\n\nBy default this tool is not fully offline. Be aware of outbound requests:\n\n- **Avanza public API** — live price/FX lookups send the asset names and currency pairs from your portfolio (this is how `--update-prices auto` works). Use `--update-prices never` to keep it fully offline (cached prices only).\n- **Riksbanken API and Yahoo Finance** — only contacted for risk metrics (`--risk`, `--beta`) to fetch policy rates and benchmark prices; this sends the benchmark ticker and date range, not your holdings.\n\n### Command Line Interface (CLI)\n\nA unified CLI is available via `cli.py`. It provides subcommands for all major operations:\n\n#### Modern Workflow (Recommended)\n\n```bash\n# Import CSV data and process transactions in one atomic operation\npython cli.py import data/transactions.csv\n\n# Show statistics with smart updates (auto-updates prices if stale)\npython cli.py stats --update-prices auto --period year --deposits all\n\n# Check system status (transactions, prices, metadata)\npython cli.py status\n\n# Reset database state (mark all transactions as unprocessed)\npython cli.py reset\n\n# Hard reset (delete all transactions, stats, and prices while keeping configuration)\n# WARNING: irreversible — permanently deletes all financial history in the database. Back it up first.\npython cli.py reset --hard\n```\n\n> **Irreversible operations.** `reset --hard`, `delete-tx`, `account allocate --undo`, and\n> `account delete` permanently remove data and rebuild derived tables. There is no undo.\n> Back up the database before running them, and prefer `--dry-run` where available\n> (e.g. `delete-tx --dry-run`) to preview the blast radius. Avoid broad selectors like\n> `delete-tx --since` unless you are certain what they will remove.\n>\n> Since the audit-hardening update, `reset --hard`, `delete-tx`, and `account delete`\n> additionally: (1) ask for confirmation — interactively via a y/N prompt, and in\n> non-interactive shells (agents, cron, scripts) only run when `--yes` is passed —\n> and (2) automatically copy the database to a timestamped\n> `<db>.pre-<command>.<YYYYMMDD-HHMMSS>.bak` file before making any changes.\n> Backup files are never cleaned up automatically — delete them yourself once you are\n> satisfied the operation went well (and consider adding `*.bak` to your `.gitignore` if\n> the database lives in a git repo).\n\nAll commands accept optional `--database` and `--special-cases` arguments to override default paths:\n\n```bash\npython cli.py --database path/to/db.db --special-cases path/to/special.json import data.csv\n```\n\n#### Smart Update Features\n\nThe new `stats` command includes intelligent caching and update logic:\n\n- **Price freshness**: Prices are considered \"fresh\" if updated within 1 day\n- **Price interpolation**: Historical valuations automatically interpolate missing asset prices linearly between nearest known dates (e.g. from transaction purchases or API price updates).\n  - Use `--no-interpolation` to disable this behavior and fall back to the closest prior price.\n- **Auto-update**: `--update-prices auto` updates only if prices are stale\n- **Force update**: `--update-prices always` forces price refresh\n- **Skip update**: `--update-prices never` uses cached prices\n- **Stats caching**: Statistics are recalculated only when needed (new transactions or price updates)\n- **Default period**: You can set a default stats cohort period.\n  ```bash\n  # Set default stats period\n  python cli.py settings default-stats-period year\n\n  # Use the default stats period\n  python cli.py stats\n  ```\n\n\n#### Account Nicknames\n\nAssign human-readable names to account numbers for easier identification:\n\n```bash\n# Set a nickname for an account\npython cli.py account nickname 1234567 \"Savings\"\n\n# List all nicknames\npython cli.py account nickname --list\n\n# Remove a nickname\npython cli.py account nickname --remove 1234567\n```\n\nNicknames are stored in the database and displayed in the `accounts` command output alongside account numbers.\n\n#### Account Filtering\n\nThe CLI supports filtering statistics by account with the `--account` flag. Statistics are calculated per-account for accuracy, then merged when viewing multiple accounts:\n\n```bash\n# Show stats for all accounts (default)\npython cli.py stats --account all\n\n# Show stats using default accounts set via settings\npython cli.py stats --account default\n\n# Show stats for specific accounts (comma-separated)\npython cli.py stats --account \"account1,savings_account\"\n\n# Accumulated statistics now work with any account combination\npython cli.py stats --account \"account1\" --accumulated\n```\n\nSet default accounts for filtering:\n```bash\n# Set default accounts\npython cli.py settings default-accounts \"account1,savings_account\"\n\n# Reset to include all accounts\npython cli.py settings default-accounts all\n```\n\nView account summaries with cash and asset values:\n```bash\n# Show all accounts\npython cli.py accounts --update-prices auto\n\n# Filter accounts (same syntax as stats command)\npython cli.py accounts --account default\n\n# View portfolio snapshot for specific accounts\npython cli.py portfolio --account \"account1,savings_account\"\n\n# Compare portfolio values for a specific account over a period\npython cli.py portfolio --account \"account1\" --start-date 2026-06-29 --end-date 2026-07-06\n```\n\nOutput format for `accounts` command:\n```\nAccount                Cash (SEK) Assets (SEK)  Total (SEK)\n--------------------------------------------------------\naccount1                        0       100000       100000\nsavings_account             50000            0        50000\n--------------------------------------------------------\nTOTAL                       50000       100000       150000\n```\n\nOutput format for `portfolio` command (single account):\n```\nAccount account1 (Account Nickname)\nDeposits: 100,000 SEK\nWithdrawals: 0 SEK\nNet invested: 100,000 SEK\nCurrent value: 105,000 SEK\nTotal gain: +5,000 SEK (+5.0%)\nAPY: 10.2% (MWRR)\n\n  Holdings:\n    Fund Name              Market Value    Allocation\n    Asset A                 80,000 SEK         76.2%\n    Asset B                 25,000 SEK         23.8%\n  Total                    105,000 SEK        100.0%\n```\n\nWith `--format json`, the single-account output includes a `holdings` list:\n```json\n{\n  \"account\": \"account1\",\n  \"display_name\": \"Account Nickname\",\n  \"deposits\": 100000.0,\n  \"withdrawals\": 0.0,\n  \"net_invested\": 100000.0,\n  \"current_value\": 105000.0,\n  \"total_gain\": 5000.0,\n  \"total_gain_percent\": 5.0,\n  \"apy\": 10.2,\n  \"apy_mode\": \"mwrr\",\n  \"holdings\": [\n    {\n      \"asset\": \"Asset A\",\n      \"amount\": 80.0,\n      \"price\": 1000.0,\n      \"market_value\": 80000.0,\n      \"allocation_percent\": 76.19\n    },\n    {\n      \"asset\": \"Asset B\",\n      \"amount\": 25.0,\n      \"price\": 1000.0,\n      \"market_value\": 25000.0,\n      \"allocation_percent\": 23.81\n    }\n  ]\n}\n```\n\n\n### Understanding Statistics Output\n\nThe statistics output has two modes that serve different purposes:\n\n#### 1. Regular Statistics (Default)\n```bash\npython cli.py stats --period month\n```\n- **Shows**: Activity for each specific month/year\n- **\"Value\" column**: Current value of deposits made **during that specific period**\n- **If zero deposits in a period**: Value = 0 (by definition)\n- **If everything withdrawn**: Value = 0 (deposits fully withdrawn)\n- **Use case**: Track performance of investments made in each period\n\n#### 2. Accumulated Statistics\n```bash\npython cli.py stats --period month --accumulated\n```\n- **Shows**: Cumulative portfolio value over time\n- **\"Value\" column**: Total portfolio value at period end (all assets held)\n- **Carries forward**: Assets from earlier periods continue to appear\n- **Use case**: See total portfolio growth over time\n\n#### 3. APY Calculation Modes\n\nYou can specify the method used to calculate APY using the `--apy-mode` argument (supported by `stats` and `portfolio` commands):\n- `--apy-mode mwrr` (default): Money-Weighted Rate of Return. This is implemented using the **Modified Dietz** method. It accounts for the timing and size of all cash flows (deposits and withdrawals) in the period.\n- `--apy-mode twrr`: Time-Weighted Rate of Return. This measures pure investment performance independent of cash flow timing.\n\n#### Example\n**January 2024:**\n- Deposit: 10,000 SEK\n- Buy Asset A: 10,000 SEK\n- Current price of Asset A: 12,000 SEK\n\n**February 2024:**\n- No deposits/withdrawals\n- Asset A still held (worth 12,000 SEK)\n\n**Regular stats show:**\n- January: Value = 12,000 SEK (value of January deposit)\n- February: Value = 0 (no February deposits)\n\n**Accumulated stats show:**\n- January: Value = 12,000 SEK\n- February: Value = 12,000 SEK (asset carried forward)\n\n### CSV Format Support\n\nThe parser automatically detects whether your CSV file uses Avanza's old or new export format (the new format includes a `Transaktionsvaluta` column). The following transaction types are recognized:\n\n- Insättning (deposit)\n- Uttag (withdrawal)\n- Köp (purchase)\n- Sälj (sale)\n- Utdelning (dividend)\n- Räntor / Ränta / Inlåningsränta (interest)\n- Utländsk källskatt / Prelskatt / Preliminärskatt (taxes)\n- Byte / Övrigt (listing changes)\n- Tillgångsinsättning (asset deposit)\n\nEmpty numeric fields (like `Antal`, `Kurs`) are treated as zero.\n\n**Asset name normalization:** Instrument names are trimmed of leading/trailing whitespace on import — Avanza exports occasionally include stray trailing spaces that would otherwise break exact-name lookups (e.g. `account allocate`). Any pre-existing names are normalized automatically the next time the database is opened.\n\n**Unsettled trades (pending nota):** When you export on the same day as a non-SEK securities trade, Avanza includes preliminary rows whose brokerage (`Courtage`) and FX rate (`Valutakurs`) are not yet finalized. The importer detects these (non-SEK buy/sell with empty `Courtage` and `Valutakurs`) and defers them — it logs a warning per skipped row and leaves them out of the database. Re-export after the trade settles and re-import. Pass `import --allow-unsettled` to override and ingest them anyway.\n\n**Price fetching note:** The `stats` command (with `--update-prices auto` or `--update-prices always`) fetches current asset prices from Avanza's public search API (`www.avanza.se/_api/search/filtered-search`). This API is intended for web frontend use and may have rate limits or terms of service restrictions. Use at your own risk and consider using official APIs if available. Always review the website's terms of service before using their data.\n\n### Using the CLI (Recommended)\n\nThe unified CLI provides all functionality in a streamlined interface:\n\n1. Add transaction data in CSV format to the `data` folder.\n    - You might need to create a \"special_cases.json\" file in order to match and replace certain values in the data. See file specification in the documentation for the SpecialCases class.\n2. Import and process transactions: `python cli.py import data/your_transactions.csv`\n3. View statistics with automatic price updates: `python cli.py stats --update-prices auto`\n4. (Optional) Set default accounts for filtering: `python cli.py settings default-accounts \"account1,savings_account\"`\n5. (Optional) Set account nicknames: `python cli.py account nickname 1234567 \"Savings\"`\n6. View account summaries: `python cli.py accounts --update-prices auto`\n7. View portfolio snapshot or compare change over a period (equivalent to `stats --positions --summary`):\n    - Snapshot: `python cli.py portfolio [--as-of YYYY-MM-DD]`\n    - Period comparison: `python cli.py portfolio --start YYYY-MM-DD [--end YYYY-MM-DD]`\n8. Check system status: `python cli.py status`\n\nNote: All valuation commands (`stats`, `accounts`, `portfolio`) accept a `--format json` flag to return data in machine-readable JSON format instead of a plain-text table/block.\n\n**Advanced usage with account filtering:**\n```bash\n# Show stats for specific accounts\npython cli.py stats --account \"account1\" --update-prices auto\n\n# Show accumulated stats for any account combination\npython cli.py stats --account \"account1\" --accumulated --update-prices auto\npython cli.py stats --account \"account1,savings_account\" --accumulated --update-prices auto\n\n# Compare different account combinations\npython cli.py accounts --account \"account1\"\npython cli.py accounts --account \"savings_account\"\npython cli.py accounts --account all\n\n# Show portfolio snapshot as of a previous date\npython cli.py portfolio --as-of 2026-07-06\n\n# Compare portfolio values between two dates\npython cli.py portfolio --start 2026-06-29 --end 2026-07-06\n\n# Show portfolio snapshot with Money-Weighted Rate of Return (MWRR) APY\npython cli.py portfolio --account \"account1\"\n\n# Show portfolio snapshot using Time-Weighted Rate of Return (TWRR) APY\npython cli.py portfolio --account \"account1\" --apy-mode twrr\n\n```\n\n## CLI Reference\n\n| Command | Description |\n| :--- | :--- |\n| `python scripts/cli.py import FILE [--allocate-virtual] [--allow-unsettled]` | Import transaction entries from Avanza CSV (auto-allocate buys to virtuals; defers unsettled/pending-nota trades — see CSV Format Support) |\n| `python scripts/cli.py stats [OPTIONS]` | Calculate and display cohort performance statistics (TWRR, deposits) |\n| `python scripts/cli.py accounts [OPTIONS]` | Display summary of all accounts with asset values and cash |\n| `python scripts/cli.py portfolio [OPTIONS]` | Show portfolio holdings, market value, allocation %, and APY (alias to `stats --positions --summary`) |\n| `python scripts/cli.py status` | Display system status (transaction counts, price dates, date range) |\n| `python scripts/cli.py settings SUBCOMMAND` | Configure defaults and account nicknames |\n| `python scripts/cli.py reset [--hard] [--yes]` | Reset database state (`--hard` deletes data; default only marks unprocessed). `--hard` prompts for confirmation (or requires `--yes` non-interactively) and writes an automatic timestamped `.bak` backup first |\n| `python scripts/cli.py delete-tx [OPTIONS]` | Delete individual transaction(s) by `--tx-id`, `--date`+`--asset`, or `--since`, then rebuild derived tables (see below). Prompts for confirmation unless `--dry-run` or `--yes` is used; writes an automatic timestamped `.bak` backup first |\n| `python scripts/cli.py account SUBCOMMAND` | Manage accounts — virtual sub-portfolios (create/allocate/transfer/list/close/delete) and nicknames (see below) |\n| `python scripts/cli.py report [OPTIONS]` | Investment report with a virtual-portfolio section and a virtual-vs-parent-vs-benchmark comparison |\n\n### Deleting transactions\n\n> **Irreversible.** `delete-tx` permanently removes the matched transactions and rebuilds\n> derived tables — there is no undo. Back up the database first and use `--dry-run` to\n> preview, especially with broad selectors like `--since`.\n\n`delete-tx` removes specific real transactions and rebuilds the derived `assets` / cohort tables, so there is no need to `reset` the whole database after a bad import (e.g. a duplicate, or a row that slipped in before an unsettled trade was deferred). Targeting is mutually exclusive:\n\n- `delete-tx --tx-id ROWID` — most precise (use `status`/`export` to find the rowid).\n- `delete-tx --date YYYY-MM-DD --asset \"Name\" [--account ACCOUNT]` — the common surgical case.\n- `delete-tx --since YYYY-MM-DD [--account ACCOUNT]` — remove everything from a date onward (e.g. undo today's import).\n\n`--cascade` widens a `--date`+`--asset` match across the account family (parent + its virtuals) so a trade and its allocated split are removed together; `--dry-run` previews the deletion; `--yes` skips the confirmation prompt (required when running non-interactively, e.g. from an agent or script — the command refuses with exit code 1 otherwise). A timestamped `<db>.pre-delete-tx.<YYYYMMDD-HHMMSS>.bak` backup is written before any rows are deleted. When an allocated buy on a virtual is deleted, its orphaned funding `Intern överföring` transfer is removed automatically (mirroring `account allocate --undo`). After every deletion all transactions are reprocessed, so the `assets`/cohort tables always reflect the remaining transactions — never a half-deleted state.\n\n### Global Options\n- `--database PATH` (default: `data/asset_data.db`)\n- `--special-cases PATH` (default: `data/special_cases.json`)\n\n### Calculation & Output Options\n- `--account ACCOUNTS`: Limit to specific accounts (e.g. `12345,67890`, `default`, or `all`). Omitting the flag (default) shows **physical accounts only** (excludes virtual portfolios); pass `all` to include virtual portfolios in aggregates.\n- `--update-prices {auto,always,never}` (stats only): Controls when to fetch latest stock/fund prices from Avanza API\n- `--update-all` (stats only): Update prices for all assets in the database, held or not\n- `--as-of DATE`: View snapshot/stats as of a historical date (`YYYY-MM-DD`)\n- `--cohorts-start DATE --cohorts-end DATE`: Filter which deposit cohorts are displayed\n- `--cohort DATE`: Shorthand to filter by a single cohort month (`YYYY-MM`) or year (`YYYY`) (e.g. `--cohort 2024` groups yearly, `--cohort 2024-12` groups monthly)\n- `--from DATE --to DATE`: Set the performance valuation window (double snapshot)\n- `--positions`, `-p` (stats only): Show positions holdings breakdown under each cohort (or summary)\n- `--summary`, `-s` (stats only): Consolidate cohort statistics into a single overview block\n- `--apy-mode {mwrr,twrr}`: APY calculation method (`mwrr` uses Modified Dietz; `twrr` uses Time-Weighted)\n- `--format {table,json}`: Output formatting (default: `table`)\n- `--quiet`, `-q`: Suppress price data staleness warnings\n- `--no-interpolation`: Disable linear interpolation for sparse historical price data (falls back to nearest prior price, which may trigger staleness warnings)\n- `--risk`: Calculate and display portfolio-level risk metrics (Annualized Standard Deviation, Sharpe Ratio, Sortino Ratio, Maximum Drawdown with peak/trough calendar months)\n- `--beta [TICKER]`: Include the portfolio Beta calculation vs the specified benchmark (e.g. `^OMXSPI`, `ACWI`). Defaults to `^OMXSPI` if the flag is passed without a ticker value. Specifying `--beta` automatically enables risk metrics.\n\n### Guidelines: When to use what date boundaries\n1. **To see how cohorts from a certain period look today:**\n   Use `--cohorts-start YYYY-MM` / `--cohorts-end YYYY-MM`\n   *Example:* `python scripts/cli.py stats --cohorts-start 2024-01`\n2. **To see all cohorts' performance over a specific valuation window:**\n   Use `--from YYYY-MM` / `--to YYYY-MM` (or `--as-of YYYY-MM`)\n   *Example:* `python scripts/cli.py stats --from 2024-01 --to 2024-12`\n3. **To see only a single cohort month or year:**\n   Use `--cohort YYYY-MM` or `--cohort YYYY`\n   *Example:* `python scripts/cli.py stats --cohort 2024-12` (sets date range to `2024-12` and default grouping to monthly)\n   *Example:* `python scripts/cli.py stats --cohort 2024` (sets date range to `2024-01` to `2024-12` and default grouping to yearly)\n\n> [!NOTE]\n> In double-snapshot mode (`--from` / `--to`), the cohort-level output displays **`Start Value`** instead of **`Deposited`** for any cohorts created before the start date. Additionally, the **`Withdrawal`** line displays withdrawals made *specifically within the selected date range*, while withdrawals made prior to the start date are already accounted for in `Start Value`.\n\n### Settings Subcommands\n- `default-accounts ACCOUNTS`: Set default accounts (comma-separated list of IDs, or `all`)\n- `default-stats-period {month,year}`: Set default period for performance reports\n\n## Virtual Portfolios\n\nVirtual portfolios let you track sub-strategies (e.g. \"YOLO bets\", \"long-term holds\") *within* a single physical Avanza account. A virtual portfolio is just another account in the database (`is_virtual = 1`, linked to a parent). Because shares are **reassigned** (not copied) to the virtual account, every share and every SEK lives on exactly one account at a time — aggregates do not double count.\n\nAll account management — sub-portfolios **and** nicknames — lives under the `account` command.\n\n### Commands\n\n```bash\n# Create a virtual sub-portfolio under a physical parent (optionally fund it)\npython cli.py account create --name \"YOLO\" --parent 1234567 [--starting-cash 5000 --starting-cash-date 2026-07-19]\n\n# Allocate an imported transaction (full, or partial via --shares)\npython cli.py account allocate --tx-date 2026-07-19 --tx-asset \"Some Meme Stock\" --to \"YOLO\" [--shares 50]\n\n# Move cash between accounts\npython cli.py account transfer-cash --amount 10000 --from 1234567 --to \"YOLO\" --date 2026-07-19\n\n# Move an asset position between accounts\npython cli.py account transfer --asset \"Tesla\" --shares 50 --from \"YOLO\" --to 1234567 --date 2026-09-01\n\n# List virtual sub-portfolios with current value and APY\npython cli.py account list [--apy-mode twrr] [--format json]\n\n# Close a sub-portfolio: move all holdings + residual cash back to its parent\npython cli.py account close --name \"YOLO\" --date 2026-09-01\n\n# Account nicknames (moved here from `settings account-nickname`)\npython cli.py account nickname 1234567 \"Main\"\npython cli.py account nickname --list\npython cli.py account nickname --remove 1234567\n```\n\n### How it works\n\n- **`allocate`** moves a transaction (or splits it) onto the virtual account. Moving a buy transfers only the **shortfall** — if the virtual already has capital (e.g. from a prior sell), that cash is used and no transfer is needed. Partial splits proportionally divide `total` and `courtage`.\n- **`allocate --to <parent>`** (undo) moves a transaction back from a virtual to the parent and **deletes** the funding transfer pair that was created during the original allocation. No compensating transactions are created. Requires `--from <virtual>`. Partial undo (`--shares`) is not supported.\n- **`transfer`** (asset move) is represented internally as a sell on the source → cash transfer → rebuy on the destination (all tagged as synthetic). This composes the existing transaction handlers and is correct on every statistics path. The **source realizes its gain** up to the transfer and the **destination gets a fresh cost basis** at the transfer price — an honest \"this position left / entered the strategy\" bookkeeping.\n- **`close`** moves every holding (via the same decomposition) plus any residual cash back to the parent, then reprocesses. The virtual account row is **preserved** (kept `is_virtual = 1`) so its historical cohort/performance data remains queryable; it simply ends up empty.\n- **`delete`** is a clean teardown: reverts all real transactions back to the parent, removes every synthetic transaction tied to the virtual (including partner legs on other accounts), and deletes the account row. Unlike `close`, it leaves no trace — use it to correct a mistake rather than wind down a strategy. **Irreversible:** the virtual's historical performance data is lost on delete; use `close` instead if you want to preserve queryable history. Requires confirmation (y/N prompt, or `--yes` for non-interactive use) and writes an automatic timestamped `.bak` backup of the database before any changes.\n- After every `account` mutation the cohort tables are rebuilt automatically (same reprocessing as an import).\n\n### Viewing virtual portfolios\n\n- `accounts` shows a hierarchical tree: each physical account lists its combined value (self + its virtual children), with the children indented and marked `[V]`. The `TOTAL` row sums physical rows only (children are a breakdown, so nothing is double counted).\n- `stats` / `portfolio` default to **physical accounts only**. Pass `--account all` to include virtual portfolios, or `--account \"YOLO\"` to view a single virtual portfolio.\n\n### SQL views (for dashboards / reports)\n\nThe SQLite database exposes virtual-portfolio-aware views that Grafana panels, weekly reports, or ad-hoc queries can consume directly. All views are recreated on every connect, so they always reflect the current schema.\n\n| View | One row per | Key columns |\n| :--- | :--- | :--- |\n| `v_account_current_valuations` | account | `account`, `is_virtual`, `parent_account`, `display_name`, `cash`, `assets`, `total` |\n| `v_account_asset_holdings` | (account, asset) | `account`, `is_virtual`, `parent_account`, `asset_name`, `held_amount` |\n| `v_virtual_portfolio_rollup` | **physical** account | `parent_account`, `own_total`, `virtual_total`, `combined_total`, `virtual_count` (+ own/virtual/combined cash & assets) |\n| `v_external_capital_flows` | capital-flow transaction | `date`, `account`, `transaction_type`, `origin`, `flow_amount` |\n\n`is_virtual`/`parent_account` let you group or filter (e.g. physical-only with `WHERE is_virtual = 0`, or hierarchy with `GROUP BY parent_account`). The rollup view gives the \"main account including its virtuals\" total with no double counting — `combined_total = own_total + virtual_total`. The flows view's `origin` (`'avanza'` vs `'virtual'`) lets you exclude internal virtual transfers when computing real money in/out.\n\n```sql\n-- Physical accounts with their virtual sub-portfolios rolled in:\nSELECT parent_display_name, own_total, virtual_total, combined_total\nFROM v_virtual_portfolio_rollup;\n\n-- Current holdings grouped by parent family:\nSELECT COALESCE(parent_account, account) AS family, asset_name, SUM(held_amount)\nFROM v_account_asset_holdings GROUP BY family, asset_name;\n```\n\n### Reporting\n\n```bash\n# Full report: overview + accounts hierarchy + virtual section + comparison\npython cli.py report --update-prices auto\n\n# Add a benchmark to the performance comparison (annualized over portfolio lifetime)\npython cli.py report --benchmark ^OMXSPI\n\n# Machine-readable\npython cli.py report --format json\n```\n\nThe `report` command shows the total portfolio value (physical own + virtual),\nper-account and per-virtual APY, each virtual's share of its parent's combined\nvalue, and a comparison table of physical / virtual / benchmark returns. It\nreuses the stats engine for APY and Yahoo Finance (when `--benchmark` is given)\nfor the benchmark period return.\n\n### Limitations\n\n- **Sells and dividends are auto-routed.** When an imported sell or dividend arrives on an account that does not hold the asset (because the shares were allocated to a virtual), `import` automatically redistributes it to the account(s) that hold the shares, so reprocessing never aborts and income is attributed correctly:\n  - **Sells**: drain the sell's own account first, then the largest related virtual holder; full reassignment when the own account holds none, proportional split when it holds some but not enough. Multi-holder cases route to the largest with a warning.\n  - **Dividends**: split proportionally across every holder so each account is credited for the shares it actually holds.\n  - This runs only when virtual portfolios exist (no-op otherwise).\n- **Buys can be auto-allocated with `import --allocate-virtual`.** When a buy on a parent account cannot be funded (because cash is stranded in a virtual from a prior sell), the flag applies heuristics to assign it to the right virtual:\n  1. A virtual that **both holds the asset and can fund the buy** → allocate there (largest holder if multiple, with a warning).\n  2. A **new asset** (nobody holds it) and exactly one virtual can fund → allocate there.\n  3. Everything else → left on the parent with a warning for manual allocation.\n  - \"Can fund\" means the virtual's capital plus the parent's remaining capital covers the buy cost. The shortfall is transferred from the parent.\n  - If a heuristic allocation is wrong, undo it with `account allocate --to <parent> --from <virtual>`.\n- Funding a virtual requires the source account to hold enough capital at the transfer date.\n\n## Special Cases\n\nCorporate actions (splits, spin-offs, zero-priced deposits) can be overridden by copying the template and defining rules:\n```bash\ncp assets/special_cases_template.json ../data/avanza/special_cases.json\n```\n\n## Contributing\n\nThank you for your interest in contributing to this project! As a single-person hobby project, contributions are not expected but always welcome. If you have any ideas, bug fixes, or improvements, feel free to submit a pull request.\n\nTo contribute to this project, please follow these guidelines:\n\n1. Fork the repository and create a new branch for your contribution.\n2. Make your changes and ensure that the code is clean and well-documented.\n3. Test your changes thoroughly to ensure they do not introduce any regressions.\n4. Submit a pull request, explaining the purpose and details of your contribution.\n\nPlease note that as a hobby project, there may be limited resources available for reviewing and merging pull requests. Your patience is appreciated.\n\nThank you for your support and happy coding!\n\n## License\n\nPlease make sure that you are allowed to access information from the price source that you are using. The author of this project is not responsible for any legal issues that may arise from the use of this project. The url used in the example script is only for demonstration purposes and should not be used without permission.\n\nThe code in this project is licensed under the MIT License. See [LICENSE](LICENSE) file for details.\n\nPlease note that this project uses other libraries. The licenses for these libraries are as follows:\n\n- Libraries in `requirements.txt`:\n  - requests: Apache License 2.0\n\n\nPlease respect the licenses for these libraries when using this project.\n\nFile v2.14.0:_meta.json\n\n{\n  \"ownerId\": \"kn7cxaw1m27d1fe93wbz40vqkd8309h7\",\n  \"slug\": \"avanza-investment-tracker\",\n  \"version\": \"2.14.0\",\n  \"publishedAt\": 1788175835413\n}\n\nFile v2.14.0:references/troubleshooting.md\n\n# Troubleshooting\n\n**Database locked:** Close SQLite browsers\n**Import fails:** Check CSV is Avanza format, UTF-8\n**Missing prices:** Check internet, try without --update-prices\n**Path errors:** Run from skill root, use data/file.csv\n\nFile v2.14.0:references/workflows.md\n\n# Workflows\n\n## First-Time Setup\n\n```bash\npip install -r requirements.txt\nmkdir -p data\ncp assets/special_cases_template.json data/special_cases.json\n# Edit data/special_cases.json if you have corporate actions\npython scripts/cli.py import data/transactions.csv\npython scripts/cli.py stats --update-prices auto\n```\n\n## Adding More Data\n\n```bash\npython scripts/cli.py import data/new_transactions.csv\npython scripts/cli.py stats\n```\n\n## Reset Everything\n\n```bash\n# Soft reset: mark all transactions as unprocessed\npython scripts/cli.py reset\n\n# Hard reset: delete all transactions, stats, and prices\npython scripts/cli.py reset --hard\n```\n\nFile v2.14.0:skill-card.md\n\n## Description:\n\nProcesses Avanza CSV exports, tracks local portfolio data, calculates TWRR and Modified Dietz returns, and supports portfolio, account, and risk reporting through a Python CLI.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[patello](https://clawhub.ai/user/patello)\n\n### License/Terms of Use:\n\nMIT\n\n## Use Case:\n\nExternal users and developers use this skill to import Avanza transaction CSVs, keep local investment records, calculate portfolio performance, and prepare account or portfolio reports.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The skill can store sensitive financial information in a local SQLite database.\n\nMitigation: Use it only with financial data you are comfortable storing locally and keep private data outside the skill directory.\n\nRisk: Live price and risk metric options can make outbound requests involving portfolio asset names, currency pairs, benchmark tickers, and date ranges.\n\nMitigation: Use --update-prices never for offline operation and avoid risk or beta options when external lookups are not acceptable.\n\nRisk: Reset, delete-tx, account allocate --undo, and account delete operations can permanently remove records.\n\nMitigation: Back up the database before destructive operations, prefer delete-tx --dry-run, and avoid broad selectors unless the removal scope is clear.\n\nRisk: Dependency currency matters for HTTP request handling.\n\nMitigation: Update or pin requests to a fixed version at or above 2.33.0.\n\n## Reference(s):\n\n- [ClawHub Skill Page](https://clawhub.ai/patello/skills/avanza-investment-tracker)\n- [README](README.md)\n- [Workflows](references/workflows.md)\n- [Troubleshooting](references/troubleshooting.md)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, JSON, Shell commands, Configuration]\n\n**Output Format:** [Markdown guidance with inline shell commands; CLI output may be tables or JSON.]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Commands may create or update a local SQLite database and timestamped backup files.]\n\n## Skill Version(s):\n\n2.14.0 (source: server release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nFile v2.14.0:assets/special_cases_template.json\n\n[\n    {\n        \"condition\": [\n            {\n                \"index\": 3,\n                \"value\": \"EXAMPLE FUND OLD NAME\"\n            },\n            {\n                \"index\": 0,\n                \"value\": \"2022-12-12\",\n                \"operator\": \"<=\"\n            }\n        ],\n        \"replacement\": [\n            {\n                \"index\": 3,\n                \"value\": \"EXAMPLE FUND NEW NAME\"\n            }\n        ]\n    },\n    {\n        \"condition\": [\n            {\n                \"index\": 9,\n                \"value\": \"ISIN1234567890\"\n            },\n            {\n                \"index\": 2,\n                \"value\": \"Värdepappersinsättning\"\n            }\n        ],\n        \"replacement\": [\n            {\n                \"index\": 2,\n                \"value\": \"Tillgångsinsättning\"\n            },\n            {\n                \"index\": 3,\n                \"value\": \"CORRECT ASSET NAME\"\n            }\n        ]\n    },\n    {\n        \"condition\": [\n            {\n                \"index\": 9,\n                \"value\": \"OLDISIN12345678\"\n            },\n            {\n                \"index\": 2,\n                \"value\": \"Värdepappersuttag\"\n            }\n        ],\n        \"replacement\": [\n            {\n                \"index\": 3,\n                \"value\": \"CORRECT OLD ASSET NAME\"\n            }\n        ]\n    },\n    {\n        \"condition\": [\n            {\n                \"index\": 9,\n                \"value\": \"ISIN9876543210\"\n            },\n            {\n                \"index\": 2,\n                \"value\": \"Värdepappersinsättning\"\n            }\n        ],\n        \"replacement\": [\n            {\n                \"index\": 2,\n                \"value\": \"Tillgångsinsättning\"\n            },\n            {\n                \"index\": 3,\n                \"value\": \"EXAMPLE ASSET CUSTOM COST BASIS\"\n            },\n            {\n                \"index\": 5,\n                \"value\": \"123.45\"\n            }\n        ]\n    }\n]\n\nFile v2.14.0:test/data/special_cases_test.json\n\n[\n    {\n        \"condition\": [\n            {\n            \"index\":0,\n            \"value\":\"2022-12-12\",\n            \"operator\":\"<=\"\n            },\n            {\n            \"index\":3,\n            \"value\":\"DNB SMB\"\n            }],\n        \"replacement\":[{\n            \"index\":3,\n            \"value\":\"DNB SMB A\"\n        }]\n    },\n    {\n        \"condition\": [{\n            \"index\":1,\n            \"value\":\"One Replacement\"\n            }],\n        \"replacement\":[{\n            \"index\":2,\n            \"value\":\"Replaced\"\n        }]\n    },\n    {\n        \"condition\": [{\n            \"index\":1,\n            \"value\":\"Two Replacements\"\n            }],\n        \"replacement\":[{\n            \"index\":2,\n            \"value\":\"Replaced1\"\n        },\n        {\n            \"index\":3,\n            \"value\":\"Replaced2\"\n        }]\n    },\n    {\n        \"condition\": [{\n            \"index\":3,\n            \"value\":\"COMPUTER INN\"\n            }],\n        \"replacement\":[{\n            \"index\":3,\n            \"value\":\"COMPUTER INNOVATION\"\n        }]\n    }\n]\n\nFile v2.14.0:changelog.txt\n\n- docs + safeguards: address ClawHub security audit findings (#86)\n\nFile v2.14.0:LICENSE\n\nMIT License\n\nCopyright (c) 2023 Patrik Ekenberg\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\nFile v2.14.0:pytest.ini\n\n[pytest]\nminversion = 6.0\ntestpaths =\n    test\npythonpath = .\nlog_cli = 1\nlog_cli_level = WARNING\nlog_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)\nlog_cli_date_format=%Y-%m-%d %H:%M:%S\n\nFile v2.14.0:requirements.txt\n\nrequests>=2.32.4\n\nArchive v2.13.0: 59 files, 159980 bytes\n\nFiles: _meta.json (145b), assets/special_cases_template.json (1911b), changelog.txt (178b), LICENSE (1071b), pytest.ini (219b), README.md (29203b), references/troubleshooting.md (234b), references/workflows.md (638b), requirements.txt (8b), scripts/calculate_stats.py (72055b), scripts/cli.py (139823b), scripts/data_parser.py (65416b), scripts/database_handler.py (36743b), scripts/risk_calculator.py (29854b), skill-card.md (2301b), SKILL.md (7664b), test/conftest.py (1091b), test/data/asset_deposit.csv (311b), test/data/fraction_writeoff.csv (352b), test/data/interest_fees.csv (601b), test/data/listing_change.csv (610b), test/data/multiple_transfers_same_day.csv (565b), test/data/new_format_data.csv (537b), test/data/new_transaction_types.csv (571b), test/data/reordered_data.csv (716b), test/data/small_data_diff_accounts.csv (1258b), test/data/small_data_plus.csv (1251b), test/data/small_data_wrong_accounts.csv (1258b), test/data/small_data.csv (716b), test/data/special_cases_test.json (1024b), test/data/transfer_attribution_single_account.csv (359b), test/data/transfer_attribution_two_accounts.csv (578b), test/data/transfer_deferral.csv (663b), test/data/transfer_proper_order.csv (663b), test/test_account_apy.py (11114b), test/test_accounts.py (57907b), test/test_add_data.py (5128b), test/test_asset_deposit_issue.py (4602b), test/test_database_handler.py (4623b), test/test_delete_tx.py (5958b), test/test_export.py (4672b), test/test_fx_conversion.py (16660b), test/test_historical_prices.py (22477b), test/test_import_normalization.py (3047b), test/test_import_unsettled.py (2616b), test/test_modified_dietz.py (3608b), test/test_parse_data.py (8129b), test/test_parse_special_cases.py (2042b), test/test_per_account_asset_tracking.py (11926b), test/test_portfolio_holdings.py (6187b), test/test_price_interpolation.py (6143b), test/test_price_staleness.py (5412b), test/test_price_updates.py (2398b), test/test_process_data_attribution.py (4242b), test/test_process_data_deferral.py (3929b), test/test_retroactive_updates.py (4460b), test/test_risk_metrics.py (11534b), test/test_stats_positions.py (13316b), test/test_twrr.py (5026b)\n\nFile v2.13.0:SKILL.md\n\n---\nname: avanza-investment-tracker\ndescription: \"Process Avanza CSV exports, calculate TWRR/Modified Dietz returns, and track portfolio performance. Use when importing stock transactions, calculating investment returns, or managing portfolio data.\"\n---\n\n# Avanza Investment Tracker\n\nParse transaction CSVs and compute portfolio performance metrics.\n\n## Quick Start\n\nRun commands from your workspace root, specifying the paths to your database and CSV:\n\n```bash\n# 1. Import new transactions\npython path/to/cli.py --database data/asset_data.db import path/to/transactions.csv\n\n# 2. Update price cache and show statistics\npython path/to/cli.py --database data/asset_data.db stats --update-prices auto\n\n# 3. View portfolio allocation and APY\npython path/to/cli.py --database data/asset_data.db portfolio --account default\n```\n\n## Data Storage Pattern\n\n**User data lives OUTSIDE the skill directory.** Recommended structure:\n\n```\nworkspace-finance/\n├── skills/avanza-investment-tracker/   # Portable skill logic\n│   ├── SKILL.md\n│   ├── scripts/\n│   └── assets/\n└── data/avanza/                        # Private portfolio data\n    ├── transactions.csv\n    ├── special_cases.json\n    └── asset_data.db\n```\n\n## CLI Reference\n\n| Command | Description |\n| :--- | :--- |\n| `python scripts/cli.py import FILE [--allocate-virtual] [--allow-unsettled]` | Import transaction entries from Avanza CSV (auto-allocate buys to virtuals; defers unsettled/pending-nota trades — see CSV Format Support) |\n| `python scripts/cli.py stats [OPTIONS]` | Calculate and display cohort performance statistics (TWRR, deposits) |\n| `python scripts/cli.py accounts [OPTIONS]` | Display summary of all accounts with asset values and cash |\n| `python scripts/cli.py portfolio [OPTIONS]` | Show portfolio holdings, market value, allocation %, and APY (alias to `stats --positions --summary`) |\n| `python scripts/cli.py status` | Display system status (transaction counts, price dates, date range) |\n| `python scripts/cli.py settings SUBCOMMAND` | Configure defaults and account nicknames |\n| `python scripts/cli.py reset [--hard]` | Reset database state (`--hard` deletes data; default only marks unprocessed) |\n| `python scripts/cli.py delete-tx [OPTIONS]` | Delete individual transaction(s) by `--tx-id`, `--date`+`--asset`, or `--since`, then rebuild derived tables (see below) |\n| `python scripts/cli.py account SUBCOMMAND` | Manage accounts — virtual sub-portfolios (create/allocate/transfer/list/close/delete) and nicknames (see below) |\n| `python scripts/cli.py report [OPTIONS]` | Investment report with a virtual-portfolio section and a virtual-vs-parent-vs-benchmark comparison |\n\n### Deleting transactions\n\n`delete-tx` removes specific real transactions and rebuilds the derived `assets` / cohort tables, so there is no need to `reset` the whole database after a bad import (e.g. a duplicate, or a row that slipped in before an unsettled trade was deferred). Targeting is mutually exclusive:\n\n- `delete-tx --tx-id ROWID` — most precise (use `status`/`export` to find the rowid).\n- `delete-tx --date YYYY-MM-DD --asset \"Name\" [--account ACCOUNT]` — the common surgical case.\n- `delete-tx --since YYYY-MM-DD [--account ACCOUNT]` — remove everything from a date onward (e.g. undo today's import).\n\n`--cascade` widens a `--date`+`--asset` match across the account family (parent + its virtuals) so a trade and its allocated split are removed together; `--dry-run` previews the deletion. When an allocated buy on a virtual is deleted, its orphaned funding `Intern överföring` transfer is removed automatically (mirroring `account allocate --undo`). After every deletion all transactions are reprocessed, so the `assets`/cohort tables always reflect the remaining transactions — never a half-deleted state.\n\n### Global Options\n- `--database PATH` (default: `data/asset_data.db`)\n- `--special-cases PATH` (default: `data/special_cases.json`)\n\n### Calculation & Output Options\n- `--account ACCOUNTS`: Limit to specific accounts (e.g. `12345,67890`, `default`, or `all`). Omitting the flag (default) shows **physical accounts only** (excludes virtual portfolios); pass `all` to include virtual portfolios in aggregates.\n- `--update-prices {auto,always,never}` (stats only): Controls when to fetch latest stock/fund prices from Avanza API\n- `--update-all` (stats only): Update prices for all assets in the database, held or not\n- `--as-of DATE`: View snapshot/stats as of a historical date (`YYYY-MM-DD`)\n- `--cohorts-start DATE --cohorts-end DATE`: Filter which deposit cohorts are displayed\n- `--cohort DATE`: Shorthand to filter by a single cohort month (`YYYY-MM`) or year (`YYYY`) (e.g. `--cohort 2024` groups yearly, `--cohort 2024-12` groups monthly)\n- `--from DATE --to DATE`: Set the performance valuation window (double snapshot)\n- `--positions`, `-p` (stats only): Show positions holdings breakdown under each cohort (or summary)\n- `--summary`, `-s` (stats only): Consolidate cohort statistics into a single overview block\n- `--apy-mode {mwrr,twrr}`: APY calculation method (`mwrr` uses Modified Dietz; `twrr` uses Time-Weighted)\n- `--format {table,json}`: Output formatting (default: `table`)\n- `--quiet`, `-q`: Suppress price data staleness warnings\n- `--no-interpolation`: Disable linear interpolation for sparse historical price data (falls back to nearest prior price, which may trigger staleness warnings)\n- `--risk`: Calculate and display portfolio-level risk metrics (Annualized Standard Deviation, Sharpe Ratio, Sortino Ratio, Maximum Drawdown with peak/trough calendar months)\n- `--beta [TICKER]`: Include the portfolio Beta calculation vs the specified benchmark (e.g. `^OMXSPI`, `ACWI`). Defaults to `^OMXSPI` if the flag is passed without a ticker value. Specifying `--beta` automatically enables risk metrics.\n\n### Guidelines: When to use what date boundaries\n1. **To see how cohorts from a certain period look today:**\n   Use `--cohorts-start YYYY-MM` / `--cohorts-end YYYY-MM`\n   *Example:* `python scripts/cli.py stats --cohorts-start 2024-01`\n2. **To see all cohorts' performance over a specific valuation window:**\n   Use `--from YYYY-MM` / `--to YYYY-MM` (or `--as-of YYYY-MM`)\n   *Example:* `python scripts/cli.py stats --from 2024-01 --to 2024-12`\n3. **To see only a single cohort month or year:**\n   Use `--cohort YYYY-MM` or `--cohort YYYY`\n   *Example:* `python scripts/cli.py stats --cohort 2024-12` (sets date range to `2024-12` and default grouping to monthly)\n   *Example:* `python scripts/cli.py stats --cohort 2024` (sets date range to `2024-01` to `2024-12` and default grouping to yearly)\n\n> [!NOTE]\n> In double-snapshot mode (`--from` / `--to`), the cohort-level output displays **`Start Value`** instead of **`Deposited`** for any cohorts created before the start date. Additionally, the **`Withdrawal`** line displays withdrawals made *specifically within the selected date range*, while withdrawals made prior to the start date are already accounted for in `Start Value`.\n\n### Settings Subcommands\n- `default-accounts ACCOUNTS`: Set default accounts (comma-separated list of IDs, or `all`)\n- `default-stats-period {month,year}`: Set default period for performance reports\n\n\n## Special Cases\n\nCorporate actions (splits, spin-offs, zero-priced deposits) can be overridden by copying the template and defining rules:\n```bash\ncp assets/special_cases_template.json ../data/avanza/special_cases.json\n```\n\n\n## See Also\n\n- **Detailed workflows**: [references/workflows.md](references/workflows.md)\n- **Troubleshooting guide**: [references/troubleshooting.md](references/troubleshooting.md)\n\nFile v2.13.0:README.md\n\n# Investment Tracker\n\n## Description\n\nThis project aims to create a tool for tracking stocks and investments on investment platforms. The original idea for this project was when I was about to buy a SteamDeck. Then I thought better of it and started to wonder how much the money would grow over time if I invested it instead. I realized that it would require me to keep track of the assets that I purchase a particular month, even if I sell them and buy new ones later on. Or if I get dividends and reinvest them.\n\nWith this project, I am able to parse data from my investment platform, keep latest asset values up to date and calculate relevant statistics.\n\nThis project is a work in progress and will be updated as I go along.\n\n## Table of Contents\n\n- [Features](#features)\n- [Installation](#installation)\n- [Usage](#usage)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Features\n\n- Parse and store data from an investment platform.\n- Keep track of where money invested each month is moved and grown over time.\n- **Per-account statistics**: Calculate gains/losses for each account separately, then merge for combined views\n- Calculate statistics for months and years:\n    - Deposit\n    - Withdrawal\n    - Current Value\n    - Total Gain/Loss\n    - Realized Gain/Loss\n    - Unrealized Gain/Loss\n    - APY (Annual Percentage Yield)\n- Two viewing modes:\n    - **Period-specific**: Track performance of investments made in each month/year\n    - **Accumulated**: See total portfolio value over time with assets carried forward\n- **Account filtering**: View statistics for any combination of accounts with full accumulated history support\n- **System status**: Check database statistics, price freshness, and transaction date range\n\n## Installation\n\n1. Clone the repository.\n2. Install the dependencies with `pip install -r requirements.txt`.\n\n## Quick Start\n\n```bash\n# 1. Import your transaction data\npython cli.py import data/your_transactions.csv\n\n# 2. Get statistics with automatic price updates\npython cli.py stats --update-prices auto --period year --deposits all\n\n# 3. Check system status anytime\npython cli.py status\n\n# 4. (Optional) Set default accounts for filtering\npython cli.py settings default-accounts \"account1,savings_account\"\n\n# 5. (Optional) Set account nicknames for readability\npython cli.py account nickname 1234567 \"Savings\"\n\n# 6. View account summaries\npython cli.py accounts --update-prices auto\n```\n\n## Usage\n\n### Command Line Interface (CLI)\n\nA unified CLI is available via `cli.py`. It provides subcommands for all major operations:\n\n#### Modern Workflow (Recommended)\n\n```bash\n# Import CSV data and process transactions in one atomic operation\npython cli.py import data/transactions.csv\n\n# Show statistics with smart updates (auto-updates prices if stale)\npython cli.py stats --update-prices auto --period year --deposits all\n\n# Check system status (transactions, prices, metadata)\npython cli.py status\n\n# Reset database state (mark all transactions as unprocessed)\npython cli.py reset\n\n# Hard reset (delete all transactions, stats, and prices while keeping configuration)\npython cli.py reset --hard\n```\n\nAll commands accept optional `--database` and `--special-cases` arguments to override default paths:\n\n```bash\npython cli.py --database path/to/db.db --special-cases path/to/special.json import data.csv\n```\n\n#### Smart Update Features\n\nThe new `stats` command includes intelligent caching and update logic:\n\n- **Price freshness**: Prices are considered \"fresh\" if updated within 1 day\n- **Price interpolation**: Historical valuations automatically interpolate missing asset prices linearly between nearest known dates (e.g. from transaction purchases or API price updates).\n  - Use `--no-interpolation` to disable this behavior and fall back to the closest prior price.\n- **Auto-update**: `--update-prices auto` updates only if prices are stale\n- **Force update**: `--update-prices always` forces price refresh\n- **Skip update**: `--update-prices never` uses cached prices\n- **Stats caching**: Statistics are recalculated only when needed (new transactions or price updates)\n- **Default period**: You can set a default stats cohort period.\n  ```bash\n  # Set default stats period\n  python cli.py settings default-stats-period year\n\n  # Use the default stats period\n  python cli.py stats\n  ```\n\n\n#### Account Nicknames\n\nAssign human-readable names to account numbers for easier identification:\n\n```bash\n# Set a nickname for an account\npython cli.py account nickname 1234567 \"Savings\"\n\n# List all nicknames\npython cli.py account nickname --list\n\n# Remove a nickname\npython cli.py account nickname --remove 1234567\n```\n\nNicknames are stored in the database and displayed in the `accounts` command output alongside account numbers.\n\n#### Account Filtering\n\nThe CLI supports filtering statistics by account with the `--account` flag. Statistics are calculated per-account for accuracy, then merged when viewing multiple accounts:\n\n```bash\n# Show stats for all accounts (default)\npython cli.py stats --account all\n\n# Show stats using default accounts set via settings\npython cli.py stats --account default\n\n# Show stats for specific accounts (comma-separated)\npython cli.py stats --account \"account1,savings_account\"\n\n# Accumulated statistics now work with any account combination\npython cli.py stats --account \"account1\" --accumulated\n```\n\nSet default accounts for filtering:\n```bash\n# Set default accounts\npython cli.py settings default-accounts \"account1,savings_account\"\n\n# Reset to include all accounts\npython cli.py settings default-accounts all\n```\n\nView account summaries with cash and asset values:\n```bash\n# Show all accounts\npython cli.py accounts --update-prices auto\n\n# Filter accounts (same syntax as stats command)\npython cli.py accounts --account default\n\n# View portfolio snapshot for specific accounts\npython cli.py portfolio --account \"account1,savings_account\"\n\n# Compare portfolio values for a specific account over a period\npython cli.py portfolio --account \"account1\" --start-date 2026-06-29 --end-date 2026-07-06\n```\n\nOutput format for `accounts` command:\n```\nAccount                Cash (SEK) Assets (SEK)  Total (SEK)\n--------------------------------------------------------\naccount1                        0       100000       100000\nsavings_account             50000            0        50000\n--------------------------------------------------------\nTOTAL                       50000       100000       150000\n```\n\nOutput format for `portfolio` command (single account):\n```\nAccount account1 (Account Nickname)\nDeposits: 100,000 SEK\nWithdrawals: 0 SEK\nNet invested: 100,000 SEK\nCurrent value: 105,000 SEK\nTotal gain: +5,000 SEK (+5.0%)\nAPY: 10.2% (MWRR)\n\n  Holdings:\n    Fund Name              Market Value    Allocation\n    Asset A                 80,000 SEK         76.2%\n    Asset B                 25,000 SEK         23.8%\n  Total                    105,000 SEK        100.0%\n```\n\nWith `--format json`, the single-account output includes a `holdings` list:\n```json\n{\n  \"account\": \"account1\",\n  \"display_name\": \"Account Nickname\",\n  \"deposits\": 100000.0,\n  \"withdrawals\": 0.0,\n  \"net_invested\": 100000.0,\n  \"current_value\": 105000.0,\n  \"total_gain\": 5000.0,\n  \"total_gain_percent\": 5.0,\n  \"apy\": 10.2,\n  \"apy_mode\": \"mwrr\",\n  \"holdings\": [\n    {\n      \"asset\": \"Asset A\",\n      \"amount\": 80.0,\n      \"price\": 1000.0,\n      \"market_value\": 80000.0,\n      \"allocation_percent\": 76.19\n    },\n    {\n      \"asset\": \"Asset B\",\n      \"amount\": 25.0,\n      \"price\": 1000.0,\n      \"market_value\": 25000.0,\n      \"allocation_percent\": 23.81\n    }\n  ]\n}\n```\n\n\n### Understanding Statistics Output\n\nThe statistics output has two modes that serve different purposes:\n\n#### 1. Regular Statistics (Default)\n```bash\npython cli.py stats --period month\n```\n- **Shows**: Activity for each specific month/year\n- **\"Value\" column**: Current value of deposits made **during that specific period**\n- **If zero deposits in a period**: Value = 0 (by definition)\n- **If everything withdrawn**: Value = 0 (deposits fully withdrawn)\n- **Use case**: Track performance of investments made in each period\n\n#### 2. Accumulated Statistics\n```bash\npython cli.py stats --period month --accumulated\n```\n- **Shows**: Cumulative portfolio value over time\n- **\"Value\" column**: Total portfolio value at period end (all assets held)\n- **Carries forward**: Assets from earlier periods continue to appear\n- **Use case**: See total portfolio growth over time\n\n#### 3. APY Calculation Modes\n\nYou can specify the method used to calculate APY using the `--apy-mode` argument (supported by `stats` and `portfolio` commands):\n- `--apy-mode mwrr` (default): Money-Weighted Rate of Return. This is implemented using the **Modified Dietz** method. It accounts for the timing and size of all cash flows (deposits and withdrawals) in the period.\n- `--apy-mode twrr`: Time-Weighted Rate of Return. This measures pure investment performance independent of cash flow timing.\n\n#### Example\n**January 2024:**\n- Deposit: 10,000 SEK\n- Buy Asset A: 10,000 SEK\n- Current price of Asset A: 12,000 SEK\n\n**February 2024:**\n- No deposits/withdrawals\n- Asset A still held (worth 12,000 SEK)\n\n**Regular stats show:**\n- January: Value = 12,000 SEK (value of January deposit)\n- February: Value = 0 (no February deposits)\n\n**Accumulated stats show:**\n- January: Value = 12,000 SEK\n- February: Value = 12,000 SEK (asset carried forward)\n\n### CSV Format Support\n\nThe parser automatically detects whether your CSV file uses Avanza's old or new export format (the new format includes a `Transaktionsvaluta` column). The following transaction types are recognized:\n\n- Insättning (deposit)\n- Uttag (withdrawal)\n- Köp (purchase)\n- Sälj (sale)\n- Utdelning (dividend)\n- Räntor / Ränta / Inlåningsränta (interest)\n- Utländsk källskatt / Prelskatt / Preliminärskatt (taxes)\n- Byte / Övrigt (listing changes)\n- Tillgångsinsättning (asset deposit)\n\nEmpty numeric fields (like `Antal`, `Kurs`) are treated as zero.\n\n**Asset name normalization:** Instrument names are trimmed of leading/trailing whitespace on import — Avanza exports occasionally include stray trailing spaces that would otherwise break exact-name lookups (e.g. `account allocate`). Any pre-existing names are normalized automatically the next time the database is opened.\n\n**Unsettled trades (pending nota):** When you export on the same day as a non-SEK securities trade, Avanza includes preliminary rows whose brokerage (`Courtage`) and FX rate (`Valutakurs`) are not yet finalized. The importer detects these (non-SEK buy/sell with empty `Courtage` and `Valutakurs`) and defers them — it logs a warning per skipped row and leaves them out of the database. Re-export after the trade settles and re-import. Pass `import --allow-unsettled` to override and ingest them anyway.\n\n**Price fetching note:** The `stats` command (with `--update-prices auto` or `--update-prices always`) fetches current asset prices from Avanza's public search API (`www.avanza.se/_api/search/filtered-search`). This API is intended for web frontend use and may have rate limits or terms of service restrictions. Use at your own risk and consider using official APIs if available. Always review the website's terms of service before using their data.\n\n### Using the CLI (Recommended)\n\nThe unified CLI provides all functionality in a streamlined interface:\n\n1. Add transaction data in CSV format to the `data` folder.\n    - You might need to create a \"special_cases.json\" file in order to match and replace certain values in the data. See file specification in the documentation for the SpecialCases class.\n2. Import and process transactions: `python cli.py import data/your_transactions.csv`\n3. View statistics with automatic price updates: `python cli.py stats --update-prices auto`\n4. (Optional) Set default accounts for filtering: `python cli.py settings default-accounts \"account1,savings_account\"`\n5. (Optional) Set account nicknames: `python cli.py account nickname 1234567 \"Savings\"`\n6. View account summaries: `python cli.py accounts --update-prices auto`\n7. View portfolio snapshot or compare change over a period (equivalent to `stats --positions --summary`):\n    - Snapshot: `python cli.py portfolio [--as-of YYYY-MM-DD]`\n    - Period comparison: `python cli.py portfolio --start YYYY-MM-DD [--end YYYY-MM-DD]`\n8. Check system status: `python cli.py status`\n\nNote: All valuation commands (`stats`, `accounts`, `portfolio`) accept a `--format json` flag to return data in machine-readable JSON format instead of a plain-text table/block.\n\n**Advanced usage with account filtering:**\n```bash\n# Show stats for specific accounts\npython cli.py stats --account \"account1\" --update-prices auto\n\n# Show accumulated stats for any account combination\npython cli.py stats --account \"account1\" --accumulated --update-prices auto\npython cli.py stats --account \"account1,savings_account\" --accumulated --update-prices auto\n\n# Compare different account combinations\npython cli.py accounts --account \"account1\"\npython cli.py accounts --account \"savings_account\"\npython cli.py accounts --account all\n\n# Show portfolio snapshot as of a previous date\npython cli.py portfolio --as-of 2026-07-06\n\n# Compare portfolio values between two dates\npython cli.py portfolio --start 2026-06-29 --end 2026-07-06\n\n# Show portfolio snapshot with Money-Weighted Rate of Return (MWRR) APY\npython cli.py portfolio --account \"account1\"\n\n# Show portfolio snapshot using Time-Weighted Rate of Return (TWRR) APY\npython cli.py portfolio --account \"account1\" --apy-mode twrr\n\n```\n\n## CLI Reference\n\n| Command | Description |\n| :--- | :--- |\n| `python scripts/cli.py import FILE [--allocate-virtual] [--allow-unsettled]` | Import transaction entries from Avanza CSV (auto-allocate buys to virtuals; defers unsettled/pending-nota trades — see CSV Format Support) |\n| `python scripts/cli.py stats [OPTIONS]` | Calculate and display cohort performance statistics (TWRR, deposits) |\n| `python scripts/cli.py accounts [OPTIONS]` | Display summary of all accounts with asset values and cash |\n| `python scripts/cli.py portfolio [OPTIONS]` | Show portfolio holdings, market value, allocation %, and APY (alias to `stats --positions --summary`) |\n| `python scripts/cli.py status` | Display system status (transaction counts, price dates, date range) |\n| `python scripts/cli.py settings SUBCOMMAND` | Configure defaults and account nicknames |\n| `python scripts/cli.py reset [--hard]` | Reset database state (`--hard` deletes data; default only marks unprocessed) |\n| `python scripts/cli.py delete-tx [OPTIONS]` | Delete individual transaction(s) by `--tx-id`, `--date`+`--asset`, or `--since`, then rebuild derived tables (see below) |\n| `python scripts/cli.py account SUBCOMMAND` | Manage accounts — virtual sub-portfolios (create/allocate/transfer/list/close/delete) and nicknames (see below) |\n| `python scripts/cli.py report [OPTIONS]` | Investment report with a virtual-portfolio section and a virtual-vs-parent-vs-benchmark comparison |\n\n### Deleting transactions\n\n`delete-tx` removes specific real transactions and rebuilds the derived `assets` / cohort tables, so there is no need to `reset` the whole database after a bad import (e.g. a duplicate, or a row that slipped in before an unsettled trade was deferred). Targeting is mutually exclusive:\n\n- `delete-tx --tx-id ROWID` — most precise (use `status`/`export` to find the rowid).\n- `delete-tx --date YYYY-MM-DD --asset \"Name\" [--account ACCOUNT]` — the common surgical case.\n- `delete-tx --since YYYY-MM-DD [--account ACCOUNT]` — remove everything from a date onward (e.g. undo today's import).\n\n`--cascade` widens a `--date`+`--asset` match across the account family (parent + its virtuals) so a trade and its allocated split are removed together; `--dry-run` previews the deletion. When an allocated buy on a virtual is deleted, its orphaned funding `Intern överföring` transfer is removed automatically (mirroring `account allocate --undo`). After every deletion all transactions are reprocessed, so the `assets`/cohort tables always reflect the remaining transactions — never a half-deleted state.\n\n### Global Options\n- `--database PATH` (default: `data/asset_data.db`)\n- `--special-cases PATH` (default: `data/special_cases.json`)\n\n### Calculation & Output Options\n- `--account ACCOUNTS`: Limit to specific accounts (e.g. `12345,67890`, `default`, or `all`). Omitting the flag (default) shows **physical accounts only** (excludes virtual portfolios); pass `all` to include virtual portfolios in aggregates.\n- `--update-prices {auto,always,never}` (stats only): Controls when to fetch latest stock/fund prices from Avanza API\n- `--update-all` (stats only): Update prices for all assets in the database, held or not\n- `--as-of DATE`: View snapshot/stats as of a historical date (`YYYY-MM-DD`)\n- `--cohorts-start DATE --cohorts-end DATE`: Filter which deposit cohorts are displayed\n- `--cohort DATE`: Shorthand to filter by a single cohort month (`YYYY-MM`) or year (`YYYY`) (e.g. `--cohort 2024` groups yearly, `--cohort 2024-12` groups monthly)\n- `--from DATE --to DATE`: Set the performance valuation window (double snapshot)\n- `--positions`, `-p` (stats only): Show positions holdings breakdown under each cohort (or summary)\n- `--summary`, `-s` (stats only): Consolidate cohort statistics into a single overview block\n- `--apy-mode {mwrr,twrr}`: APY calculation method (`mwrr` uses Modified Dietz; `twrr` uses Time-Weighted)\n- `--format {table,json}`: Output formatting (default: `table`)\n- `--quiet`, `-q`: Suppress price data staleness warnings\n- `--no-interpolation`: Disable linear interpolation for sparse historical price data (falls back to nearest prior price, which may trigger staleness warnings)\n- `--risk`: Calculate and display portfolio-level risk metrics (Annualized Standard Deviation, Sharpe Ratio, Sortino Ratio, Maximum Drawdown with peak/trough calendar months)\n- `--beta [TICKER]`: Include the portfolio Beta calculation vs the specified benchmark (e.g. `^OMXSPI`, `ACWI`). Defaults to `^OMXSPI` if the flag is passed without a ticker value. Specifying `--beta` automatically enables risk metrics.\n\n### Guidelines: When to use what date boundaries\n1. **To see how cohorts from a certain period look today:**\n   Use `--cohorts-start YYYY-MM` / `--cohorts-end YYYY-MM`\n   *Example:* `python scripts/cli.py stats --cohorts-start 2024-01`\n2. **To see all cohorts' performance over a specific valuation window:**\n   Use `--from YYYY-MM` / `--to YYYY-MM` (or `--as-of YYYY-MM`)\n   *Example:* `python scripts/cli.py stats --from 2024-01 --to 2024-12`\n3. **To see only a single cohort month or year:**\n   Use `--cohort YYYY-MM` or `--cohort YYYY`\n   *Example:* `python scripts/cli.py stats --cohort 2024-12` (sets date range to `2024-12` and default grouping to monthly)\n   *Example:* `python scripts/cli.py stats --cohort 2024` (sets date range to `2024-01` to `2024-12` and default grouping to yearly)\n\n> [!NOTE]\n> In double-snapshot mode (`--from` / `--to`), the cohort-level output displays **`Start Value`** instead of **`Deposited`** for any cohorts created before the start date. Additionally, the **`Withdrawal`** line displays withdrawals made *specifically within the selected date range*, while withdrawals made prior to the start date are already accounted for in `Start Value`.\n\n### Settings Subcommands\n- `default-accounts ACCOUNTS`: Set default accounts (comma-separated list of IDs, or `all`)\n- `default-stats-period {month,year}`: Set default period for performance reports\n\n## Virtual Portfolios\n\nVirtual portfolios let you track sub-strategies (e.g. \"YOLO bets\", \"long-term holds\") *within* a single physical Avanza account. A virtual portfolio is just another account in the database (`is_virtual = 1`, linked to a parent). Because shares are **reassigned** (not copied) to the virtual account, every share and every SEK lives on exactly one account at a time — aggregates do not double count.\n\nAll account management — sub-portfolios **and** nicknames — lives under the `account` command.\n\n### Commands\n\n```bash\n# Create a virtual sub-portfolio under a physical parent (optionally fund it)\npython cli.py account create --name \"YOLO\" --parent 1234567 [--starting-cash 5000 --starting-cash-date 2026-07-19]\n\n# Allocate an imported transaction (full, or partial via --shares)\npython cli.py account allocate --tx-date 2026-07-19 --tx-asset \"Some Meme Stock\" --to \"YOLO\" [--shares 50]\n\n# Move cash between accounts\npython cli.py account transfer-cash --amount 10000 --from 1234567 --to \"YOLO\" --date 2026-07-19\n\n# Move an asset position between accounts\npython cli.py account transfer --asset \"Tesla\" --shares 50 --from \"YOLO\" --to 1234567 --date 2026-09-01\n\n# List virtual sub-portfolios with current value and APY\npython cli.py account list [--apy-mode twrr] [--format json]\n\n# Close a sub-portfolio: move all holdings + residual cash back to its parent\npython cli.py account close --name \"YOLO\" --date 2026-09-01\n\n# Account nicknames (moved here from `settings account-nickname`)\npython cli.py account nickname 1234567 \"Main\"\npython cli.py account nickname --list\npython cli.py account nickname --remove 1234567\n```\n\n### How it works\n\n- **`allocate`** moves a transaction (or splits it) onto the virtual account. Moving a buy transfers only the **shortfall** — if the virtual already has capital (e.g. from a prior sell), that cash is used and no transfer is needed. Partial splits proportionally divide `total` and `courtage`.\n- **`allocate --to <parent>`** (undo) moves a transaction back from a virtual to the parent and **deletes** the funding transfer pair that was created during the original allocation. No compensating transactions are created. Requires `--from <virtual>`. Partial undo (`--shares`) is not supported.\n- **`transfer`** (asset move) is represented internally as a sell on the source → cash transfer → rebuy on the destination (all tagged as synthetic). This composes the existing transaction handlers and is correct on every statistics path. The **source realizes its gain** up to the transfer and the **destination gets a fresh cost basis** at the transfer price — an honest \"this position left / entered the strategy\" bookkeeping.\n- **`close`** moves every holding (via the same decomposition) plus any residual cash back to the parent, then reprocesses. The virtual account row is **preserved** (kept `is_virtual = 1`) so its historical cohort/performance data remains queryable; it simply ends up empty.\n- **`delete`** is a clean teardown: reverts all real transactions back to the parent, removes every synthetic transaction tied to the virtual (including partner legs on other accounts), and deletes the account row. Unlike `close`, it leaves no trace — use it to correct a mistake rather than wind down a strategy.\n- After every `account` mutation the cohort tables are rebuilt automatically (same reprocessing as an import).\n\n### Viewing virtual portfolios\n\n- `accounts` shows a hierarchical tree: each physical account lists its combined value (self + its virtual children), with the children indented and marked `[V]`. The `TOTAL` row sums physical rows only (children are a breakdown, so nothing is double counted).\n- `stats` / `portfolio` default to **physical accounts only**. Pass `--account all` to include virtual portfolios, or `--account \"YOLO\"` to view a single virtual por...","readmeExcerpt":"Skill: avanza-investment-tracker Owner: patello Summary: Process Avanza CSV exports, calculate TWRR/Modified Dietz returns, and track portfolio performance. Use when importing stock transactions, calculating investment returns, or managing portfolio data. Reads/writes a local SQLite database, and (for live prices and risk metrics) makes outbound HTTPS requests to Avanza, Riksbanken, and Yahoo Finance. Includes irreve","codeSnippets":[],"executableExamples":[{"language":"bash","snippet":"# 1. Import new transactions\npython path/to/cli.py --database data/asset_data.db import path/to/transactions.csv\n\n# 2. Update price cache and show statistics\npython path/to/cli.py --database data/asset_data.db stats --update-prices auto\n\n# 3. View portfolio allocation and APY\npython path/to/cli.py --database data/asset_data.db portfolio --account default"},{"language":"text","snippet":"workspace-finance/\n├── skills/avanza-investment-tracker/   # Portable skill logic\n│   ├── SKILL.md\n│   ├── scripts/\n│   └── assets/\n└── data/avanza/                        # Private portfolio data\n    ├── transactions.csv\n    ├── special_cases.json\n    └── asset_data.db"},{"language":"bash","snippet":"cp assets/special_cases_template.json ../data/avanza/special_cases.json"},{"language":"bash","snippet":"# 1. Import your transaction data\npython cli.py import data/your_transactions.csv\n\n# 2. Get statistics with automatic price updates\npython cli.py stats --update-prices auto --period year --deposits all\n\n# 3. Check system status anytime\npython cli.py status\n\n# 4. (Optional) Set default accounts for filtering\npython cli.py settings default-accounts \"account1,savings_account\"\n\n# 5. (Optional) Set account nicknames for readability\npython cli.py account nickname 1234567 \"Savings\"\n\n# 6. View account summaries\npython cli.py accounts --update-prices auto"},{"language":"bash","snippet":"# Import CSV data and process transactions in one atomic operation\npython cli.py import data/transactions.csv\n\n# Show statistics with smart updates (auto-updates prices if stale)\npython cli.py stats --update-prices auto --period year --deposits all\n\n# Check system status (transactions, prices, metadata)\npython cli.py status\n\n# Reset database state (mark all transactions as unprocessed)\npython cli.py reset\n\n# Hard reset (delete all transactions, stats, and prices while keeping configuration)\n# WARNING: irreversible — permanently deletes all financial history in the database. Back it up first.\npython cli.py reset --hard"},{"language":"bash","snippet":"python cli.py --database path/to/db.db --special-cases path/to/special.json import data.csv"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: avanza-investment-tracker\ndescription: \"Process Avanza CSV exports, calculate TWRR/Modified Dietz returns, and track portfolio performance. Use when importing stock transactions, calculating investment returns, or managing portfolio data. Reads/writes a local SQLite database, and (for live prices and risk metrics) makes outbound HTTPS requests to Avanza, Riksbanken, and Yahoo Finance. Includes irreversible deletion commands (reset --hard, delete-tx, account delete) — see Security and Data Access in SKILL.md/README.\"\nmetadata:\n  openclaw:\n    requires:\n      bins:\n        - python3\n    permissions:\n      filesystem:\n        - \"read/write: user-specified local SQLite database (--database path); no other files accessed\"\n      network:\n        - \"https://www.avanza.se (price/FX/chart data for held assets; optional, disable with --update-prices never)\"\n        - \"https://api.riksbank.se (reference rates for risk metrics; optional)\"\n        - \"https://query1.finance.yahoo.com (benchmark index prices for beta/correlation; optional)\"\n---\n\n# Avanza Investment Tracker\n\nParse transaction CSVs and compute portfolio performance metrics.\n\n## Security and Data Access\n\nBe aware of what this skill does before running it:\n\n- **Local database writes:** imports, price updates, and portfolio management read and write a local SQLite database.\n- **Network access (optional but on by default):** live price/FX lookups contact Avanza's public API with the asset names in your portfolio; risk metrics (`--risk`, `--beta`) may also contact the Riksbanken API and Yahoo Finance (benchmark ticker + date range). Use `--update-prices never` to stay fully offline.\n- **Irreversible deletions:** `reset --hard`, `delete-tx`, `account allocate --undo`, and `account delete` permanently remove transactions and rebuild derived tables. There is no built-in undo. Back up your database first (e.g. `cp` or git), and prefer `delete-tx --dry-run` to preview. Avoid broad selectors like `delete-tx --since` unless you are certain of the blast radius.\n\n## Quick Start\n\nRun commands from your workspace root, specifying the paths to your database and CSV:\n\n```bash\n# 1. Import new transactions\npython path/to/cli.py --database data/asset_data.db import path/to/transactions.csv\n\n# 2. Update price cache and show statistics\npython path/to/cli.py --database data/asset_data.db stats --update-prices auto\n\n# 3. View portfolio allocation and APY\npython path/to/cli.py --database data/asset_data.db portfolio --account default\n```\n\n## Data Storage Pattern\n\n**User data lives OUTSIDE the skill directory.** Recommended structure:\n\n```\nworkspace-finance/\n├── skills/avanza-investment-tracker/   # Portable skill logic\n│   ├── SKILL.md\n│   ├── scripts/\n│   └── assets/\n└── data/avanza/                        # Private portfolio data\n    ├── transactions.csv\n    ├── special_cases.json\n    └── asset_data.db\n```\n\n## CLI Reference\n\n| Command | Description |\n| :--- | :--- |\n| `python scripts/cli.py import FILE [--allo"},{"path":"README.md","content":"# Investment Tracker\n\n## Description\n\nThis project aims to create a tool for tracking stocks and investments on investment platforms. The original idea for this project was when I was about to buy a SteamDeck. Then I thought better of it and started to wonder how much the money would grow over time if I invested it instead. I realized that it would require me to keep track of the assets that I purchase a particular month, even if I sell them and buy new ones later on. Or if I get dividends and reinvest them.\n\nWith this project, I am able to parse data from my investment platform, keep latest asset values up to date and calculate relevant statistics.\n\nThis project is a work in progress and will be updated as I go along.\n\n## Table of Contents\n\n- [Features](#features)\n- [Installation](#installation)\n- [Usage](#usage) — includes [Network access and privacy](#network-access-and-privacy)\n- [CLI Reference](#cli-reference)\n- [Virtual Portfolios](#virtual-portfolios)\n- [Special Cases](#special-cases)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Features\n\n- Parse and store data from an investment platform.\n- Keep track of where money invested each month is moved and grown over time.\n- **Per-account statistics**: Calculate gains/losses for each account separately, then merge for combined views\n- Calculate statistics for months and years:\n    - Deposit\n    - Withdrawal\n    - Current Value\n    - Total Gain/Loss\n    - Realized Gain/Loss\n    - Unrealized Gain/Loss\n    - APY (Annual Percentage Yield)\n- Two viewing modes:\n    - **Period-specific**: Track performance of investments made in each month/year\n    - **Accumulated**: See total portfolio value over time with assets carried forward\n- **Account filtering**: View statistics for any combination of accounts with full accumulated history support\n- **System status**: Check database statistics, price freshness, and transaction date range\n- **Virtual portfolios**: Track sub-portfolios (e.g. strategy sleeves) separately with allocation, transfers, and virtual-vs-parent performance comparison\n- **Risk metrics**: Optional risk/beta calculations against a benchmark (fetches policy rates and benchmark prices from Riksbanken/Yahoo Finance)\n- **Reporting**: Investment report command with a virtual-portfolio section and benchmark comparison\n- **Safety rails**: Destructive commands (`reset --hard`, `delete-tx`, `account delete`) require confirmation and write an automatic timestamped `.bak` backup first\n\n## Installation\n\n1. Clone the repository.\n2. Install the dependencies with `pip install -r requirements.txt`.\n\n## Quick Start\n\n```bash\n# 1. Import your transaction data\npython cli.py import data/your_transactions.csv\n\n# 2. Get statistics with automatic price updates\npython cli.py stats --update-prices auto --period year --deposits all\n\n# 3. Check system status anytime\npython cli.py status\n\n# 4. (Optional) Set default accounts for filtering\npython cli.py settings default-accounts \"account1,savings_account\"\n\n# "},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn7cxaw1m27d1fe93wbz40vqkd8309h7\",\n  \"slug\": \"avanza-investment-tracker\",\n  \"version\": \"2.14.1\",\n  \"publishedAt\": 1788178662828\n}"},{"path":"references/troubleshooting.md","content":"# Troubleshooting\n\n**Database locked:** Close SQLite browsers\n**Import fails:** Check CSV is Avanza format, UTF-8\n**Missing prices:** Check internet, try without --update-prices\n**Path errors:** Run from skill root, use data/file.csv"},{"path":"references/workflows.md","content":"# Workflows\n\n## First-Time Setup\n\n```bash\npip install -r requirements.txt\nmkdir -p data\ncp assets/special_cases_template.json data/special_cases.json\n# Edit data/special_cases.json if you have corporate actions\npython scripts/cli.py import data/transactions.csv\npython scripts/cli.py stats --update-prices auto\n```\n\n## Adding More Data\n\n```bash\npython scripts/cli.py import data/new_transactions.csv\npython scripts/cli.py stats\n```\n\n## Reset Everything\n\n```bash\n# Soft reset: mark all transactions as unprocessed\npython scripts/cli.py reset\n\n# Hard reset: delete all transactions, stats, and prices\npython scripts/cli.py reset --hard\n```"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"Process Avanza CSV exports, calculate TWRR/Modified Dietz returns, and track portfolio performance. Use when importing stock transactions, calculating investment returns, or managing portfolio data. Reads/writes a local SQLite database, and (for live prices and risk metrics) makes outbound HTTPS requests to Avanza, Riksbanken, and Yahoo Finance. Includes irreversible deletion commands (reset --hard, delete-tx, account delete) — see Security and Data Access in SKILL.md/README. Skill: avanza-investment-tracker Owner: patello Summary: Process Avanza CSV exports, calculate TWRR/Modified Dietz returns, and track portfolio performance. Use when importing stock transactions, calculating investment returns, or managing portfolio data. Reads/writes a local SQLite database, and (for live prices and risk metrics) makes outbound HTTPS requests to Avanza, Riksbanken, and Yahoo Finance. Includes irreve","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1525,"uniquenessScore":49,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-09T10:52:41.344Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-09T10:52:41.344Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-09T23:42:09.236Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}