Currency Converter with Live Rates

 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: