Skip to content

sonnyb9/MMM-Fintech

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

122 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MMM-Fintech

A MagicMirror² module for displaying consolidated financial holdings with real-time pricing.

Note: This module is a work in progress. Features and APIs may change.

Important Notes

Stock Sale Settlement (T+2)

When you sell stocks, ETFs, or mutual funds in US markets, the proceeds settle two business days after the trade date (T+2). During this time:

  • Your holdings will reflect the reduced share count immediately (as reported by your brokerage)
  • Cash proceeds won't appear until settlement (typically 2 business days later)
  • Total portfolio value may appear lower than expected until cash settles

Example: If you sell $20,000 worth of stock on Wednesday, your portfolio total will drop by ~$20,000 until Friday when the cash settles into your money market fund (SPAXX, etc.). This is normal brokerage behavior and not a bug.

If you need to manually account for pending settlements, you can add the expected cash to manual-holdings.json temporarily.

Features

  • Multi-Asset Support: Crypto, stocks, ETFs, mutual funds, and forex
  • SnapTrade Integration: Fetches holdings from Fidelity, Coinbase, and other brokerages
  • Coinbase Integration: Fetches crypto holdings via CDP API
  • Twelve Data Integration: Stocks, ETFs, and forex pricing
  • EODHD Integration: Mutual fund pricing with support for funds not covered by Twelve Data
  • Manual Holdings: Support for manual entry without any API registrations
  • Cost Basis & Gain/Loss: Display unrealized gain/loss percentages
  • Portfolio Charts: Visual performance tracking over time
  • Real-time Pricing: Configurable update intervals by asset type
  • Market Hours Scheduling: Limit stock/forex polling to market hours
  • 24h Change: Shows percent change for each holding
  • Portfolio Total: Displays total value in configurable currency
  • Forex Rates: Display exchange rates with optional inverse pairs
  • Currency Conversion: Display values in any currency (USD, EUR, GBP, etc.)
  • Privacy Mode: Hide quantity and total while keeping price and 24h change visible
  • Secure Credentials: AES-256-GCM encrypted API keys
  • Error Indicators: Visual warnings when sync fails
  • Configurable Sorting: By value or alphabetically

Data Providers

MMM-Fintech supports multiple ways to track your holdings, from fully manual to fully automated.

Manual Holdings (No API Required)

Purpose: Track holdings without any API registrations or costs

Cost: Free

If you prefer not to register for any data provider APIs, you can manually enter your holdings in manual-holdings.json. This requires you to update the file yourself whenever your holdings change.

How it works: You enter your holdings (symbol, quantity, type) in a JSON file. The module will still need a pricing provider for market-priced holdings: Twelve Data for stocks/ETFs/forex, and EODHD for mutual funds when configured. You can also use the cash type for money market funds which uses a fixed $1.00 price with no API calls.

Best for: Users who want simplicity, don't want to share credentials with third-party services, or have holdings at brokerages not supported by SnapTrade.

See Manual Holdings Setup for the file format.


SnapTrade (Recommended for Automated Brokerage Holdings)

Purpose: Automatically fetches holdings from connected brokerage accounts (Fidelity, Coinbase, Schwab, etc.)

Cost: Pay-as-you-go pricing. See SnapTrade Pricing for details.

Why use it: SnapTrade provides unified access to multiple brokerages through a single API. When connected to Coinbase, it returns complete holdings including staked crypto (SOL, ETH) which the Coinbase CDP API cannot access. Holdings update automatically—no manual file editing required.

Get credentials:

  1. Create account at SnapTrade Dashboard
  2. Generate API key (clientId and consumerKey)
  3. See SnapTrade Getting Started for full setup

Coinbase CDP API (Free - Crypto Only)

Purpose: Automatically fetches crypto holdings directly from Coinbase

Cost: Free. See Coinbase Developer Platform

Limitation: The CDP API does not return staked crypto assets. If you have staked SOL, ETH, or other assets, they will not appear. Use SnapTrade (connected to Coinbase) or manual holdings instead to track staked assets.

Get credentials:

  1. Go to CDP Portal API Keys
  2. Create a Secret API key with View permission
  3. Select ECDSA algorithm (required)
  4. Download the JSON file

