DEV Community

Otto
Otto

Posted on

Build a Crypto Portfolio Tracker in Python: Live Prices, P&L & Allocation in 150 Lines

Tags: python, crypto, bitcoin, programming


I got tired of switching between 4 tabs to check my crypto portfolio. So I built a simple Python tracker that pulls live prices, calculates P&L, and shows portfolio allocation — no API key needed.

Here's what it does:

  • Fetches live prices from CoinGecko (free, no signup)
  • Calculates profit/loss per coin and overall
  • Shows portfolio allocation as percentages
  • Saves history to a JSON file

The Full Script

import json
import urllib.request
import datetime
import os

PORTFOLIO_FILE = "portfolio.json"
HISTORY_FILE = "history.json"

# Your holdings — edit this
DEFAULT_PORTFOLIO = {
    "bitcoin": {"amount": 0.1, "avg_buy_price": 35000},
    "ethereum": {"amount": 1.5, "avg_buy_price": 2200},
    "solana": {"amount": 10, "avg_buy_price": 120},
}

def load_portfolio():
    if os.path.exists(PORTFOLIO_FILE):
        with open(PORTFOLIO_FILE) as f:
            return json.load(f)
    return DEFAULT_PORTFOLIO

def save_portfolio(portfolio):
    with open(PORTFOLIO_FILE, "w") as f:
        json.dump(portfolio, f, indent=2)

def get_prices(coin_ids):
    """Fetch live prices from CoinGecko — no API key required."""
    ids = ",".join(coin_ids)
    url = f"https://api.coingecko.com/api/v3/simple/price?ids={ids}&vs_currencies=usd,eur"
    req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
    with urllib.request.urlopen(req, timeout=10) as response:
        return json.loads(response.read())

def calculate_pnl(portfolio, prices):
    results = []
    total_invested = 0
    total_current = 0

    for coin, data in portfolio.items():
        if coin not in prices:
            continue

        current_price = prices[coin].get("eur", 0)
        amount = data["amount"]
        avg_buy = data["avg_buy_price"]

        invested = amount * avg_buy
        current_value = amount * current_price
        pnl = current_value - invested
        pnl_pct = (pnl / invested * 100) if invested > 0 else 0

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

        total_invested += invested
        total_current += current_value

    return results, total_invested, total_current

def display_portfolio(results, total_invested, total_current):
    print("\n" + "="*60)
    print(f"  CRYPTO PORTFOLIO TRACKER — {datetime.date.today()}")
    print("="*60)

    for r in results:
        allocation = (r["current_value"] / total_current * 100) if total_current > 0 else 0
        sign = "+" if r["pnl"] >= 0 else ""
        color = "🟢" if r["pnl"] >= 0 else "🔴"

        print(f"\n{color} {r['coin']}")
        print(f"   Amount:        {r['amount']}")
        print(f"   Avg buy:       €{r['avg_buy_price']:,.2f}")
        print(f"   Current:       €{r['current_price']:,.2f}")
        print(f"   Value:         €{r['current_value']:,.2f}")
        print(f"   P&L:           {sign}{r['pnl']:,.2f} ({sign}{r['pnl_pct']:.1f}%)")
        print(f"   Allocation:    {allocation:.1f}%")

    total_pnl = total_current - total_invested
    total_pnl_pct = (total_pnl / total_invested * 100) if total_invested > 0 else 0
    sign = "+" if total_pnl >= 0 else ""

    print("\n" + "-"*60)
    print(f"  💰 TOTAL INVESTED:   €{total_invested:,.2f}")
    print(f"  📊 CURRENT VALUE:    €{total_current:,.2f}")
    print(f"  📈 TOTAL P&L:        {sign}{total_pnl:,.2f} ({sign}{total_pnl_pct:.1f}%)")
    print("="*60 + "\n")

def save_snapshot(results, total_invested, total_current):
    """Save daily snapshot for history tracking."""
    history = []
    if os.path.exists(HISTORY_FILE):
        with open(HISTORY_FILE) as f:
            history = json.load(f)

    snapshot = {
        "date": datetime.date.today().isoformat(),
        "total_invested": total_invested,
        "total_value": total_current,
        "pnl": total_current - total_invested,
        "coins": {r["coin"]: r["current_price"] for r in results}
    }

    # Replace today's snapshot if exists
    history = [h for h in history if h["date"] != snapshot["date"]]
    history.append(snapshot)
    history.sort(key=lambda x: x["date"])

    with open(HISTORY_FILE, "w") as f:
        json.dump(history, f, indent=2)
    print(f"  ✅ Snapshot saved to {HISTORY_FILE}")

def show_history():
    if not os.path.exists(HISTORY_FILE):
        print("No history yet. Run the tracker first.")
        return

    with open(HISTORY_FILE) as f:
        history = json.load(f)

    print("\n📅 PORTFOLIO HISTORY")
    print("-"*50)
    for h in history[-10:]:  # Last 10 days
        pnl = h["pnl"]
        sign = "+" if pnl >= 0 else ""
        print(f"  {h['date']}{h['total_value']:,.2f}  ({sign}{pnl:,.2f})")

if __name__ == "__main__":
    import sys

    if len(sys.argv) > 1 and sys.argv[1] == "--history":
        show_history()
        exit()

    portfolio = load_portfolio()

    print("🔄 Fetching live prices...")
    try:
        prices = get_prices(list(portfolio.keys()))
    except Exception as e:
        print(f"❌ Error fetching prices: {e}")
        exit(1)

    results, total_invested, total_current = calculate_pnl(portfolio, prices)
    display_portfolio(results, total_invested, total_current)
    save_snapshot(results, total_invested, total_current)
Enter fullscreen mode Exit fullscreen mode

How to Use It

  1. Edit DEFAULT_PORTFOLIO with your actual holdings and average buy prices
  2. Run: python tracker.py
  3. Check history: python tracker.py --history

That's it. No pip install, no API keys. Just Python stdlib + CoinGecko's free endpoint.

What You'll See

======================================================
  CRYPTO PORTFOLIO TRACKER — 2026-03-21
======================================================

🟢 BITCOIN
   Amount:        0.1
   Avg buy:       €35,000.00
   Current:       €78,450.00
   Value:         €7,845.00
   P&L:           +€3,945.00 (+11.3%)
   Allocation:    68.2%

🔴 ETHEREUM
   Amount:        1.5
   Avg buy:       €2,200.00
   Current:       €1,890.00
   Value:         €2,835.00
   P&L:           -€465.00 (-14.1%)
   Allocation:    24.7%

------------------------------------------------------
  💰 TOTAL INVESTED:   €8,800.00
  📊 CURRENT VALUE:    €11,495.00
  📈 TOTAL P&L:        +€2,695.00 (+30.6%)
======================================================
Enter fullscreen mode Exit fullscreen mode

Want More?

If you want the full version with DCA planning, automated alerts, monthly reports, and a complete trading journal in Notion, I built exactly that:

The tracker above is a solid start. The full kit saves you another 10 hours of building. Your call.


Built this for my own use. Sharing because it took me a few hours to get right and might save you the same time.

Top comments (0)