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)
How to Use It
- Edit
DEFAULT_PORTFOLIOwith your actual holdings and average buy prices - Run:
python tracker.py - 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%)
======================================================
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:
- 🐍 DCA Crypto Bot Python (€14.99) — Full DCA simulator + portfolio tracker + alerts
- 📊 Trading Journal Pro Notion (€19.99) — Track every trade, P&L dashboard, psychology journal
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)