Twelve Data (Free Tier - Pricing Only)

Purpose: Provides real-time pricing for stocks, ETFs, and forex rates

Cost: Free tier includes 8 API calls/minute (800/day). See Twelve Data Pricing

Note: Twelve Data is a pricing provider only—it does not track holdings. Holdings come from SnapTrade, Coinbase, manual entry, or a combination. In the current provider split, Twelve Data is used for stocks, ETFs, and forex. With the default 20-minute update interval during market hours, you'll use approximately 200 calls/day for 10 symbols, well within the free tier.

Get credentials:

  1. Create account at Twelve Data
  2. Copy your API key from the dashboard

EODHD (Mutual Fund Pricing)

Purpose: Provides pricing for mutual funds, including funds that may not be available through Twelve Data

Cost: Free tier available, but small. See EODHD Pricing

Note: EODHD is optional and is only used for mutual_fund holdings when configured. MMM-Fintech resolves supported fund symbols internally to the appropriate exchange-form ticker such as .US or .NMFQS. Because the EODHD free tier is limited, mutual fund price refreshes are intentionally throttled more conservatively than stock/ETF updates.

Get credentials:

  1. Create account at EODHD
  2. Copy your API token from the dashboard

Provider Priority

When fetching holdings, the module uses this priority:

  1. SnapTrade (if configured) — Fetches from all connected brokerages
  2. Coinbase CDP (if SnapTrade not configured) — Fetches crypto only
  3. Manual holdings — Always merged for additional positions or as standalone

For pricing:

  • Crypto: Coinbase CDP API
  • Stocks/ETFs/Forex: Twelve Data API
  • Mutual Funds: EODHD when configured, otherwise Twelve Data fallback
  • Cash (money market): Fixed at $1.00 (no API calls)

Installation

cd ~/MagicMirror/modules
git clone https://github.com/sonnyb9/MMM-Fintech.git
cd MMM-Fintech
npm install

Setup

Quick Setup (Recommended)

Use the unified setup wizard to configure all providers interactively:

cd ~/MagicMirror/modules/MMM-Fintech
node setup.js

The wizard will guide you through:

  • SnapTrade (automated brokerage holdings, includes staked crypto)
  • Coinbase CDP API (crypto only, excludes staked assets)
  • Twelve Data (stock/ETF/forex pricing)
  • Manual Holdings (no API, manual JSON editing)

After configuring SnapTrade, run this to connect your brokerages:

node snaptrade-connect.js

Open the URL in a browser to authorize access. Note: Connection URLs expire in 5 minutes.


Manual Setup (Alternative)

If you prefer individual setup scripts instead of the wizard:

1. Set Up SnapTrade (Recommended)

SnapTrade provides the most complete holdings data, including staked crypto.

node setup-snaptrade.js

Enter your clientId and consumerKey when prompted. Then generate a connection portal URL:

node snaptrade-connect.js

Open the URL in a browser to connect your brokerage accounts (Fidelity, Coinbase, etc.). Note: The connection portal URL expires in 5 minutes—run the command again if it expires before you complete the connection.

2. Set Up Coinbase CDP API (Alternative to SnapTrade for Crypto)

Only needed if you're not using SnapTrade for crypto holdings.

  1. Download your API key JSON from CDP Portal
  2. Place cdp_api_key.json in the module folder
  3. Run:
node setup-credentials.js

This will:

  • Generate an encryption key at ~/.mmm-fintech-key
  • Create cdp-credentials.enc from your JSON file
  • Prompt to delete the original JSON (recommended)

3. Set Up Twelve Data (Required for Stock/ETF/Forex Pricing)

node setup-twelvedata.js

Enter your API key when prompted. This creates twelvedata-credentials.enc.

4. Set Up EODHD (Optional - Recommended for Mutual Funds)

node setup-eodhd.js

Enter your API token when prompted. This creates eodhd-credentials.enc.


Configure MagicMirror

Add to your config/config.js:

{
  module: "MMM-Fintech",
  position: "middle_center",  // Recommended for best display
  config: {
    title: "Portfolio",
    showLastUpdated: true,
    showPricePerUnit: true,
    showQuantity: true,
    showForex: true,
    sortBy: "value"
  }
}

