Import and sync broker CSV portfolio data to Google Sheets DataHub. Supports Fidelity (automated) with multi-broker planned. USE WHEN user mentions import broker data OR sync portfolio OR update positions OR CSV import OR portfolio-sync OR ingest positions OR bring in positions OR downloaded from Fidelity OR working with Portfolio_Positions CSVs. Handles file ingestion from Downloads, position updates, SPAXX/margin validation, safety checks, and formula protection.
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 after ingest
User: "portfolio-sync"
-> Reads Portfolio_Positions_*.csv and Balances_*.csv from notebooks/updates/
-> 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_Mar-06-2026.csv
Portfolio_Positions_Mar-06-2026 (1).csv
-> Reads header of each to classify
-> Regular view (has "Average Cost Basis") -> notebooks/updates/Portfolio_Positions_Mar-06-2026.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):
Portfolio_Positions_*.csv) is latest by date in notebooks/updates/Balances_for_Account_*.csv) is available and current in notebooks/updates/Files not in notebooks/updates/ yet? Run IngestPositions first to move them from ~/Downloads/.
Both CSVs Required: Positions CSV alone is insufficient. Balances CSV provides:
Skill Type: Domain (workflow guidance) Enforcement: BLOCK (data integrity critical) Priority: Critical