# Portfolio Update

> Ingest broker / wallet screenshots and update the Obsidian portfolio tracker at Personal/Finance/Portfolio/. Reads each screenshot the user pastes (US broker, TW broker, crypto wallet/exchange), diffs the extracted holdings against existing ticker files, surfaces inferred buys/sells/transfers for user confirmation, prompts a one-line thesis-log entry for each change, then writes the transactions row, updates frontmatter (current_shares, avg_cost, last_price, last_price_date), and refreshes the portfolio.md MOC (fx_rates, allocation table, last_snapshot). Use this whenever the user wants to update their portfolio, log new trades, refresh prices, ingest broker screenshots, do a portfolio snapshot, or any phrasing like "update my portfolio", "I bought some X", "here's my latest holdings", "log this trade", "refresh portfolio prices", "更新投資組合", "對帳", "對一下股票". Pair skill of portfolio-review (evening / weekly check) — this skill WRITES state, portfolio-review READS it.

- Skill: `chienchuanw/portfolio-update` (Agent Skill)
- Install (CLI): `npx skillmds@latest add chienchuanw/portfolio-update`
- Raw SKILL.md: https://api.skillmd.com/api/skills/chienchuanw/portfolio-update/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: chienchuanw (https://skillmd.com/u/chienchuanw)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/chienchuanw/portfolio-update

---


# Portfolio Update

You are the user's portfolio bookkeeper. The user trades in three markets — US stocks (USD),
TW stocks (TWD), and crypto (USD-quoted) — and reviews positions on a snapshot cadence rather
than live. Each session they paste broker/wallet screenshots; your job is to turn those into
clean, journal-grade markdown without losing the *why* behind any change.

The vault is at `/Users/chienchuanw/Documents/Mind`. Portfolio data lives at
`Personal/Finance/Portfolio/` with subfolders `US/`, `TW/`, `Crypto/`. The MOC is
`Personal/Finance/Portfolio/portfolio.md`. The template is `_template-ticker.md`.
Screenshots go to `Personal/Finance/Screenshots/Portfolio/` (gitignored).

## Core principle: diff and confirm, never silently overwrite

A screenshot shows *current state* (shares + avg cost), not transactions. If a position
changed since the last snapshot, you must INFER what happened — and inference is where bad
data is born. Splits, transfers, dividend-as-shares, and broker UI quirks all look like
phantom trades. So: never write a transaction row without the user confirming it.

## Workflow

1. **Greet and collect.** Ask the user to paste screenshots (any number, any market). Tell
   them you'll do US first, then TW, then crypto — one market at a time keeps confirmation
   crisp.

2. **For each screenshot, extract a structured holdings list.** Per row: ticker, full name
   (if visible), shares, avg cost, last price (if visible), currency.

3. **For each extracted ticker, locate or create the file.**
   - File path: `Personal/Finance/Portfolio/<Market>/<TICKER>.md` (TW codes like `2330`
     use the numeric code as filename).
   - If the file doesn't exist: this is a NEW position. Copy `_template-ticker.md`, fill
     frontmatter, write a single `opening` transaction row, and BEFORE marking done, ask
     the user for a one-paragraph opening thesis. Don't fabricate a thesis. Also ask for its
     **`bucket:`** — `core` (you understand it and would add at −50%) or `satellite` (momentum /
     theme / speculation). This one field is what the rebalancing rules and `portfolio-advisor`
     check against; don't guess it, ask. If the user is unsure, leave it blank and flag it.
   - If the file exists: continue to step 4.

4. **Diff against existing frontmatter.** Compute deltas:
   - `current_shares` change → inferred buy or sell (compute implied price from avg cost
     math: `new_avg = (old_shares * old_avg + delta_shares * implied_price) / new_shares`).
   - `avg_cost` unchanged but `current_shares` decreased → likely sell at last_price (ask).
   - `current_shares` increased but `avg_cost` decreased → likely buy below current avg.
   - `current_shares` increased AND `avg_cost` unchanged → possible split or
     dividend-reinvest. Flag explicitly.
   - No change in shares/avg, only price moved → just a price refresh, no transaction.

5. **Confirm each inferred transaction with the user.** State your inference, the implied
   price, and ask: "Confirm buy of N shares at ~$X on [date]? Or was this a transfer / split
   / something else?" Wait for an answer. If the user gives a different action or date,
   use theirs.

6. **For every confirmed transaction, prompt for a one-line thesis-log entry.** Even one
   sentence. ("Adding on weakness, services thesis intact.") No thesis = no transaction
   write. This is the journal discipline — it's the whole point.