5. Add Manual Holdings (Optional)

Create manual-holdings.json in the module folder for any holdings not covered by APIs, or as your sole holdings source if not using SnapTrade or Coinbase:

{
  "description": "Manual holdings and forex pairs",
  "lastUpdated": "2025-12-29T00:00:00Z",
  "holdings": [
    {
      "symbol": "SOL",
      "quantity": 12.919,
      "type": "crypto",
      "source": "coinbase-staked",
      "notes": "100% staked"
    },
    {
      "symbol": "AAPL",
      "quantity": 50,
      "type": "stock",
      "source": "fidelity"
    },
    {
      "symbol": "VOO",
      "quantity": 25,
      "type": "etf",
      "source": "fidelity"
    },
    {
      "symbol": "SPAXX",
      "quantity": 1000,
      "type": "cash",
      "source": "fidelity",
      "notes": "Money market fund"
    },
    {
      "symbol": "PRCFX",
      "quantity": 53.498,
      "type": "mutual_fund",
      "source": "troweprice-ira",
      "notes": "T. Rowe Price Capital Appreciation and Income Fund"
    }
  ],
  "forex": [
    {"pair": "USD/PHP"},
    {"pair": "USD/EUR"}
  ]
}

Asset Types:

  • crypto - Cryptocurrency (priced via Coinbase)
  • stock - Individual stocks (priced via Twelve Data)
  • etf - Exchange-traded funds (priced via Twelve Data)
  • mutual_fund - Mutual funds (priced via EODHD when configured, otherwise Twelve Data fallback)
  • cash - Money market funds with stable $1.00 NAV (no API calls)

Optional Manual Fields:

  • costBasis - Total cost basis for gain/loss calculations
  • manualPrice - Optional local override price per share/unit when the pricing provider does not support the symbol
  • manualChange24h - Optional 24h change percentage paired with manualPrice (defaults to 0)

Forex Pairs: Inverse rates shown as a column (can be hidden via showInverseForex: false)

Note: You must update this file manually whenever your holdings change. If using SnapTrade, this file is only needed for holdings at unsupported brokerages.

Configuration Options

Option Default Accepted Values Description
title "Portfolio" Any string Header text
fontSize 100 Any number; interpreted as a percentage such as 80, 100, 120 Font size for the module wrapper
displayMode "table" "table", "ticker" Display holdings as a table or a horizontally scrolling ticker
tickerSpeed 50 Any positive number Ticker scroll speed in pixels per second
tickerStartDelay 3000 Any non-negative number Milliseconds to hold the ticker still before scrolling starts
tickerPause 0 Any non-negative number Milliseconds to pause on each item (0 disables pauses)
tickerCollapseCash false true, false When true, combines cash-equivalent holdings into a single Cash ticker item
currency "USD" Any currency code string such as "USD", "EUR", "GBP", "PHP" Currency code used for display; symbol rendering is built in for USD, EUR, GBP, JPY, CNY, PHP, CAD, AUD, CHF, INR, KRW, MXN, BRL, SGD, and HKD
currencyStyle "symbol" "symbol", "code" Display values with a currency symbol or currency code
cryptoPriceUpdateInterval 300000 Any positive number Crypto price refresh interval in milliseconds
stockPriceUpdateInterval 1200000 Any positive number Stock, ETF, mutual fund, and forex refresh interval in milliseconds
showLastUpdated true true, false Show the last successful sync timestamp
showPricePerUnit true true, false Show the price-per-unit column
showQuantity true true, false Show quantity and value columns, plus the total row
showGainLoss true true, false Show gain/loss percentage when cost basis data is available
showForex true true, false Show the forex rates section
showInverseForex true true, false Show inverse rates in the forex section
cryptoAsForex [] Array of crypto symbol strings, for example ["BTC", "ETH"] Treat selected crypto holdings as forex-style rates
sortBy "value" "value", "name" Sort holdings by descending value or by symbol/name
maxRetries 6 Any non-negative integer Maximum retry attempts for provider API requests
holdingsSyncTime "07:45" Time string in HH:MM 24-hour format Daily holdings sync time
staleHoldingsThreshold 90000000 Any positive number Holdings age threshold in milliseconds before data is marked stale
stalePricesThreshold 3900000 Any positive number Price age threshold in milliseconds before data is marked stale
showCharts false true, false Enable portfolio history charts
chartMode "combined" "combined", "separate", "exclude-crypto" Use one combined chart, split traditional and crypto into separate charts, or hide crypto from the chart
chartPeriod "1M" "1D", "1W", "1M", "3M", "1Y", "All" Default chart history period
showPeriodSelector false true, false Show chart period buttons for touch or click interaction
historyRetention 1825 Any positive integer Daily-history retention in days
hourlyRetention 720 Any positive integer Hourly-history retention in hours
marketHours See below Object with stock, etf, mutual_fund, and forex schedules Market-hour polling schedules by asset type

