DEV Community

Paarthurnax
Paarthurnax

Posted on

Build a Free Crypto Portfolio Tracker Agent in 30 Minutes

Build a Free Crypto Portfolio Tracker Agent in 30 Minutes

Most portfolio trackers are web apps that require an account, track your data, and eventually upsell you. You don't need any of that. With Python and a few free APIs, you can build a portfolio tracker that runs on your machine, stores everything locally, and costs nothing to operate.

Let's build it.


What You'll Build

  • A portfolio tracker that fetches real-time prices
  • P&L calculation for each holding
  • A daily summary emailed or printed to terminal
  • Data stored locally (SQLite or JSON)
  • Optional: Telegram alerts when portfolio moves ±5%

Time to build: ~30 minutes

Cost: Free

External services: None required


Step 1: Define Your Portfolio

Start simple — a JSON file with your holdings:

{
  "holdings": [
    {"symbol": "BTC", "amount": 0.05, "avg_buy_price": 30000},
    {"symbol": "ETH", "amount": 1.5, "avg_buy_price": 1800},
    {"symbol": "SOL", "amount": 20, "avg_buy_price": 85},
    {"symbol": "BNB", "amount": 3, "avg_buy_price": 250}
  ]
}
Enter fullscreen mode Exit fullscreen mode

Save as portfolio.json. You'll update this when you buy/sell.


Step 2: Fetch Real-Time Prices

Binance offers a completely free, no-auth-required price API:

import requests

def get_prices(symbols: list) -> dict:
    """Fetch current prices for a list of symbols."""
    prices = {}
    for symbol in symbols:
        ticker = f"{symbol}USDT"
        url = f"https://api.binance.com/api/v3/ticker/price?symbol={ticker}"
        try:
            r = requests.get(url, timeout=5)
            prices[symbol] = float(r.json()["price"])
        except Exception as e:
            print(f"Error fetching {symbol}: {e}")
            prices[symbol] = None
    return prices
Enter fullscreen mode Exit fullscreen mode

Test it:

prices = get_prices(["BTC", "ETH", "SOL", "BNB"])
print(prices)
# {'BTC': 29540.0, 'ETH': 1847.32, 'SOL': 92.15, 'BNB': 248.70}
Enter fullscreen mode Exit fullscreen mode

Step 3: Calculate Portfolio Value and P&L

import json

def calculate_portfolio(portfolio_file="portfolio.json") -> dict:
    with open(portfolio_file) as f:
        portfolio = json.load(f)

    holdings = portfolio["holdings"]
    symbols = [h["symbol"] for h in holdings]
    prices = get_prices(symbols)

    results = []
    total_value = 0
    total_invested = 0

    for holding in holdings:
        symbol = holding["symbol"]
        amount = holding["amount"]
        avg_buy = holding["avg_buy_price"]
        current_price = prices.get(symbol)

        if current_price is None:
            continue

        current_value = amount * current_price
        invested = amount * avg_buy
        pnl = current_value - invested
        pnl_pct = ((current_price - avg_buy) / avg_buy) * 100

        results.append({
            "symbol": symbol,
            "amount": amount,
            "avg_buy": avg_buy,
            "current_price": current_price,
            "current_value": current_value,
            "invested": invested,
            "pnl": pnl,
            "pnl_pct": pnl_pct
        })

        total_value += current_value
        total_invested += invested

    total_pnl = total_value - total_invested
    total_pnl_pct = ((total_value - total_invested) / total_invested) * 100 if total_invested > 0 else 0

    return {
        "holdings": results,
        "total_value": total_value,
        "total_invested": total_invested,
        "total_pnl": total_pnl,
        "total_pnl_pct": total_pnl_pct
    }
Enter fullscreen mode Exit fullscreen mode

Step 4: Pretty-Print the Summary

from datetime import datetime

def print_portfolio_summary(data: dict):
    print(f"\n{'='*55}")
    print(f"  PORTFOLIO TRACKER — {datetime.now().strftime('%Y-%m-%d %H:%M')}")
    print(f"{'='*55}")
    print(f"{'ASSET':<8} {'AMOUNT':<10} {'PRICE':<12} {'VALUE':<12} {'P&L':<12} {'%'}")
    print(f"{'-'*55}")

    for h in data["holdings"]:
        pnl_str = f"${h['pnl']:+,.2f}"
        print(f"{h['symbol']:<8} {h['amount']:<10.4f} ${h['current_price']:<11,.2f} ${h['current_value']:<11,.2f} {pnl_str:<12} {h['pnl_pct']:+.1f}%")

    print(f"{'='*55}")
    print(f"Total Value:    ${data['total_value']:,.2f}")
    print(f"Total Invested: ${data['total_invested']:,.2f}")

    pnl_symbol = "+" if data['total_pnl'] >= 0 else ""
    print(f"Total P&L:      ${data['total_pnl']:+,.2f} ({data['total_pnl_pct']:+.2f}%)")
    print(f"{'='*55}")

# Run it
data = calculate_portfolio()
print_portfolio_summary(data)
Enter fullscreen mode Exit fullscreen mode

Output:

=======================================================
  PORTFOLIO TRACKER — 2024-03-22 09:15
=======================================================
ASSET    AMOUNT     PRICE        VALUE        P&L          %
-------------------------------------------------------
BTC      0.0500     $29,540.00   $1,477.00    -$23.00      -1.5%
ETH      1.5000     $1,847.32    $2,770.98    +$70.98      +2.6%
SOL      20.0000    $92.15       $1,843.00    +$143.00     +8.4%
BNB      3.0000     $248.70      $746.10      -$8.90       -1.2%
=======================================================
Total Value:    $6,837.08
Total Invested: $6,655.00
Total P&L:      +$182.08 (+2.74%)
=======================================================
Enter fullscreen mode Exit fullscreen mode

Step 5: Save Historical Data

Track your portfolio over time:

from pathlib import Path
import json
from datetime import datetime

HISTORY_FILE = Path("dragonsoul/portfolio_history.json")

def save_snapshot(data: dict):
    history = []
    if HISTORY_FILE.exists():
        history = json.loads(HISTORY_FILE.read_text())

    snapshot = {
        "timestamp": datetime.now().isoformat(),
        "total_value": data["total_value"],
        "total_pnl": data["total_pnl"],
        "total_pnl_pct": data["total_pnl_pct"]
    }
    history.append(snapshot)

    # Keep last 90 days of snapshots
    history = history[-2160:]  # 24 per day * 90 days

    HISTORY_FILE.parent.mkdir(exist_ok=True)
    HISTORY_FILE.write_text(json.dumps(history, indent=2))
Enter fullscreen mode Exit fullscreen mode

Step 6: Add Alerts

Alert when portfolio moves significantly:

TELEGRAM_TOKEN = "your_bot_token"
CHAT_ID = "your_chat_id"

def check_and_alert(data: dict, previous_value: float):
    if previous_value == 0:
        return

    change_pct = ((data["total_value"] - previous_value) / previous_value) * 100

    if abs(change_pct) >= 5:  # Alert on 5% swings
        emoji = "🚀" if change_pct > 0 else "📉"
        msg = f"{emoji} Portfolio moved {change_pct:+.1f}%!\nTotal: ${data['total_value']:,.2f} (P&L: {data['total_pnl_pct']:+.2f}%)"
        requests.post(
            f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage",
            json={"chat_id": CHAT_ID, "text": msg}
        )
Enter fullscreen mode Exit fullscreen mode

Step 7: Run on a Schedule

import time

while True:
    data = calculate_portfolio()
    print_portfolio_summary(data)
    save_snapshot(data)
    time.sleep(3600)  # Check every hour
Enter fullscreen mode Exit fullscreen mode

Or integrate with OpenClaw's felix_loop.py which handles scheduling, logging, and error recovery automatically.


Making It Better

From this foundation, you can add:

  • Charts — matplotlib for weekly P&L graphs
  • Rebalancing alerts — notify when allocation drifts from target
  • Tax tracking — log buys/sells for end-of-year calculation
  • Multi-exchange — pull data from Coinbase, Kraken, Binance simultaneously

Full Setup Available

The complete portfolio tracker with OpenClaw integration, Telegram alerts, and historical charting is at dragonwhisper36.gumroad.com. Everything pre-configured, runs locally in minutes.

Top comments (0)