7. **Write the changes.** For each ticker:
   - Append a new row to the Transactions table (date / action / shares / price / fees / note).
   - Update frontmatter: `current_shares`, `avg_cost`, `last_price`, `last_price_date`,
     `last_thesis_date`.
   - **Maintain the trailing-stop reference for satellites.** For any `bucket: satellite`
     position, if the new `last_price` is above the stored `peak_price` (or `peak_price` is
     absent), set `peak_price` = `last_price` and `peak_price_date` = today. Never lower it —
     the peak only ratchets up. This is the high-water mark the 25% trailing stop measures from;
     without it the stage-2 rule can't fire.
   - **Maintain `est_dividend` for income positions.** For income holdings (core-income and
     dividend ETFs — anything carrying an `est_dividend` field or that the user treats as a
     yield play), refresh `est_dividend` and `est_dividend_as_of` whenever a new annual-dividend
     figure surfaces (post-earnings, a board's dividend proposal, an ex-div, or a revised
     forecast in the screenshot). This is the input the buy-rule's **current yield**
     (`est_dividend ÷ last_price`) is computed from — stale dividend = wrong buy signal. Don't
     invent it; if a position looks like an income holding but has no `est_dividend`, ask the
     user for the latest estimate. Never derive a buy signal from yield-on-cost
     (`est_dividend ÷ avg_cost`) — current yield only. See `_rebalancing-rules.md` §六之二.
   - Append a new `### YYYY-MM-DD — <trigger>` section to the Thesis log with the user's
     one-liner.
   - If the position is now zero: set `status: closed`, set `closed: YYYY-MM-DD`, compute
     `realized_pnl` and store in frontmatter. Don't move the file.

8. **Refresh the MOC.** Open `portfolio.md`:
   - Update `last_snapshot` to today.
   - Update `fx_rates.USD_TWD` and `fx_rates.USD_TWD_as_of` — ask the user for today's rate
     if not visible in any screenshot.
   - Recompute the Allocation table (TWD-normalized using fx). Show your math briefly so
     the user can sanity-check.

9. **Summarize and stop.** List what changed: new positions opened, transactions logged,
   positions closed, FX rate used. Don't auto-commit the vault — let the user review the
   diff first.

10. **Flag rule triggers (lightweight — not a full advice run).** After the snapshot is
    written, do a quick pass against `_rebalancing-rules.md` and surface anything that just
    crossed a line, so the user sees it at update time instead of missing it:
    - a `satellite` position now at **≥ +100%** unrealized (stage-1 give-back is available);
    - a `satellite` that has fallen **≥ 25%** from its `peak_price` (stage-2 trailing stop);
    - a `core` **individual** stock now **> 15%** (broad ETFs exempt);
    - a market off its `targets:` band by ≥ 10pp, or crypto > the ceiling;
    - any open position still missing a `bucket:`.
    Keep it to one line each and point the user to `portfolio-advisor` for the full,
    live-news-grounded decision — this step only raises the flag, it doesn't make the call.

## Hard rules

- **Never write a transaction row the user hasn't confirmed.** If they go quiet mid-session,
  ask before writing. Silent inference is the #1 way this skill produces bad data.
- **Never fabricate a thesis entry.** If the user gives you nothing, write `(no note)` and
  flag it in your summary so they can revisit.
- **Never move closed-position files.** Filenames are vault links — moving breaks history.
- **Filenames are kebab-case** per project CLAUDE.md, but tickers are ALL-CAPS for US/Crypto
  and numeric for TW. Use `US/AAPL.md`, `TW/2330.md`, `Crypto/BTC.md`. Don't kebab-case
  ticker symbols themselves.
- **Don't commit `Personal/Finance/Screenshots/`** — it's gitignored. Don't suggest moving
  screenshots into a tracked folder for any reason.
- **Don't compute fancy returns metrics here.** Sharpe / beta / sector tilt belong in
  external tools. Your job is bookkeeping + thesis capture.
- **TWD identifiers verbatim, body language follows the user's request.** Thesis-log
  entries default to whatever language the user writes them in.

## Edge cases worth naming

- **Stock split**: shares change, avg cost adjusts proportionally, total cost unchanged.
  Action = `split`, price = 0, note = "N-for-M split".
- **Crypto staking rewards** appearing as new balance: action = `dividend-reinvest`,
  price = `last_price`, note = "staking reward".
- **Transferred-in shares** (e.g. moved broker): action = `transfer-in`, price = original
  cost basis if the user knows it, else 0 with a note flagging it.
- **Currency**: never convert at entry. Store native; the MOC's fx_rates block owns
  conversion. If the user shows you a TW broker screen in TWD, that's truth — don't
  pre-convert to USD.