Market Hours Scheduling

Stock, ETF, and mutual fund price updates are limited to US market hours by default. Forex updates follow the 24/5 forex market schedule (Sunday 5pm - Friday 5pm ET). This reduces unnecessary API calls and rate limit issues outside trading hours.

Default Configuration:

marketHours: {
  stock: {
    enabled: true,
    timezone: "America/New_York",
    open: "09:30",
    close: "16:00",
    days: [1, 2, 3, 4, 5],  // Mon-Fri (0=Sun, 6=Sat)
    postClosePoll: true     // One final update after close
  },
  etf: {
    enabled: true,
    timezone: "America/New_York",
    open: "09:30",
    close: "16:00",
    days: [1, 2, 3, 4, 5],
    postClosePoll: true
  },
  mutual_fund: {
    enabled: true,
    timezone: "America/New_York",
    open: "09:30",
    close: "16:00",
    days: [1, 2, 3, 4, 5],
    postClosePoll: true
  },
  forex: {
    enabled: true,
    timezone: "America/New_York",
    sundayOpen: "17:00",    // Market opens Sunday 5pm ET
    fridayClose: "17:00"    // Market closes Friday 5pm ET
  }
}

Disable Market Hours (poll 24/7):

config: {
  marketHours: {
    stock: { enabled: false },
    etf: { enabled: false },
    mutual_fund: { enabled: false },
    forex: { enabled: false }
  }
}

Key behaviors:

  • postClosePoll: When enabled, allows one final price update after market close each trading day
  • days: Array of trading days (0=Sunday through 6=Saturday)
  • Crypto: Always updates regardless of market hours (24/7 market)

Example Configurations

Privacy Mode (hide quantities and total)

config: {
  showQuantity: false,
  showPricePerUnit: true
}

Euro Display

config: {
  currency: "EUR",
  currencyStyle: "symbol"
}

Show BTC/ETH as Exchange Rates

config: {
  cryptoAsForex: ["BTC", "ETH"],
  showInverseForex: false
}

Smaller Font

config: {
  fontSize: 80
}

Enable Portfolio Charts

config: {
  showCharts: true,
  chartPeriod: "1M"
}

Separate Crypto Chart

config: {
  showCharts: true,
  chartMode: "separate"
}

Ticker Mode

Ticker mode displays holdings as a horizontal scrolling ticker bar, ideal for TV displays or landscape orientations.

Features:

  • Portfolio total shown first with value-weighted 24h change
  • Cash-equivalent holdings appear immediately after the portfolio total
  • Cash-equivalent holdings show total value instead of a static $1.00 NAV
  • Each holding shows: Symbol, Price, Change %
  • Green/red color coding for positive/negative changes
  • (Closed) indicator for non-crypto assets when markets are closed
  • Charts can still be displayed alongside the ticker

Basic Ticker Configuration:

config: {
  displayMode: "ticker",
  tickerSpeed: 50,      // pixels per second
  tickerStartDelay: 3000,
  tickerPause: 0        // no pause between items
}

Ticker with Collapsed Cash:

config: {
  displayMode: "ticker",
  tickerSpeed: 50,
  tickerStartDelay: 3000,
  tickerCollapseCash: true
}

Ticker with Chart:

config: {
  displayMode: "ticker",
  tickerSpeed: 40,
  tickerStartDelay: 3000,
  showCharts: true,
  chartMode: "combined",
  chartPeriod: "1W"
}

