Building a Precious Metals Price Tracker with Python
Track gold, silver, platinum, and palladium prices in real-time with Python. This comprehensive tutorial shows you how to build a production-ready precious metals tracker using pandas for data analysis, matplotlib for visualization, and automated price alerts. Perfect for investors, developers, and data analysts.
Precious metals have been stores of value for thousands of years, and today's investors need sophisticated tools to track their performance. Whether you're managing a portfolio, conducting research, or building a fintech application, Python provides the perfect ecosystem for working with commodities data.
This tutorial builds a complete price tracking system from scratch. You'll learn how to fetch real-time and historical data for gold (XAU), silver (XAG), platinum (XPT), and palladium (XPD), analyze trends with pandas, create professional visualizations, and set up automated alerts. All code is production-ready and follows Python best practices.
1. Project Setup and Dependencies
Installing Required Libraries
First, set up a virtual environment and install the necessary packages:
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install requests pandas matplotlib numpy redis python-dotenv
# Create requirements.txt
cat > requirements.txt << EOF
requests==2.31.0
pandas==2.1.0
matplotlib==3.8.0
numpy==1.26.0
redis==5.0.0
python-dotenv==1.0.0
EOF
Project Structure
Organize your project with a clean structure:
precious-metals-tracker/
├── venv/
├── src/
│ ├── __init__.py
│ ├── api_client.py # API integration
│ ├── data_handler.py # Pandas data operations
│ ├── alerts.py # Price alert system
│ ├── visualizer.py # Matplotlib charts
│ └── utils.py # Helper functions
├── data/
│ ├── cache/ # Cached API responses
│ └── exports/ # CSV exports
├── config.py # Configuration
├── .env # API keys (git-ignored)
├── main.py # Entry point
└── requirements.txt
Configuration Setup
# config.py
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
"""Application configuration."""
# API Configuration
API_KEY = os.getenv('UNIRATE_API_KEY')
API_BASE_URL = 'https://api.unirateapi.com/v1'
# Supported precious metals
METALS = {
'XAU': {'name': 'Gold', 'color': '#FFD700'},
'XAG': {'name': 'Silver', 'color': '#C0C0C0'},
'XPT': {'name': 'Platinum', 'color': '#E5E4E2'},
'XPD': {'name': 'Palladium', 'color': '#CED0DD'}
}
# Redis cache settings
REDIS_HOST = os.getenv('REDIS_HOST', 'localhost')
REDIS_PORT = int(os.getenv('REDIS_PORT', 6379))
CACHE_TTL = 1800 # 30 minutes
# Data directories
DATA_DIR = 'data'
CACHE_DIR = os.path.join(DATA_DIR, 'cache')
EXPORT_DIR = os.path.join(DATA_DIR, 'exports')
# Alert thresholds (percentage change)
ALERT_THRESHOLD = 2.0 # Alert on 2% change
@classmethod
def ensure_directories(cls):
"""Create necessary directories."""
os.makedirs(cls.CACHE_DIR, exist_ok=True)
os.makedirs(cls.EXPORT_DIR, exist_ok=True)
Environment Variables: Create a .env file in your project root with your API key: UNIRATE_API_KEY=your_api_key_here. Never commit this file to version control.
2. Precious Metals API Integration
API Client Implementation
Build a robust API client with caching and error handling:
# src/api_client.py
import requests
import redis
import json
from datetime import datetime, timedelta
from decimal import Decimal
from typing import Dict, List, Optional
from config import Config
class PreciousMetalsAPI:
"""
API client for precious metals price data.
Supports gold, silver, platinum, and palladium.
"""
def __init__(self, api_key: str, use_cache: bool = True):
self.api_key = api_key
self.base_url = Config.API_BASE_URL
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'PreciousMetalsTracker/1.0'
})
# Redis cache
self.use_cache = use_cache
if use_cache:
self.redis = redis.Redis(
host=Config.REDIS_HOST,
port=Config.REDIS_PORT,
decode_responses=True
)
def get_current_prices(
self,
metals: List[str] = None,
base_currency: str = 'USD'
) -> Dict[str, Decimal]:
"""
Get current prices for specified metals.
Args:
metals: List of metal codes (e.g., ['XAU', 'XAG'])
base_currency: Currency for prices (default: USD)
Returns:
Dict mapping metal codes to prices per troy ounce
"""
if metals is None:
metals = list(Config.METALS.keys())
cache_key = f"current_prices:{base_currency}"
# Check cache
if self.use_cache:
cached = self.redis.get(cache_key)
if cached:
all_prices = json.loads(cached)
return {
metal: Decimal(all_prices[metal])
for metal in metals
if metal in all_prices
}
# Fetch from API
try:
response = self.session.get(
f"{self.base_url}/latest/{base_currency}",
params={'api_key': self.api_key},
timeout=10
)
response.raise_for_status()
data = response.json()
# Convert XAU/XAG/XPT/XPD rates to prices per troy ounce
prices = {}
for metal in metals:
if metal in data['rates']:
# Rate is troy ounces per base currency, invert it
rate = Decimal(str(data['rates'][metal]))
prices[metal] = Decimal('1') / rate
# Cache the results
if self.use_cache:
cache_data = {k: str(v) for k, v in prices.items()}
self.redis.setex(
cache_key,
Config.CACHE_TTL,
json.dumps(cache_data)
)
return prices
except requests.RequestException as e:
raise RuntimeError(f"Failed to fetch prices: {e}")
def get_historical_prices(
self,
metal: str,
start_date: str,
end_date: str,
base_currency: str = 'USD'
) -> Dict[str, Decimal]:
"""
Get historical prices for a metal over a date range.
Args:
metal: Metal code (e.g., 'XAU')
start_date: Start date (YYYY-MM-DD)
end_date: End date (YYYY-MM-DD)
base_currency: Currency for prices
Returns:
Dict mapping dates to prices
"""
try:
response = self.session.get(
f"{self.base_url}/timeseries",
params={
'api_key': self.api_key,
'base': base_currency,
'start_date': start_date,
'end_date': end_date
},
timeout=15
)
response.raise_for_status()
data = response.json()
prices = {}
for date, rates in data['rates'].items():
if metal in rates:
rate = Decimal(str(rates[metal]))
prices[date] = Decimal('1') / rate
return prices
except requests.RequestException as e:
raise RuntimeError(f"Failed to fetch historical data: {e}")
def get_price_for_date(
self,
metal: str,
date: str,
base_currency: str = 'USD'
) -> Decimal:
"""Get price for a specific metal on a specific date."""
try:
response = self.session.get(
f"{self.base_url}/historical/{date}/{base_currency}",
params={'api_key': self.api_key},
timeout=5
)
response.raise_for_status()
data = response.json()
if metal in data['rates']:
rate = Decimal(str(data['rates'][metal]))
return Decimal('1') / rate
raise ValueError(f"Metal {metal} not found in response")
except requests.RequestException as e:
raise RuntimeError(f"Failed to fetch price for {date}: {e}")
3. Working with Pandas DataFrames
Data Handler Implementation
# src/data_handler.py
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from typing import Dict, List
from decimal import Decimal
from config import Config
class MetalsDataHandler:
"""Handle precious metals data with pandas."""
def __init__(self, api_client):
self.api = api_client
def create_current_prices_df(self) -> pd.DataFrame:
"""
Create DataFrame with current prices for all metals.
Returns:
DataFrame with columns: metal, name, price, timestamp
"""
prices = self.api.get_current_prices()
data = []
for metal_code, price in prices.items():
data.append({
'metal': metal_code,
'name': Config.METALS[metal_code]['name'],
'price_usd': float(price),
'timestamp': datetime.now()
})
df = pd.DataFrame(data)
df['price_usd'] = df['price_usd'].round(2)
return df
def create_historical_df(
self,
metal: str,
days: int = 365
) -> pd.DataFrame:
"""
Create DataFrame with historical prices.
Args:
metal: Metal code (e.g., 'XAU')
days: Number of days of history
Returns:
DataFrame with date index and price column
"""
end_date = datetime.now()
start_date = end_date - timedelta(days=days)
prices = self.api.get_historical_prices(
metal,
start_date.strftime('%Y-%m-%d'),
end_date.strftime('%Y-%m-%d')
)
# Convert to DataFrame
df = pd.DataFrame([
{'date': date, 'price': float(price)}
for date, price in prices.items()
])
df['date'] = pd.to_datetime(df['date'])
df = df.sort_values('date')
df.set_index('date', inplace=True)
return df
def create_multi_metal_df(
self,
metals: List[str],
days: int = 90
) -> pd.DataFrame:
"""
Create DataFrame with multiple metals for comparison.
Returns:
DataFrame with date index and columns for each metal
"""
dfs = []
for metal in metals:
df = self.create_historical_df(metal, days)
df.columns = [metal]
dfs.append(df)
# Merge all DataFrames
result = pd.concat(dfs, axis=1)
result.fillna(method='ffill', inplace=True) # Forward fill missing values
return result
def calculate_statistics(self, df: pd.DataFrame) -> Dict:
"""
Calculate statistical metrics for price data.
Args:
df: DataFrame with price data
Returns:
Dict with statistics
"""
stats = {
'current': float(df['price'].iloc[-1]),
'mean': float(df['price'].mean()),
'median': float(df['price'].median()),
'std': float(df['price'].std()),
'min': float(df['price'].min()),
'max': float(df['price'].max()),
'change_pct': self._calculate_percent_change(df)
}
return stats
def _calculate_percent_change(self, df: pd.DataFrame) -> float:
"""Calculate percentage change from first to last value."""
if len(df) < 2:
return 0.0
first = df['price'].iloc[0]
last = df['price'].iloc[-1]
return float(((last - first) / first) * 100)
def calculate_moving_averages(
self,
df: pd.DataFrame,
windows: List[int] = [7, 30, 90]
) -> pd.DataFrame:
"""
Add moving average columns to DataFrame.
Args:
df: DataFrame with price column
windows: List of window sizes for moving averages
Returns:
DataFrame with additional MA columns
"""
result = df.copy()
for window in windows:
col_name = f'MA_{window}'
result[col_name] = result['price'].rolling(
window=window,
min_periods=1
).mean()
return result
Usage Examples
# Example: Working with the data handler
from src.api_client import PreciousMetalsAPI
from src.data_handler import MetalsDataHandler
from config import Config
# Initialize
api = PreciousMetalsAPI(Config.API_KEY)
handler = MetalsDataHandler(api)
# Get current prices
current_df = handler.create_current_prices_df()
print("Current Precious Metals Prices:")
print(current_df)
print()
# Get gold historical data
gold_df = handler.create_historical_df('XAU', days=365)
gold_stats = handler.calculate_statistics(gold_df)
print("Gold Statistics (1 year):")
for key, value in gold_stats.items():
print(f" {key}: ${value:.2f}" if key != 'change_pct'
else f" {key}: {value:.2f}%")
print()
# Compare all metals
multi_df = handler.create_multi_metal_df(['XAU', 'XAG', 'XPT', 'XPD'], days=90)
print("90-day correlation matrix:")
print(multi_df.corr())
4. Implementing Price Alert System
Alert Manager
# src/alerts.py
import json
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime
from typing import Dict, List, Optional
from decimal import Decimal
import redis
from config import Config
class PriceAlert:
"""Individual price alert configuration."""
def __init__(
self,
metal: str,
target_price: Decimal,
direction: str, # 'above' or 'below'
email: Optional[str] = None
):
self.metal = metal
self.target_price = target_price
self.direction = direction
self.email = email
self.created_at = datetime.now()
self.triggered = False
def check(self, current_price: Decimal) -> bool:
"""Check if alert should trigger."""
if self.triggered:
return False
if self.direction == 'above':
return current_price >= self.target_price
else: # below
return current_price <= self.target_price
class AlertManager:
"""Manage price alerts for precious metals."""
def __init__(self, api_client):
self.api = api_client
self.redis = redis.Redis(
host=Config.REDIS_HOST,
port=Config.REDIS_PORT,
decode_responses=True
)
self.alerts_key = 'metal_alerts'
def add_alert(self, alert: PriceAlert) -> str:
"""
Add a new price alert.
Returns:
Alert ID
"""
alert_id = f"{alert.metal}_{datetime.now().timestamp()}"
alert_data = {
'id': alert_id,
'metal': alert.metal,
'target_price': str(alert.target_price),
'direction': alert.direction,
'email': alert.email,
'created_at': alert.created_at.isoformat(),
'triggered': False
}
# Store in Redis
self.redis.hset(
self.alerts_key,
alert_id,
json.dumps(alert_data)
)
return alert_id
def check_all_alerts(self) -> List[Dict]:
"""
Check all active alerts against current prices.
Returns:
List of triggered alerts
"""
triggered_alerts = []
# Get current prices
current_prices = self.api.get_current_prices()
# Get all alerts
alerts_data = self.redis.hgetall(self.alerts_key)
for alert_id, alert_json in alerts_data.items():
alert_data = json.loads(alert_json)
if alert_data['triggered']:
continue
metal = alert_data['metal']
if metal not in current_prices:
continue
current_price = current_prices[metal]
target_price = Decimal(alert_data['target_price'])
direction = alert_data['direction']
# Check if alert triggers
should_trigger = (
(direction == 'above' and current_price >= target_price) or
(direction == 'below' and current_price <= target_price)
)
if should_trigger:
alert_data['triggered'] = True
alert_data['triggered_at'] = datetime.now().isoformat()
alert_data['triggered_price'] = str(current_price)
# Update in Redis
self.redis.hset(
self.alerts_key,
alert_id,
json.dumps(alert_data)
)
triggered_alerts.append(alert_data)
# Send notification if email provided
if alert_data['email']:
self._send_email_notification(alert_data, current_price)
return triggered_alerts
def _send_email_notification(
self,
alert_data: Dict,
current_price: Decimal
):
"""Send email notification for triggered alert."""
metal_name = Config.METALS[alert_data['metal']]['name']
subject = f"Price Alert: {metal_name} {alert_data['direction']} ${alert_data['target_price']}"
body = f"""
Your price alert has been triggered!
Metal: {metal_name} ({alert_data['metal']})
Target Price: ${alert_data['target_price']}
Direction: {alert_data['direction']}
Current Price: ${current_price:.2f}
Alert created: {alert_data['created_at']}
Triggered: {alert_data['triggered_at']}
"""
# Note: Configure SMTP settings in your environment
# This is a placeholder implementation
print(f"Email notification: {subject}")
print(body)
def get_active_alerts(self) -> List[Dict]:
"""Get all active (non-triggered) alerts."""
alerts_data = self.redis.hgetall(self.alerts_key)
active = []
for alert_json in alerts_data.values():
alert_data = json.loads(alert_json)
if not alert_data['triggered']:
active.append(alert_data)
return active
def remove_alert(self, alert_id: str):
"""Remove an alert."""
self.redis.hdel(self.alerts_key, alert_id)
Production Note: For email notifications, configure SMTP settings using environment variables. Consider using services like SendGrid or AWS SES for reliable email delivery in production.
5. Data Visualization with Matplotlib
Chart Generator
# src/visualizer.py
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import pandas as pd
from typing import List, Optional
from config import Config
import os
class MetalsVisualizer:
"""Create charts for precious metals data."""
def __init__(self, output_dir: Optional[str] = None):
self.output_dir = output_dir or Config.EXPORT_DIR
# Set style
plt.style.use('seaborn-v0_8-darkgrid')
def plot_price_history(
self,
df: pd.DataFrame,
metal: str,
title: Optional[str] = None,
show_ma: bool = True,
save: bool = True
):
"""
Plot price history for a single metal.
Args:
df: DataFrame with date index and price column
metal: Metal code
title: Chart title
show_ma: Show moving averages
save: Save to file
"""
fig, ax = plt.subplots(figsize=(12, 6))
# Plot price
metal_name = Config.METALS[metal]['name']
color = Config.METALS[metal]['color']
ax.plot(
df.index,
df['price'],
color=color,
linewidth=2,
label=f'{metal_name} Price'
)
# Add moving averages
if show_ma and 'MA_7' in df.columns:
ax.plot(df.index, df['MA_7'], '--', alpha=0.7, label='7-day MA')
ax.plot(df.index, df['MA_30'], '--', alpha=0.7, label='30-day MA')
# Formatting
ax.set_title(title or f'{metal_name} Price History', fontsize=16, fontweight='bold')
ax.set_xlabel('Date', fontsize=12)
ax.set_ylabel('Price (USD per troy oz)', fontsize=12)
ax.legend(loc='best')
ax.grid(True, alpha=0.3)
# Format x-axis dates
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
plt.xticks(rotation=45)
plt.tight_layout()
if save:
filename = f"{metal}_history_{pd.Timestamp.now().strftime('%Y%m%d')}.png"
filepath = os.path.join(self.output_dir, filename)
plt.savefig(filepath, dpi=300, bbox_inches='tight')
print(f"Chart saved: {filepath}")
plt.show()
def plot_multi_metal_comparison(
self,
df: pd.DataFrame,
metals: List[str],
normalize: bool = True,
save: bool = True
):
"""
Compare multiple metals on one chart.
Args:
df: DataFrame with columns for each metal
metals: List of metal codes
normalize: Normalize to percentage change
save: Save to file
"""
fig, ax = plt.subplots(figsize=(14, 7))
for metal in metals:
if metal not in df.columns:
continue
data = df[metal].copy()
if normalize:
# Normalize to percentage change from start
data = ((data / data.iloc[0]) - 1) * 100
metal_name = Config.METALS[metal]['name']
color = Config.METALS[metal]['color']
ax.plot(
df.index,
data,
color=color,
linewidth=2,
label=metal_name
)
# Formatting
title = 'Precious Metals Comparison'
if normalize:
title += ' (% Change)'
ax.set_ylabel('Change from Start (%)', fontsize=12)
else:
ax.set_ylabel('Price (USD per troy oz)', fontsize=12)
ax.set_title(title, fontsize=16, fontweight='bold')
ax.set_xlabel('Date', fontsize=12)
ax.legend(loc='best')
ax.grid(True, alpha=0.3)
# Format x-axis
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
plt.xticks(rotation=45)
plt.tight_layout()
if save:
filename = f"metals_comparison_{pd.Timestamp.now().strftime('%Y%m%d')}.png"
filepath = os.path.join(self.output_dir, filename)
plt.savefig(filepath, dpi=300, bbox_inches='tight')
print(f"Chart saved: {filepath}")
plt.show()
def plot_correlation_heatmap(
self,
df: pd.DataFrame,
save: bool = True
):
"""Plot correlation heatmap for multiple metals."""
import numpy as np
fig, ax = plt.subplots(figsize=(10, 8))
# Calculate correlation
corr = df.corr()
# Create heatmap
im = ax.imshow(corr, cmap='RdYlGn', aspect='auto', vmin=-1, vmax=1)
# Set ticks and labels
metals = [Config.METALS[m]['name'] for m in corr.columns]
ax.set_xticks(range(len(metals)))
ax.set_yticks(range(len(metals)))
ax.set_xticklabels(metals)
ax.set_yticklabels(metals)
# Rotate labels
plt.setp(ax.get_xticklabels(), rotation=45, ha='right')
# Add correlation values
for i in range(len(corr)):
for j in range(len(corr)):
text = ax.text(j, i, f'{corr.iloc[i, j]:.2f}',
ha='center', va='center', color='black')
ax.set_title('Precious Metals Price Correlation',
fontsize=16, fontweight='bold', pad=20)
# Add colorbar
cbar = plt.colorbar(im, ax=ax)
cbar.set_label('Correlation Coefficient', rotation=270, labelpad=20)
plt.tight_layout()
if save:
filename = f"correlation_heatmap_{pd.Timestamp.now().strftime('%Y%m%d')}.png"
filepath = os.path.join(self.output_dir, filename)
plt.savefig(filepath, dpi=300, bbox_inches='tight')
print(f"Chart saved: {filepath}")
plt.show()
6. Historical Data Analysis
Advanced Analytics
import pandas as pd
import numpy as np
from scipy import stats
class MetalsAnalyzer:
"""Advanced analysis for precious metals data."""
@staticmethod
def calculate_volatility(df: pd.DataFrame, window: int = 30) -> pd.Series:
"""
Calculate rolling volatility (standard deviation of returns).
Args:
df: DataFrame with price column
window: Rolling window size
Returns:
Series with volatility values
"""
returns = df['price'].pct_change()
volatility = returns.rolling(window=window).std() * np.sqrt(252)
return volatility
@staticmethod
def find_support_resistance(
df: pd.DataFrame,
window: int = 20
) -> dict:
"""
Find support and resistance levels.
Returns:
Dict with support and resistance prices
"""
rolling_min = df['price'].rolling(window=window).min()
rolling_max = df['price'].rolling(window=window).max()
return {
'support': float(rolling_min.iloc[-1]),
'resistance': float(rolling_max.iloc[-1]),
'current': float(df['price'].iloc[-1])
}
@staticmethod
def calculate_rsi(df: pd.DataFrame, period: int = 14) -> pd.Series:
"""
Calculate Relative Strength Index (RSI).
Args:
df: DataFrame with price column
period: RSI period
Returns:
Series with RSI values (0-100)
"""
delta = df['price'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))
return rsi
# Usage example
from src.api_client import PreciousMetalsAPI
from src.data_handler import MetalsDataHandler
from config import Config
api = PreciousMetalsAPI(Config.API_KEY)
handler = MetalsDataHandler(api)
analyzer = MetalsAnalyzer()
# Get gold data
gold_df = handler.create_historical_df('XAU', days=365)
# Calculate metrics
volatility = analyzer.calculate_volatility(gold_df)
support_resistance = analyzer.find_support_resistance(gold_df)
rsi = analyzer.calculate_rsi(gold_df)
print("Gold Analysis:")
print(f"Current Volatility: {volatility.iloc[-1]:.2%}")
print(f"Support Level: ${support_resistance['support']:.2f}")
print(f"Resistance Level: ${support_resistance['resistance']:.2f}")
print(f"Current RSI: {rsi.iloc[-1]:.2f}")
7. CSV Export and Data Persistence
Export Manager
import pandas as pd
import os
from datetime import datetime
from config import Config
class DataExporter:
"""Export precious metals data to various formats."""
def __init__(self, output_dir: str = None):
self.output_dir = output_dir or Config.EXPORT_DIR
def export_to_csv(
self,
df: pd.DataFrame,
filename: str,
include_timestamp: bool = True
) -> str:
"""
Export DataFrame to CSV file.
Returns:
Path to exported file
"""
if include_timestamp:
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = f"{filename}_{timestamp}.csv"
filepath = os.path.join(self.output_dir, filename)
df.to_csv(filepath, index=True)
print(f"Data exported to: {filepath}")
return filepath
def export_summary_report(
self,
data_handler,
metals: list = None
) -> str:
"""
Generate and export comprehensive summary report.
Returns:
Path to report file
"""
if metals is None:
metals = list(Config.METALS.keys())
report_data = []
for metal in metals:
# Get 1-year data
df = data_handler.create_historical_df(metal, days=365)
stats = data_handler.calculate_statistics(df)
report_data.append({
'Metal': Config.METALS[metal]['name'],
'Code': metal,
'Current Price': f"${stats['current']:.2f}",
'Avg Price (1Y)': f"${stats['mean']:.2f}",
'1Y High': f"${stats['max']:.2f}",
'1Y Low': f"${stats['min']:.2f}",
'Change (1Y)': f"{stats['change_pct']:.2f}%",
'Volatility': f"${stats['std']:.2f}"
})
report_df = pd.DataFrame(report_data)
filename = f"metals_summary_report_{datetime.now().strftime('%Y%m%d')}.csv"
filepath = self.export_to_csv(report_df, filename, include_timestamp=False)
return filepath
8. Advanced Features and Optimization
Complete Application
# main.py
import argparse
from src.api_client import PreciousMetalsAPI
from src.data_handler import MetalsDataHandler
from src.alerts import AlertManager, PriceAlert
from src.visualizer import MetalsVisualizer
from src.utils import DataExporter
from config import Config
from decimal import Decimal
def main():
"""Main application entry point."""
parser = argparse.ArgumentParser(
description='Precious Metals Price Tracker'
)
parser.add_argument(
'--mode',
choices=['current', 'history', 'alerts', 'compare', 'export'],
required=True,
help='Operation mode'
)
parser.add_argument('--metal', help='Metal code (XAU, XAG, XPT, XPD)')
parser.add_argument('--days', type=int, default=90, help='Days of history')
args = parser.parse_args()
# Initialize components
Config.ensure_directories()
api = PreciousMetalsAPI(Config.API_KEY)
handler = MetalsDataHandler(api)
visualizer = MetalsVisualizer()
exporter = DataExporter()
if args.mode == 'current':
# Display current prices
df = handler.create_current_prices_df()
print("\n" + "="*50)
print("CURRENT PRECIOUS METALS PRICES")
print("="*50)
print(df.to_string(index=False))
print("="*50 + "\n")
elif args.mode == 'history':
# Show price history
metal = args.metal or 'XAU'
df = handler.create_historical_df(metal, args.days)
df = handler.calculate_moving_averages(df)
stats = handler.calculate_statistics(df)
print(f"\n{Config.METALS[metal]['name']} Statistics ({args.days} days):")
for key, value in stats.items():
if key == 'change_pct':
print(f" {key}: {value:.2f}%")
else:
print(f" {key}: ${value:.2f}")
# Generate chart
visualizer.plot_price_history(df, metal, show_ma=True)
elif args.mode == 'compare':
# Compare all metals
metals = list(Config.METALS.keys())
df = handler.create_multi_metal_df(metals, args.days)
visualizer.plot_multi_metal_comparison(df, metals, normalize=True)
visualizer.plot_correlation_heatmap(df)
elif args.mode == 'export':
# Export summary report
filepath = exporter.export_summary_report(handler)
print(f"Summary report exported to: {filepath}")
elif args.mode == 'alerts':
# Check alerts
alert_mgr = AlertManager(api)
triggered = alert_mgr.check_all_alerts()
if triggered:
print(f"\n{len(triggered)} alert(s) triggered:")
for alert in triggered:
print(f" {alert['metal']}: ${alert['triggered_price']}")
else:
print("No alerts triggered")
if __name__ == '__main__':
main()
Running the Application:
python main.py --mode current
python main.py --mode history --metal XAU --days 365
python main.py --mode compare --days 180
python main.py --mode export
Conclusion
You now have a complete, production-ready precious metals price tracking system built with Python. This application demonstrates professional software engineering practices: clean architecture, proper error handling, caching for performance, comprehensive data analysis with pandas, and beautiful visualizations with matplotlib.
You can extend this foundation to add features like automated trading signals, portfolio management, SMS alerts, web dashboards with Flask or Django, or integration with other financial data sources. The modular design makes it easy to add new capabilities without refactoring existing code.
Related Articles
Power Your App with Precious Metals Data
UniRate API provides reliable precious metals pricing for gold, silver, platinum, and palladium with historical data back to 1968. Perfect for the price tracker you just built. Simple REST API, generous free tier, and affordable pricing.
Get Started Free