Import and sync broker CSV portfolio data to Google Sheets DataHub. Supports multiple brokers (Fidelity, Schwab, Vanguard, etc.)...
Safely import broker CSV position exports into the Google Sheets DataHub tab, ensuring data integrity, validating changes, and protecting sacred formulas.
Supported Brokers:
Broker Detection: Finance Guru automatically detects your broker from user-profile.yaml (set during onboarding). CSV parsing is tailored to your broker's format.
See: docs/broker-csv-export-guide.md for detailed export instructions per broker.
When executing a workflow, output the corresponding notification:
| Workflow | Trigger | File |
|---|---|---|
| IngestPositions | "ingest positions", "import positions", "bring in positions", user mentions downloading from Fidelity | workflows/IngestPositions.md |
| SyncPortfolio | "sync portfolio", "portfolio-sync", "import fidelity" | workflows/SyncPortfolio.md |
Typical flow: IngestPositions (move from Downloads) -> SyncPortfolio (push to Google Sheets)
Notifications:
Running the **IngestPositions** workflow from the **PortfolioSyncing** skill...
Running the **SyncPortfolio** workflow from the **PortfolioSyncing** skill...
Example 1: Full flow from Downloads
User: "ingest positions" or "bring in positions"
-> Scans ~/Downloads/ for Portfolio_Positions_*.csv and Balances_*.csv
-> Classifies regular vs dividend view by reading headers
-> Moves regular view as-is (already date-tagged)
-> Renames dividend view to Dividend_Positions_MMM-DD-YYYY.csv
-> Moves Balances file (overwrites existing)
-> Reports files moved and suggests "portfolio-sync" next
Example 2: Sync (live from SnapTrade)
User: "portfolio-sync"
-> Pulls live positions + balances via the SnapTrade CLI (no CSV read)
-> Compares with Google Sheets DataHub
-> Updates quantities, cost basis, SPAXX, margin debt
-> Reports changes and validates formulas
Example 3: Update positions after trades
User: "I just bought more JEPI, sync my portfolio"
-> Invokes SyncPortfolio workflow
-> Detects quantity change in JEPI
-> If >10% change, asks for confirmation
-> Updates DataHub with new position data
Example 4: Handling duplicate downloads
User downloads both regular and dividend views from Fidelity
-> ~/Downloads/ contains: Portfolio_Positions_REDACTED.csv
Portfolio_Positions_REDACTED (1).csv
-> Reads header of each to classify
-> Regular view (has "Average Cost Basis") -> notebooks/updates/Portfolio_Positions_REDACTED.csv
-> Dividend view (has "Ex-date") -> notebooks/updates/Dividend_Positions_Mar-06-2026.csv
Header row (17 columns):
Account Number,Account Name,Investment Type,Symbol,Description,Quantity,Last Price,Last Price Change,Current Value,Today's Gain/Loss Dollar,Today's Gain/Loss Percent,Total Gain/Loss Dollar,Total Gain/Loss Percent,Percent Of Account,Cost Basis Total,Average Cost Basis,Type
Key fields for sync: Symbol (col 4), Quantity (col 6), Average Cost Basis (col 16), Type (col 17 — "Margin" or "Cash")
Header row (19 columns):
Account Number,Account Name,Investment Type,Symbol,Description,Quantity,Last Price,Last Price Change,Current Value,Percent Of Account,Ex-date,Amount per share,Pay date,Dist. yield,Distribution yield as of,SEC yield,SEC yield as of,Est. annual income,Type
Quick classifier: If header contains Ex-date -> dividend view. If header contains Average Cost Basis -> regular view.
Key-value format (not columnar). Extract:
Cash Position Logic:
SPAXX value from Positions CSV (shows only settled money market)Golden Rule: NEVER include columns C-F in your update range. NEVER pass empty strings to any cell.
Empty strings ("") in columns C-F DELETE the GOOGLEFINANCE and calculation formulas. Always update columns A, B, G individually:
// ✅ RIGHT - Update ONLY writable columns, one at a time
mcp__gdrive__sheets(operation: "updateCells", params: {
spreadsheetId: SPREADSHEET_ID,
range: "DataHub!B13:B13", // ✅ Single column, specific row
values: [["72.942"]]
})
// ❌ WRONG - Multi-column range with empty strings kills formulas
mcp__gdrive__sheets(operation: "updateCells", params: {
range: "DataHub!A13:G13",
values: [["JEPI", "72.942", "", "", "", "", "$56.48"]] // ❌ Empty strings delete formulas
})
| Action | Correct | Wrong |
|---|---|---|
| Update quantity | range: "DataHub!B13:B13" |
range: "DataHub!A13:G13" with empty strings |
| Update cost basis | range: "DataHub!G13:G13" |
Including columns C-F in range |
| Add new ticker | 3 separate calls (A, B, G) | Single call with empty strings in C-F |
When adding new tickers, classify into the correct portfolio layer in Column S.
Do NOT hardcode layer assignments. Instead, read the current layer definitions from:
fin-guru/data/spreadsheet-architecture.md → "Pattern-Based Layer Classification" sectionIf a new ticker doesn't clearly match any layer pattern, set to "UNKNOWN - Manual Review Required" and alert the user for classification.
STOP conditions (require user confirmation):
FLAG conditions (alert user but proceed):
When STOPPED: Show clear diff table, ask user to confirm, proceed only after explicit approval.
When FLAGGED: Show the discrepancy, proceed with update but highlight in summary.
Spreadsheet ID: Read from fin-guru/data/user-profile.yaml → google_sheets.portfolio_tracker.spreadsheet_id
Builder (Write-enabled): Can update columns A, B, G; can add new rows; can apply layer classification; CANNOT modify formulas.
All Other Agents (Read-only): Market Researcher, Quant Analyst, Strategy Advisor — can read all data, cannot write, must defer to Builder for updates.
fin-guru/data/spreadsheet-architecture.mdfin-guru/data/spreadsheet-quick-ref.mdfin-guru/data/user-profile.yamlformula-protection skill for sacred formula rulesBefore syncing (SyncPortfolio) — positions + balances now come live from SnapTrade:
config/snaptrade-accounts.yaml (role set, enabled: true)SNAPTRADE_* keys are present in .envThe CSV format reference above still applies to the CSV fallback (--source csv for margin, or re-verification) and to the Dividend view / History CSVs, which dividend-tracking and TransactionSyncing still consume.
Skill Type: Domain (workflow guidance) Enforcement: BLOCK (data integrity critical) Priority: Critical