Market Status:

  • Crypto: Always shows live 24h change (24/7 market)
  • Stocks/ETFs/Mutual Funds: Shows change since previous close during market hours; shows last trading day's change with (Closed) indicator when markets are closed
  • Forex: Shows change since previous close during trading hours (Sun 5pm - Fri 5pm ET)

Portfolio Charts

The module can display portfolio value charts over time.

Chart Modes:

  • combined - Single chart showing total portfolio value
  • separate - Two charts: Traditional investments + Crypto
  • exclude-crypto - Single chart showing only traditional investments

Chart Defaults:

  • chartMode: "combined"
  • chartPeriod: "1M"
  • showPeriodSelector: false

Time Periods:

  • 1D - Last 24 hours (uses hourly snapshots)
  • 1W - Last 7 days (uses hourly snapshots for higher granularity)
  • 1M - Last 30 days (uses hourly snapshots if available, falls back to daily)
  • 3M, 1Y, All - Use daily snapshots

Chart Features:

  • Cost Basis Line: Dashed horizontal line showing your total cost basis (requires cost basis data from SnapTrade or manual holdings)
  • Period Change Label: Shows the percentage change over the displayed period (e.g., "+5.23%") in green/red
  • Optional Period Buttons: Set showPeriodSelector: true to show 1D, 1W, 1M, 3M, 1Y, and All buttons in the UI
  • Improved Tick Marks: Cleaner Y-axis formatting with appropriate precision

Data Collection:

  • Hourly snapshots recorded during each price update (default: 30 days / 720 hours retention)
  • Daily snapshots recorded during the morning holdings sync (default: 5 years retention)
  • Data starts fresh from when you enable charts (no historical backfill)

Storage Requirements (10 holdings):

  • ~120 bytes per hourly snapshot
  • ~400 bytes per daily snapshot
  • ~100 KB for 30 days of hourly data
  • ~150 KB per year of daily data

Scheduling

  • Holdings sync: Daily at configured time (default: 7:45am), plus on startup if data >24 hours old
  • Crypto prices: Every 5 minutes (configurable), 24/7
  • Stock/ETF prices: Every 20 minutes during market hours (configurable)
  • Forex prices: Every 20 minutes during forex market hours (Sun 5pm - Fri 5pm ET)

API Rate Limits

Twelve Data Free Tier: 800 calls/day

  • With 20-minute intervals during market hours: ~200 calls/day for 10 symbols
  • Currency conversion adds 1 extra call per sync (if not USD)
  • Crypto uses Coinbase (separate limit)

Files

File Description Git
MMM-Fintech.js Frontend: DOM rendering, socket handling Tracked
node_helper.js Backend: Provider coordination, scheduling, caching Tracked
MMM-Fintech.css Module styling Tracked
providers/index.js Factory functions for provider creation Tracked
providers/base.js Base class with shared utilities Tracked
providers/coinbase.js Coinbase CDP API provider (crypto) Tracked
providers/eodhd.js EODHD provider (mutual fund pricing) Tracked
providers/twelvedata.js Twelve Data API provider (stocks/ETFs/forex) Tracked
providers/snaptrade.js SnapTrade API provider (brokerage holdings) Tracked
lib/history-manager.js Chart history snapshot management Tracked
health-check.js Runs the bundled diagnostics with a summary report Tracked
setup-credentials.js CLI tool to encrypt Coinbase CDP API key Tracked
setup-eodhd.js CLI tool to encrypt EODHD API key Tracked
setup-twelvedata.js CLI tool to encrypt Twelve Data API key Tracked
setup-snaptrade.js CLI tool to encrypt SnapTrade credentials Tracked
snaptrade-connect.js CLI tool to generate brokerage connection URL Tracked
test-eodhd.js CLI diagnostic for EODHD mutual fund pricing Tracked
cdp_api_key.json Original Coinbase key (delete after setup) Ignored
cdp-credentials.enc Encrypted Coinbase credentials Ignored
eodhd-credentials.enc Encrypted EODHD credentials Ignored
twelvedata-credentials.enc Encrypted Twelve Data credentials Ignored
snaptrade-credentials.enc Encrypted SnapTrade credentials Ignored
~/.mmm-fintech-key Encryption key (shared by all providers) N/A
manual-holdings.json Manual holdings and forex Ignored
cache.json Cached data Ignored
history.json Chart history snapshots Ignored

Finding Valid Symbols

Crypto (Coinbase):

Stocks/ETFs (Twelve Data):

Mutual Funds (EODHD when configured, otherwise Twelve Data fallback):

  • Use standard fund tickers: PRCFX, PREIX, FXAIX
  • EODHD resolves supported symbols internally to exchange-form tickers like .US or .NMFQS

Forex (Twelve Data):

  • Format: BASE/QUOTE (e.g., USD/PHP, EUR/USD)

Troubleshooting

View Logs

pm2 logs <your-magicmirror-pm2-process> --lines 100 | grep -i fintech

Force Holdings Sync

rm ~/MagicMirror/modules/MMM-Fintech/cache.json
pm2 restart <your-magicmirror-pm2-process>

Diagnostic Test Scripts

The module includes test scripts to verify provider connections and data flow:

cd ~/MagicMirror/modules/MMM-Fintech

Health Check (Recommended)

Quick health check: Run all diagnostic tests in sequence with a summary report:

node health-check.js

This runs all five test scripts below and provides a pass/fail summary with timing information. Use this as your first diagnostic step.

Individual Test Scripts

Script Purpose When to Use
test-snaptrade-provider.js Tests SnapTrade initialization and holdings fetch After SnapTrade setup to verify credentials and brokerage connections
test-twelvedata.js Tests Twelve Data pricing for stocks, ETFs, and forex After Twelve Data setup to verify API key and symbol availability
test-eodhd.js Tests EODHD pricing for mutual funds After EODHD setup or when a mutual fund is not pricing correctly
test-full-sync.js Tests complete holdings sync across configured providers When holdings aren't appearing or to debug provider priority
test-costbasis.js Tests cost basis and gain/loss data from SnapTrade When G/L column shows incorrect values or dashes

Example usage:

# Run complete health check (recommended)
node health-check.js

# Or run individual tests for specific diagnostics
node test-snaptrade-provider.js
node test-twelvedata.js
node test-eodhd.js
node test-full-sync.js

Common Errors

"Crypto holdings data is N hours old"

  • Check sync logs: pm2 logs <your-magicmirror-pm2-process> --lines 100 | grep -E "Holdings sync"
  • Restart: pm2 restart <your-magicmirror-pm2-process>

"Invalid symbol 'XYZ'"

  • Verify symbol exists on the appropriate exchange
  • Check type field matches the asset (crypto vs stock)

"TwelveData provider not configured"

  • Run node setup-twelvedata.js to add credentials

"EODHD provider not configured"

  • Optional for mutual funds
  • Run node setup-eodhd.js to add credentials

"SnapTrade provider not configured"

  • Run node setup-snaptrade.js to add credentials

"Rate limit exceeded"

  • Twelve Data: Wait for credit reset (per minute)
  • Coinbase: Uses exponential backoff automatically

"Market closed for stock, skipped price refresh"

  • Normal behavior outside market hours
  • Set marketHours.stock.enabled: false to disable

"SnapTrade API timeout - check network/service status"

  • SnapTrade API failed to respond within timeout period (30 seconds)
  • Module automatically retries with exponential backoff: 1min, 2min, 4min
  • After 4 total attempts (1 initial + 3 retries), error is displayed in footer
  • Check logs for detailed retry information: pm2 logs <your-magicmirror-pm2-process> | grep SnapTrade
  • Usually resolves automatically on next sync cycle
  • If persistent, check SnapTrade service status or network connectivity

API Retry & Timeout Behavior

The SnapTrade provider includes robust retry logic with exponential backoff:

  • Timeout: 30 seconds per API call
  • Retry delays: 1 minute → 2 minutes → 4 minutes
  • Total attempts: 4 (initial + 3 retries)
  • Logging: All attempts and failures are logged with timestamps

If all retries are exhausted, the module continues operation with stale data and displays a timeout warning in the footer. The error clears automatically on the next successful sync.

Roadmap

See ROADMAP.md for planned features and development phases.

License

MIT

About

MagicMirror² module for securely displaying consolidated financial holdings (crypto and brokerage) using once-daily position sync and intraday pricing separation.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors