import json
import os
import time
from datetime import datetime
from pathlib import Path
from collections import defaultdict
try:
import requests
REQUESTS_OK = True
except ImportError:
REQUESTS_OK = False
# ============================================================
# CONFIGURATION
# ============================================================
# Free API — no key needed (up to 1500 requests/month)
BASE_URL = "https://api.exchangerate-api.com/v4/latest/"
CACHE_FILE = "exchange_rates_cache.json"
HISTORY_FILE = "conversion_history.json"
CACHE_EXPIRY = 3600 # seconds (1 hour)
# ============================================================
# ALL SUPPORTED CURRENCIES WITH NAMES
# ============================================================
CURRENCY_NAMES = {
"USD": "US Dollar",
"EUR": "Euro",
"GBP": "British Pound",
"INR": "Indian Rupee",
"JPY": "Japanese Yen",
"AUD": "Australian Dollar",
"CAD": "Canadian Dollar",
"CHF": "Swiss Franc",
"CNY": "Chinese Yuan",
"SGD": "Singapore Dollar",
"AED": "UAE Dirham",
"SAR": "Saudi Riyal",
"MYR": "Malaysian Ringgit",
"HKD": "Hong Kong Dollar",
"NZD": "New Zealand Dollar",
"SEK": "Swedish Krona",
"NOK": "Norwegian Krone",
"DKK": "Danish Krone",
"ZAR": "South African Rand",
"MXN": "Mexican Peso",
"BRL": "Brazilian Real",
"RUB": "Russian Ruble",
"KRW": "South Korean Won",
"TRY": "Turkish Lira",
"IDR": "Indonesian Rupiah",
"THB": "Thai Baht",
"PKR": "Pakistani Rupee",
"BDT": "Bangladeshi Taka",
"LKR": "Sri Lankan Rupee",
"NPR": "Nepalese Rupee",
"PHP": "Philippine Peso",
"VND": "Vietnamese Dong",
"EGP": "Egyptian Pound",
"NGN": "Nigerian Naira",
"KWD": "Kuwaiti Dinar",
"BHD": "Bahraini Dinar",
"OMR": "Omani Rial",
"QAR": "Qatari Riyal",
"ILS": "Israeli Shekel",
"PLN": "Polish Zloty",
"CZK": "Czech Koruna",
"HUF": "Hungarian Forint",
"RON": "Romanian Leu",
"HRK": "Croatian Kuna",
"BGN": "Bulgarian Lev",
"ISK": "Icelandic Krona",
"ARS": "Argentine Peso",
"CLP": "Chilean Peso",
"COP": "Colombian Peso",
"PEN": "Peruvian Sol",
"TWD": "Taiwan Dollar",
}
# ============================================================
# CACHE MANAGEMENT
# ============================================================
def load_cache():
if Path(CACHE_FILE).exists():
try:
with open(CACHE_FILE, "r") as f:
return json.load(f)
except:
pass
return {}
def save_cache(data):
with open(CACHE_FILE, "w") as f:
json.dump(data, f, indent=2)
def is_cache_valid(cache, base):
if base not in cache:
return False
ts = cache[base].get("timestamp", 0)
return (time.time() - ts) < CACHE_EXPIRY
# ============================================================
# FETCH RATES FROM API
# ============================================================
def fetch_rates(base="USD"):
"""
Fetch live exchange rates for a base currency.
Returns dict of rates or None on failure.
"""
if not REQUESTS_OK:
print(" requests library not installed.")
return None
cache = load_cache()
# Return cached rates if still fresh
if is_cache_valid(cache, base):
age = int(time.time() - cache[base]["timestamp"])
print(f" Using cached rates (age: {age}s)")
return cache[base]["rates"]
# Fetch from API
print(f" Fetching live rates for {base}...")
try:
resp = requests.get(BASE_URL + base, timeout=10)
resp.raise_for_status()
data = resp.json()
rates = data.get("rates", {})
# Save to cache
cache[base] = {
"rates": rates,
"timestamp": time.time(),
"date": data.get("date", "")
}
save_cache(cache)
print(f" Live rates fetched successfully! ({len(rates)} currencies)")
return rates
except requests.exceptions.ConnectionError:
print(" No internet connection. Checking for cached rates...")
if base in cache:
print(" Using expired cache as fallback.")
return cache[base]["rates"]
return None
except Exception as e:
print(f" API error: {e}")
if base in cache:
return cache[base]["rates"]
return None
# ============================================================
# CONVERT CURRENCY
# ============================================================
def convert(amount, from_cur, to_cur, rates):
"""
Convert amount from from_cur to to_cur using rates
(rates are relative to their base currency).
"""
from_cur = from_cur.upper()
to_cur = to_cur.upper()
if from_cur not in rates:
print(f" Currency not found: {from_cur}")
return None
if to_cur not in rates:
print(f" Currency not found: {to_cur}")
return None
# Convert: amount / from_rate * to_rate
result = (amount / rates[from_cur]) * rates[to_cur]
return round(result, 4)
# ============================================================
# MULTI-CURRENCY CONVERSION (1 to Many)
# ============================================================
def convert_to_many(amount, from_cur, targets, rates):
"""Convert one amount to multiple currencies at once."""
results = {}
for to_cur in targets:
result = convert(amount, from_cur, to_cur, rates)
if result is not None:
results[to_cur] = result
return results
# ============================================================
# HISTORY MANAGEMENT
# ============================================================
def load_history():
if Path(HISTORY_FILE).exists():
try:
with open(HISTORY_FILE, "r") as f:
return json.load(f)
except:
pass
return []
def save_to_history(entry):
history = load_history()
history.append(entry)
history = history[-100:] # keep last 100
with open(HISTORY_FILE, "w") as f:
json.dump(history, f, indent=2)
def display_history():
history = load_history()
if not history:
print("\n No conversion history yet.")
return
print("\n" + "="*58)
print(f" CONVERSION HISTORY (last {len(history)} entries)")
print("="*58)
print(f" {'TIME':<20} {'FROM':<18} {'TO':<18} RATE")
print(" " + "-"*54)
for h in reversed(history[-20:]):
from_str = f"{h['amount']} {h['from']}"
to_str = f"{h['result']} {h['to']}"
print(f" {h['time']:<20} {from_str:<18} {to_str:<18} "
f"1 {h['from']} = {h['rate']} {h['to']}")
print("="*58)
# ============================================================
# DISPLAY RATE TABLE
# ============================================================
def display_rate_table(base, rates, filter_list=None):
currencies = filter_list if filter_list else list(CURRENCY_NAMES.keys())
available = [(c, rates[c]) for c in currencies if c in rates and c != base]
print("\n" + "="*55)
print(f" EXCHANGE RATES (Base: {base} — {CURRENCY_NAMES.get(base, '')})")
print("="*55)
print(f" {'CODE':<6} {'CURRENCY NAME':<25} {'RATE':>12}")
print(" " + "-"*44)
for code, rate in sorted(available, key=lambda x: x[0]):
name = CURRENCY_NAMES.get(code, code)
print(f" {code:<6} {name:<25} {rate:>12.4f}")
print("="*55)
print(f" Rates relative to 1 {base}")
# ============================================================
# CURRENCY SEARCH
# ============================================================
def search_currency(query):
query = query.lower()
matches = [
(code, name) for code, name in CURRENCY_NAMES.items()
if query in code.lower() or query in name.lower()
]
if not matches:
print(f"\n No currencies found for: '{query}'")
else:
print(f"\n Found {len(matches)} match(es):")
for code, name in matches:
print(f" {code:<6} {name}")
# ============================================================
# POPULAR PAIRS QUICK VIEW
# ============================================================
def popular_pairs(rates):
pairs = [
("USD", "EUR"), ("USD", "GBP"), ("USD", "INR"),
("USD", "JPY"), ("USD", "AED"), ("EUR", "GBP"),
("EUR", "INR"), ("GBP", "INR"), ("USD", "CNY"),
("USD", "AUD"), ("USD", "CAD"), ("USD", "SAR"),
]
print("\n" + "="*45)
print(" POPULAR CURRENCY PAIRS")
print("="*45)
print(f" {'PAIR':<10} {'RATE':>12} {'REVERSE':>12}")
print(" " + "-"*40)
for fr, to in pairs:
if fr in rates and to in rates:
rate = round((1 / rates[fr]) * rates[to], 4)
rev_rate = round((1 / rates[to]) * rates[fr], 4)
print(f" {fr}/{to:<7} {rate:>12.4f} {rev_rate:>12.4f}")
print("="*45)
print(" All rates vs USD base")
# ============================================================
# MAIN MENU
# ============================================================
def print_menu():
print("\n" + "-"*48)
print(" CURRENCY CONVERTER (Live Rates)")
print("-"*48)
print(" 1. Convert currency")
print(" 2. Convert to multiple currencies")
print(" 3. View exchange rate table")
print(" 4. Popular currency pairs")
print(" 5. Search currency by name/code")
print(" 6. View conversion history")
print(" 7. Refresh rates (clear cache)")
print(" 0. Exit")
print("-"*48)
def main():
print("\n" + "="*55)
print(" CURRENCY CONVERTER — Live Rates")
print("="*55)
if not REQUESTS_OK:
print("\n requests library not installed!")
print(" Install it: pip install requests")
return
# Load initial USD rates
print("\n Loading exchange rates...")
rates = fetch_rates("USD")
if not rates:
print("\n Could not load rates. Check your internet connection.")
return
# Show cache info
cache = load_cache()
if "USD" in cache:
ts = datetime.fromtimestamp(cache["USD"]["timestamp"])
print(f" Rates date: {cache['USD'].get('date','')} "
f"(cached at {ts.strftime('%H:%M:%S')})")
while True:
print_menu()
choice = input(" > ").strip()
# --------------------------------------------------
if choice == "1":
print("\n Single Currency Conversion")
print(f" (Type currency code e.g. USD, INR, EUR)")
from_cur = input(" From currency: ").strip().upper()
to_cur = input(" To currency : ").strip().upper()
if from_cur not in rates:
print(f" Unknown currency: {from_cur}. Use option 5 to search.")
continue
if to_cur not in rates:
print(f" Unknown currency: {to_cur}. Use option 5 to search.")
continue
try:
amount = float(input(" Amount : ").strip())
except ValueError:
print(" Invalid amount.")
continue
result = convert(amount, from_cur, to_cur, rates)
if result is not None:
rate_val = round((1 / rates[from_cur]) * rates[to_cur], 6)
from_name = CURRENCY_NAMES.get(from_cur, from_cur)
to_name = CURRENCY_NAMES.get(to_cur, to_cur)
print(f"\n {'='*45}")
print(f" {amount:,.2f} {from_cur} ({from_name})")
print(f" = {result:,.4f} {to_cur} ({to_name})")
print(f" Rate: 1 {from_cur} = {rate_val} {to_cur}")
print(f" {'='*45}")
save_to_history({
"time": datetime.now().strftime("%d-%m-%Y %H:%M"),
"amount": amount,
"from": from_cur,
"to": to_cur,
"result": result,
"rate": rate_val
})
# --------------------------------------------------
elif choice == "2":
print("\n Convert to Multiple Currencies")
from_cur = input(" From currency: ").strip().upper()
if from_cur not in rates:
print(f" Unknown currency: {from_cur}")
continue
try:
amount = float(input(" Amount: ").strip())
except ValueError:
print(" Invalid amount.")
continue
print(" Target currencies (space-separated, e.g. EUR GBP INR JPY):")
targets_input = input(" > ").strip().upper().split()
if not targets_input:
# Default popular targets
targets_input = ["USD", "EUR", "GBP", "INR", "JPY",
"AUD", "CAD", "AED", "SGD", "CHF"]
results = convert_to_many(amount, from_cur, targets_input, rates)
print(f"\n {amount:,.2f} {from_cur} = ")
print(" " + "-"*40)
for cur, val in results.items():
name = CURRENCY_NAMES.get(cur, cur)
print(f" {val:>14,.4f} {cur} ({name})")
# --------------------------------------------------
elif choice == "3":
base = input("\n Base currency (default USD): ").strip().upper() or "USD"
if base != "USD":
rates = fetch_rates(base)
if not rates:
print(" Could not fetch rates for that base.")
continue
display_rate_table(base, rates)
# --------------------------------------------------
elif choice == "4":
popular_pairs(rates)
# --------------------------------------------------
elif choice == "5":
query = input("\n Search (name or code): ").strip()
search_currency(query)
# --------------------------------------------------
elif choice == "6":
display_history()
# --------------------------------------------------
elif choice == "7":
if Path(CACHE_FILE).exists():
os.remove(CACHE_FILE)
print("\n Cache cleared.")
print(" Fetching fresh rates...")
rates = fetch_rates("USD")
if rates:
print(" Rates updated successfully!")
# --------------------------------------------------
elif choice == "0":
print("\n Goodbye!\n")
break
else:
print(" Invalid choice.")
# ============================================================
# RUN
# ============================================================
if __name__ == "__main__":
main()
No comments:
Post a Comment