Cryptocurrency Portfolio Tracker Python, JavaScript, API

👤 Sharing: AI
```python
# Python (Backend - Flask API)
from flask import Flask, jsonify, request
import requests
import json

app = Flask(__name__)

# Replace with your actual API key (CoinGecko, CoinMarketCap, etc.)
API_KEY = "YOUR_API_KEY_HERE"  #  A lot of free options require no API key, but some do.


# In-memory portfolio (For simplicity - use a database in a real application)
portfolio = {}


@app.route('/portfolio', methods=['GET'])
def get_portfolio():
    """
    Returns the user's cryptocurrency portfolio.
    """
    return jsonify(portfolio)


@app.route('/portfolio', methods=['POST'])
def add_to_portfolio():
    """
    Adds a cryptocurrency to the user's portfolio.
    Expects JSON in the following format:
    {
        "symbol": "BTC",
        "amount": 0.5
    }
    """
    data = request.get_json()

    if not data or 'symbol' not in data or 'amount' not in data:
        return jsonify({'error': 'Invalid request.  Must include "symbol" and "amount".'}), 400

    symbol = data['symbol'].upper()  # Ensure uppercase
    amount = float(data['amount'])

    if symbol in portfolio:
        portfolio[symbol] += amount
    else:
        portfolio[symbol] = amount

    return jsonify({'message': f'Added {amount} {symbol} to portfolio.'}), 201  # 201 Created


@app.route('/portfolio/<symbol>', methods=['DELETE'])
def remove_from_portfolio(symbol):
    """
    Removes a cryptocurrency from the user's portfolio.
    """
    symbol = symbol.upper()
    if symbol in portfolio:
        del portfolio[symbol]
        return jsonify({'message': f'Removed {symbol} from portfolio.'})
    else:
        return jsonify({'error': f'{symbol} not found in portfolio.'}), 404  # 404 Not Found


@app.route('/price/<symbol>', methods=['GET'])
def get_crypto_price(symbol):
    """
    Fetches the current price of a cryptocurrency using the CoinGecko API.
    """
    symbol = symbol.lower()  # CoinGecko requires lowercase symbols

    # CoinGecko API endpoint - a good free option
    url = f'https://api.coingecko.com/api/v3/simple/price?ids={symbol}&vs_currencies=usd'

    try:
        response = requests.get(url)
        response.raise_for_status()  # Raise HTTPError for bad responses (4xx or 5xx)
        data = response.json()

        if symbol in data and 'usd' in data[symbol]:
            price = data[symbol]['usd']
            return jsonify({'symbol': symbol.upper(), 'price': price})
        else:
            return jsonify({'error': f'Could not retrieve price for {symbol.upper()}.'}), 404

    except requests.exceptions.RequestException as e:
        print(f"Error fetching price: {e}")
        return jsonify({'error': f'Error fetching price for {symbol.upper()}.'}), 500


@app.route('/portfolio/value', methods=['GET'])
def get_portfolio_value():
    """
    Calculates the total value of the portfolio in USD.
    """
    total_value = 0
    for symbol, amount in portfolio.items():
        # Get the price for each cryptocurrency
        response = requests.get(f'/price/{symbol}')  # Use the internal API endpoint
        if response.status_code == 200:
            price_data = response.json()
            price = price_data['price']
            total_value += amount * price
        else:
            return jsonify({'error': f'Could not calculate portfolio value.  Error fetching price for {symbol}.'}), 500
    return jsonify({'total_value': total_value})

if __name__ == '__main__':
    app.run(debug=True)
```

```javascript
// JavaScript (Frontend - Example using Fetch API)

// Assuming you have an HTML file with elements like:
// <input type="text" id="symbolInput">
// <input type="number" id="amountInput">
// <button id="addCoinButton">Add Coin</button>
// <ul id="portfolioList"></ul>
// <div id="totalValue"></div>

const apiUrl = 'http://127.0.0.1:5000'; //  Flask app's address

document.addEventListener('DOMContentLoaded', () => {
    const symbolInput = document.getElementById('symbolInput');
    const amountInput = document.getElementById('amountInput');
    const addCoinButton = document.getElementById('addCoinButton');
    const portfolioList = document.getElementById('portfolioList');
    const totalValueDiv = document.getElementById('totalValue');

    // Function to fetch and display the portfolio
    const displayPortfolio = async () => {
        try {
            const response = await fetch(`${apiUrl}/portfolio`);
            const portfolio = await response.json();

            portfolioList.innerHTML = ''; // Clear the list

            for (const symbol in portfolio) {
                const amount = portfolio[symbol];
                const listItem = document.createElement('li');
                listItem.textContent = `${symbol}: ${amount}`;
                portfolioList.appendChild(listItem);
            }

        } catch (error) {
            console.error('Error fetching portfolio:', error);
            alert('Failed to fetch portfolio.');
        }
    };

    // Function to fetch and display total portfolio value
    const displayTotalValue = async () => {
        try {
            const response = await fetch(`${apiUrl}/portfolio/value`);
            const data = await response.json();
            const totalValue = data.total_value;
            totalValueDiv.textContent = `Total Portfolio Value: $${totalValue.toFixed(2)}`; // Format to 2 decimal places

        } catch (error) {
            console.error('Error fetching total value:', error);
            alert('Failed to fetch total portfolio value.');
        }
    };

    // Function to add a coin to the portfolio
    const addCoin = async () => {
        const symbol = symbolInput.value.trim();
        const amount = parseFloat(amountInput.value);  // Parse to a number

        if (!symbol || isNaN(amount) || amount <= 0) {
            alert('Please enter a valid symbol and amount.');
            return;
        }

        try {
            const response = await fetch(`${apiUrl}/portfolio`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ symbol: symbol, amount: amount }),
            });

            if (response.ok) {
                symbolInput.value = '';
                amountInput.value = '';
                displayPortfolio(); // Refresh the portfolio display
                displayTotalValue(); // Refresh total value
            } else {
                const errorData = await response.json(); // Try to get error message from server
                alert(`Failed to add coin: ${errorData.error || 'Unknown error'}`);  // Use the error message, or a generic one
            }
        } catch (error) {
            console.error('Error adding coin:', error);
            alert('Failed to add coin.');
        }
    };

    // Event listener for the "Add Coin" button
    addCoinButton.addEventListener('click', addCoin);

    // Initial display of the portfolio
    displayPortfolio();
    displayTotalValue();
});
```

**HTML (Example `index.html`)**

```html
<!DOCTYPE html>
<html>
<head>
    <title>Cryptocurrency Portfolio Tracker</title>
</head>
<body>
    <h1>Cryptocurrency Portfolio Tracker</h1>

    <div>
        <label for="symbolInput">Coin Symbol:</label>
        <input type="text" id="symbolInput" placeholder="e.g., BTC">
    </div>

    <div>
        <label for="amountInput">Amount:</label>
        <input type="number" id="amountInput" placeholder="e.g., 0.5">
    </div>

    <button id="addCoinButton">Add Coin</button>

    <h2>Portfolio</h2>
    <ul id="portfolioList"></ul>

    <div id="totalValue"></div>

    <script src="script.js"></script>  <!-- Make sure this points to your JavaScript file -->
</body>
</html>
```

**Explanation:**

1.  **Python (Flask API - Backend):**

    *   **Flask Setup:** Initializes a Flask web application.
    *   **API Key:**  The `API_KEY` variable should be replaced with your actual API key if you are using a service that requires one (many free options do not).  Using a service that provides real-time pricing is important for any tracking application.
    *   **In-Memory Portfolio:**  The `portfolio` dictionary stores the user's holdings.  This is a very basic example, and in a real application, you would use a database (like PostgreSQL, MySQL, or MongoDB) to persist the portfolio data.
    *   **API Endpoints:**
        *   `/portfolio (GET)`: Returns the current portfolio.
        *   `/portfolio (POST)`: Adds a cryptocurrency and amount to the portfolio. It expects JSON data in the request body.
        *   `/portfolio/<symbol> (DELETE)`: Removes a cryptocurrency from the portfolio.
        *   `/price/<symbol> (GET)`: Fetches the current price of a cryptocurrency from CoinGecko (a free API).  It handles potential errors from the API request.  It's crucial to handle errors gracefully in a production environment.
        *   `/portfolio/value (GET)`: Calculates the total portfolio value by iterating through the portfolio, fetching the price of each coin, and summing the value.
    *   **Error Handling:** Includes basic error handling (e.g., checking for valid input, handling API request errors).  More robust error handling and logging would be necessary for a production application.
    *   **CORS:** In a real application, if your frontend and backend are served from different origins, you'll need to configure CORS (Cross-Origin Resource Sharing) to allow the frontend to make requests to the backend. You can use the `flask_cors` extension.

2.  **JavaScript (Frontend):**

    *   **DOM Manipulation:**  Uses JavaScript to interact with HTML elements (input fields, buttons, a list for displaying the portfolio, and a div for displaying the total value).
    *   **Fetch API:**  Uses the `fetch` API to make requests to the Flask backend.
    *   **Asynchronous Operations:** Uses `async/await` to handle asynchronous operations (fetching data from the API).
    *   **`displayPortfolio()` Function:** Fetches the portfolio data from the `/portfolio` endpoint and updates the HTML list to display the holdings.
    *   **`displayTotalValue()` Function:** Fetches the total portfolio value from the `/portfolio/value` endpoint and updates the HTML to display the value.
    *   **`addCoin()` Function:**
        *   Gets the coin symbol and amount from the input fields.
        *   Sends a POST request to the `/portfolio` endpoint with the data.
        *   Handles the response from the backend (success or error).
        *   Refreshes the portfolio and total value displays.
    *   **Event Listener:**  Attaches a click event listener to the "Add Coin" button to call the `addCoin()` function.
    *   **Error Handling:** Includes basic error handling (e.g., checking for empty input, handling fetch errors).  More comprehensive error handling would be needed in a production app.

3.  **HTML:**

    *   Provides a basic user interface with input fields for the coin symbol and amount, a button to add coins, a list to display the portfolio, and a div to display the total value.

**How to Run:**

1.  **Install Dependencies (Backend):**

    ```bash
    pip install flask requests
    ```

2.  **Save the Python code** as `app.py` (or similar).  Replace `YOUR_API_KEY_HERE` with a valid API key if needed.
3.  **Save the JavaScript code** as `script.js` and the HTML code as `index.html` in the same directory.
4.  **Run the Flask app:**

    ```bash
    python app.py
    ```

    This will start the Flask development server (usually on `http://127.0.0.1:5000`).

5.  **Open `index.html`** in your web browser.

**Key Improvements and Considerations for a Production Application:**

*   **Database:** Replace the in-memory `portfolio` with a proper database (e.g., PostgreSQL, MySQL, MongoDB) for persistent storage.  Use an ORM like SQLAlchemy or an ODM like MongoEngine to interact with the database.
*   **User Authentication:** Implement user authentication (e.g., using Flask-Login) so that each user has their own portfolio.
*   **API Key Security:**  Never hardcode API keys in your code.  Use environment variables or a configuration file to store sensitive information.  Consider using a secrets management service (like AWS Secrets Manager or HashiCorp Vault).
*   **Rate Limiting:** Be aware of API rate limits and implement strategies to handle them (e.g., using a retry mechanism with exponential backoff).
*   **Error Handling:** Implement comprehensive error handling and logging.  Use a logging library (like Python's `logging` module) to record errors and other important events.
*   **Input Validation:**  Thoroughly validate user input to prevent security vulnerabilities (e.g., SQL injection, cross-site scripting).  Use a library like `marshmallow` for serialization and validation.
*   **CORS:** Configure CORS properly if your frontend and backend are served from different origins.
*   **Testing:** Write unit tests and integration tests to ensure the reliability of your code.
*   **Deployment:**  Use a production-ready web server (like Gunicorn or uWSGI) to deploy your Flask application.  Consider using a containerization technology like Docker for easier deployment.
*   **Frontend Framework/Library:** Consider using a more robust frontend framework or library like React, Angular, or Vue.js for a more complex and maintainable user interface.
*   **Real-time Updates:** For a more dynamic experience, consider using WebSockets to provide real-time updates to the portfolio value.  Libraries like Socket.IO can help with this.
*   **Data Visualization:** Add charts and graphs to visualize the portfolio performance.  Libraries like Chart.js or D3.js can be used for this.
*   **API Documentation:** Use a tool like Swagger to generate API documentation automatically.
*   **Security Best Practices:** Follow security best practices throughout the development process to protect your application from vulnerabilities.

This enhanced explanation and the complete code example provide a solid foundation for building a more sophisticated cryptocurrency portfolio tracker.  Remember to prioritize security, scalability, and maintainability as you develop your application.
👁️ Viewed: 10

Comments