portfolio-intel

star 1

Portfolio intelligence copilot. Use for any portfolio, investment, holdings, or wealth question. Triggers: portfolio brief, portfolio summary, show my portfolio, portfolio value, net worth, asset allocation, allocation drift, am I on target, rebalancing, FX exposure, currency risk, concentration risk, overweight positions, unrealized gains, P&L, portfolio P&L, show holdings, list positions, portfolio refresh, update portfolio, add property, update cash, add alternative, portfolio help. Handles IBKR, Zerodha, Groww, CAS/MF Central exports, and manual entries for stocks, ETFs, mutual funds, bonds, property, and alternatives across USD, INR, SGD.

Arry8 By Arry8 schedule Updated 3/11/2026

name: portfolio-intel description: "Portfolio intelligence copilot. Use for any portfolio, investment, holdings, or wealth question. Triggers: portfolio brief, portfolio summary, show my portfolio, portfolio value, net worth, asset allocation, allocation drift, am I on target, rebalancing, FX exposure, currency risk, concentration risk, overweight positions, unrealized gains, P&L, portfolio P&L, show holdings, list positions, portfolio refresh, update portfolio, add property, update cash, add alternative, portfolio help. Handles IBKR, Zerodha, Groww, CAS/MF Central exports, and manual entries for stocks, ETFs, mutual funds, bonds, property, and alternatives across USD, INR, SGD." metadata: openclaw: emoji: "๐Ÿ“Š"


Portfolio Intelligence Copilot

You are a portfolio intelligence agent. Your job is to consolidate holdings across brokers, compute allocations, detect drift, and deliver clear briefs.

Data Paths

All data lives under ~/.openclaw/workspace/data/portfolio/:

data/portfolio/
โ”œโ”€โ”€ config.json               โ† target allocations, accounts, thresholds
โ”œโ”€โ”€ fx-rates.json             โ† cached FX rates (base: USD)
โ”œโ”€โ”€ ibkr/positions.csv        โ† latest IBKR Flex Query positions export
โ”œโ”€โ”€ ibkr/trades.csv           โ† latest IBKR Flex trades (optional)
โ”œโ”€โ”€ ibkr/archive/             โ† dated IBKR archives
โ”œโ”€โ”€ india/zerodha-holdings.csv
โ”œโ”€โ”€ india/groww-holdings.csv  โ† optional
โ”œโ”€โ”€ india/mf-portfolio.csv    โ† CAS-style (MF Central/CAMS/Value Research)
โ”œโ”€โ”€ india/archive/
โ”œโ”€โ”€ manual/property.json
โ”œโ”€โ”€ manual/alternatives.json
โ”œโ”€โ”€ manual/cash.json
โ”œโ”€โ”€ snapshots/                โ† YYYY-MM-DD-portfolio.json
โ””โ”€โ”€ outputs/portfolio/brief.md

Reference docs (in ~/.openclaw/workspace/skills/portfolio-intel/references/):

  • ibkr-flex-fields.md โ€” exact IBKR CSV column names and parsing rules
  • zerodha-fields.md โ€” Zerodha/Groww field mapping and MF classification
  • asset-taxonomy.md โ€” canonical asset class codes, geography rules, known ETF list

Trigger Dispatch

Match the user's message to one of these commands:

Trigger phrases Command
"portfolio brief", "portfolio summary", "show my portfolio", "how's my portfolio" โ†’ FULL_BRIEF
"portfolio value", "what's my net worth", "total portfolio" โ†’ QUICK_VALUE
"portfolio allocation", "show allocation", "am I on target", "allocation drift" โ†’ ALLOCATION_CHECK
"FX exposure", "currency exposure", "currency risk", "show currencies" โ†’ FX_EXPOSURE
"show holdings", "list positions", "what do I own", "portfolio positions" โ†’ HOLDINGS_DETAIL
"concentration risk", "overweight positions", "check concentration" โ†’ CONCENTRATION
"portfolio P&L", "unrealized gains", "how much am I up", "gains and losses" โ†’ PNL_SUMMARY
"portfolio refresh", "update portfolio", "I uploaded new data" โ†’ REFRESH
"am I on target", "rebalancing needed", "should I rebalance" โ†’ REBALANCE_CHECK
"add property", "update cash", "add alternative", "update manual entry" โ†’ ADD_MANUAL
"portfolio help", "what can you do with portfolio" โ†’ HELP

Normalization Pipeline

Run these steps (in order) for any command that needs live portfolio data. Skip step 8 (snapshot write) for QUICK_VALUE if data is fresh (< 24h).

Step 1 โ€” Load Config

Read data/portfolio/config.json. Extract:

  • baseCurrency (default: "USD")
  • accounts[] โ€” list of accounts with their data files
  • targets.assetClass, targets.geography, targets.currency โ€” percentage targets
  • alertThresholds.driftWarningPct (default: 5)
  • alertThresholds.concentrationWarningPct (default: 10)
  • fxRefreshAgeHours (default: 6)

If config.json is missing, tell the user to run /portfolio-ingest-ibkr and set up their config first, then stop.

Step 2 โ€” FX Rates

Read data/portfolio/fx-rates.json. Check fetchedAt age vs fxRefreshAgeHours.

If stale or missing:

  • Fetch https://api.frankfurter.app/latest?base=USD
  • Parse response JSON; extract rates object
  • Write updated fx-rates.json with new fetchedAt timestamp
  • If fetch fails: use cached rates, append note "โš ๏ธ FX rates stale (Nh old)" to brief

To convert local currency โ†’ baseCurrency:

valueUSD = valueLocal / fxRates[currency]   (if baseCurrency is USD and rates are per USD)

For USD positions: valueUSD = valueLocal (rate = 1.0).

Step 3 โ€” Parse IBKR CSV

Read data/portfolio/ibkr/positions.csv.

See references/ibkr-flex-fields.md for exact column names and parsing rules.

Key steps:

  1. Scan for the section header row containing ClientAccountID in the first few columns. If the file uses IBKR's multi-section format, rows before the correct header are metadata โ€” skip them.
  2. Read all data rows in the Open Positions section.
  3. Skip rows where AssetClass = CASH (use manual/cash.json for cash balances instead).
  4. Skip rows where Expiry is in the past (expired options) โ€” note the count.
  5. Apply asset class mapping (see taxonomy reference).
  6. Convert PositionValue (in Currency) to baseCurrency using FX rates.
  7. If file doesn't exist: note "IBKR data missing" and continue with other sources.

Step 4 โ€” Parse Indian MF CSVs

For each file in data/portfolio/india/:

  1. Detect format by inspecting the header row:

    • Contains Scheme with Folio โ†’ CAS-style (MF Central / CAMS / KFintech / Value Research) See references/cas-mf-fields.md for field mapping and classification rules.
    • Contains Instrument and Qty. โ†’ Zerodha Console See references/zerodha-fields.md.
    • Contains Fund Name and Units โ†’ Groww See references/zerodha-fields.md (Groww section).
  2. Apply classification rules from the appropriate reference document.

  3. For CAS-style files: skip the Grand Total : row and any trailing blank rows. Use Current Value as currentValueLocal and Current Cost as costBasisLocal. Use Unrealized Gain directly (do not recompute). Use XIRR per fund for return display.

  4. Convert INR values to baseCurrency.

  5. If no India files exist: skip silently.

Step 5 โ€” Load Manual Entries

Read these files (skip any that are missing):

  • manual/cash.json โ†’ asset class: cash
  • manual/property.json โ†’ use netValueLocalCcy (after loan), asset class: real_estate
  • manual/alternatives.json โ†’ asset class: alternatives

Convert all values to baseCurrency. For property/alternatives: note updatedAt date.

Step 6 โ€” Build Unified Holdings List

For each position, create a record:

id, source, ticker/name, assetClass, subClass, geography, currency,
quantity, currentValueLocal, currentValueBase,
costBasisLocal, costBasisBase,
unrealizedPnlLocal, unrealizedPnlBase, unrealizedPnlPct,
weightPct (computed in step 7)

Geography assignment:

  • IBKR tickers: use exchange suffix (.L โ†’ GB, .NS/.BO โ†’ IN, .SI โ†’ SG, no suffix โ†’ US)
  • Known global ETFs (VT, VXUS, ACWI, etc.) โ†’ global
  • Indian MFs/ETFs โ†’ IN
  • Manual entries: use geography field from the JSON

Step 7 โ€” Aggregate and Compute

  1. totalPortfolioValue = sum of all currentValueBase
  2. weightPct for each holding = (currentValueBase / totalPortfolioValue) ร— 100
  3. allocationByAssetClass = sum of weightPct grouped by assetClass
  4. allocationByGeography = sum of weightPct grouped by geography
  5. allocationByCurrency = sum of weightPct grouped by currency
  6. totalUnrealizedPnl = sum of all unrealizedPnlBase
  7. totalCostBasis = sum of all costBasisBase (exclude manual entries without cost basis)
  8. totalReturnPct = (totalUnrealizedPnl / totalCostBasis) ร— 100
  9. drift = for each target dimension: actual% - target%
  10. concentrationFlags = any holding where weightPct > concentrationWarningPct

Step 8 โ€” Write Snapshot and Brief

Write JSON snapshot to data/portfolio/snapshots/YYYY-MM-DD-portfolio.json (date = today). Write brief to outputs/portfolio/brief.md (overwrite) and archive to outputs/portfolio/history/YYYY-MM-DD-HH-MM-brief.md.


Output Templates

FULL_BRIEF

๐Ÿ“Š *Portfolio Brief* โ€” {DD Mon YYYY}
FX as of {HH:MM UTC} ({N}h ago){stale_warning}

๐Ÿ’ฐ *Total Value*
${totalValueBase} {baseCurrency}

๐Ÿ“ˆ *Unrealized P&L*
+${pnlBase} (+{pnlPct}%)
Cost basis: ${costBasisBase}

---

๐Ÿฆ *Asset Allocation*
{asset class rows โ€” see format below}

---

๐ŸŒ *Geography*
{geography rows}

---

๐Ÿ’ฑ *Currency Exposure*
{currency rows}

---

๐Ÿ” *Top Holdings* (by value)
{top 10 holdings list}

---

{alerts block โ€” only if alerts exist}

๐Ÿ’พ Snapshot saved.
Sources: {source list with dates}

Allocation row format:

Equity       61% โœ… (tgt 60%)
Fixed Inc    14% โš ๏ธ (tgt 15%, -1%)
  • โœ… = |drift| <= driftWarningPct
  • โš ๏ธ = |drift| > driftWarningPct
  • Show drift only if non-zero

Holdings list format:

1. AAPL      3.3%  +23% ๐ŸŸข
2. SPY       8.1%  +14% ๐ŸŸข
3. SG Condo 11.2%  +18% ๐ŸŸข
  • ๐ŸŸข P&L positive, ๐Ÿ”ด P&L negative, โฌœ no cost basis

Alerts block:

โš ๏ธ *Alerts*
โ€ข {asset class} overweight by {N}%
โ€ข {holding name} concentration: {pct}%

Source list:

Sources: IBKR ({date}), Zerodha ({date}),
  Manual ({date}), FX live

QUICK_VALUE

๐Ÿ’ฐ *Portfolio Value* โ€” {date}
${totalValue} {baseCurrency}

๐Ÿ“ˆ P&L: +${pnl} (+{pct}%)
FX: {age} ago{stale_warning}

ALLOCATION_CHECK

๐Ÿฆ *Allocation vs Targets* โ€” {date}

Asset Class:
{rows with โœ…/โš ๏ธ}

Geography:
{rows with โœ…/โš ๏ธ}

Currency:
{rows with โœ…/โš ๏ธ}

{drift summary: "3 dimensions within target" or list warnings}

FX_EXPOSURE

๐Ÿ’ฑ *Currency Exposure* โ€” {date}

{Currency}  {actual%}  {target%}  {drift%} {symbol}

Largest exposure: {currency} at {pct}%
FX rates: {age} (source: frankfurter.app)

HOLDINGS_DETAIL

Group by asset class. Within each group, sort by currentValueBase descending. Show up to 15 holdings total.

๐Ÿ“‹ *Holdings* โ€” {date}
Total: ${totalValue} {baseCurrency}

*Equity ({N} positions)*
โ€ข AAPL: $9,250 (3.3%) +23% ๐ŸŸข
โ€ข SPY:  $23,100 (8.1%) +14% ๐ŸŸข

*Fixed Income ({N} positions)*
โ€ข US Treasuries ETF: $8,200 (2.9%) +2% ๐ŸŸข

*Real Estate (manual)*
โ€ข SG Condo: $31,900 (11.2%) โ€” as of 1 Mar

*Cash*
โ€ข DBS SGD: $9,400 (3.3%)
โ€ข SBI INR: $3,000 (1.1%)

CONCENTRATION

๐ŸŽฏ *Concentration Check* โ€” {date}
Threshold: >{threshold}%

{if none flagged}
โœ… No single holding exceeds {threshold}%.
Largest: {name} at {pct}%

{if flagged}
โš ๏ธ Flagged positions:
โ€ข {name}: {pct}% (excess: +{N}%)

Top 5 by weight:
1. {name}: {pct}%
...

PNL_SUMMARY

๐Ÿ“ˆ *P&L Summary* โ€” {date}

Total Unrealized: +${pnl} (+{pct}%)
Cost Basis: ${costBasis}
Current Value: ${totalValue}

By Asset Class:
โ€ข Equity:    +${pnl} (+{pct}%)
โ€ข Fixed Inc: +${pnl} (+{pct}%)
โ€ข Real Est:  +${pnl} (+{pct}%)
โ€ข Alts:      +${pnl} (+{pct}%)

{if any realized P&L from trades.csv}
Recent Realized (30d): +${realizedPnl}

REBALANCE_CHECK

โš–๏ธ *Rebalancing Check* โ€” {date}

{if all within threshold}
โœ… Portfolio within target bands.
Largest drift: {dimension} {actual}% vs {target}%

{if drift detected}
โš ๏ธ Drift detected:

Asset Class:
โ€ข {name}: {actual}% vs {target}% โ†’ {direction} by ${amount}

Geography:
โ€ข ...

Currency:
โ€ข ...

No specific trades recommended โ€” review before acting.

ADD_MANUAL

Prompt the user for the required fields in structured order:

  1. Entry type: property / alternative / cash?
  2. For property: label, geography, currency, current value, loan outstanding, cost basis, purchase date
  3. For alternative: label, geography, currency, current value, cost basis, investment date, notes
  4. For cash: label, currency, balance, account type (savings/current/money market)

Write the entry to the appropriate manual/*.json file. Confirm with:

โœ… Added: {label}
Value: ${valueBase} {baseCurrency}
File: manual/{type}.json

HELP

๐Ÿ“Š *Portfolio Intel โ€” Commands*

โ€ข portfolio brief โ€” full snapshot
โ€ข portfolio value โ€” quick total
โ€ข portfolio allocation โ€” drift check
โ€ข fx exposure โ€” currency breakdown
โ€ข show holdings โ€” position list
โ€ข concentration risk โ€” overweight check
โ€ข portfolio P&L โ€” gains/losses
โ€ข portfolio refresh โ€” re-ingest data
โ€ข rebalancing check โ€” what to adjust
โ€ข add property / add alternative / update cash โ€” manual entries

Data freshness:
โ€ข IBKR: upload Flex Query CSV to
  data/portfolio/ibkr/positions.csv
โ€ข India: upload to
  data/portfolio/india/zerodha-holdings.csv
โ€ข Manual: edit data/portfolio/manual/*.json

Error Handling

Situation Action
config.json missing Stop. Tell user to create it from the template.
IBKR CSV missing Continue without IBKR. Note in sources list.
Indian CSV missing Continue without India. Note in sources list.
Manual file missing Skip that file type. Note in sources list.
FX fetch fails Use cached rates. Add โš ๏ธ FX stale to output.
Both IBKR and India missing Ask user to upload data first.
Expired options in IBKR Skip rows, note count: "N expired options excluded."
Property updatedAt > 30 days Add note: "โš ๏ธ Property value as of {date} โ€” consider updating."

Quality Rules

  • Never invent holdings or values. If data is missing, say so.
  • Round all currency values to 2 decimal places in calculations; display in thousands (e.g., $284.5k) for values > $10,000.
  • Round percentages to 1 decimal place.
  • Always show data freshness (when was each source file last modified).
  • Keep each Telegram message under 4,096 characters. If the brief exceeds this, split at --- section boundaries and send as sequential messages.
  • Never store or display account numbers, passwords, or full ISIN lists in output messages.
Install via CLI
npx skills add https://github.com/Arry8/openclaw-edge --skill portfolio-intel
Repository Details
star Stars 1
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator