stock market simulation




<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Virtual Stock Simulator - Live P&L</title>
  <style>
    :root {
      --bg: #0f172a;
      --card: #1e293b;
      --text: #f8fafc;
      --green: #10b981;
      --red: #ef4444;
      --blue: #3b82f6;
    }
    body {
      background-color: var(--bg);
      color: var(--text);
      font-family: system-ui, -apple-system, sans-serif;
      margin: 0;
      padding: 20px;
      display: flex;
      justify-content: center;
    }
    .app-container {
      width: 100%;
      max-width: 650px;
    }
    header {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 15px;
      border-bottom: 2px solid #334155;
      padding-bottom: 15px;
      margin-bottom: 25px;
    }
    h1 { margin: 0; font-size: 26px; grid-column: span 2; }
    
    .financial-card {
      padding: 12px 20px;
      border-radius: 8px;
    }
    .balance-box {
      background: linear-gradient(135deg, #1e3a8a, var(--blue));
    }
    .pnl-box {
      background-color: var(--card);
      border: 1px solid #334155;
    }
    .financial-card p { margin: 0; font-size: 11px; opacity: 0.8; text-transform: uppercase; letter-spacing: 0.5px; }
    .financial-card h2 { margin: 5px 0 0 0; font-size: 20px; }

    /* Search Component */
    .search-box {
      display: flex;
      gap: 10px;
      margin-bottom: 20px;
    }
    input[type="text"] {
      flex: 1;
      padding: 12px;
      background: var(--card);
      border: 1px solid #475569;
      color: white;
      border-radius: 6px;
      font-size: 16px;
    }
    button {
      padding: 12px 20px;
      border: none;
      border-radius: 6px;
      font-weight: bold;
      cursor: pointer;
      font-size: 14px;
    }
    button:hover { opacity: 0.9; }
    .btn-search { background-color: var(--blue); color: white; }
    .btn-buy { background-color: var(--green); color: white; }
    .btn-sell { background-color: var(--red); color: white; }

    /* Quote Display Card */
    .stock-card {
      background-color: var(--card);
      padding: 20px;
      border-radius: 8px;
      margin-bottom: 30px;
      display: none; 
    }
    .stock-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 15px;
    }
    .stock-header h3 { margin: 0; font-size: 24px; }
    .price-tag { font-size: 24px; font-weight: bold; color: var(--green); }
    .trade-controls {
      display: flex;
      align-items: center;
      gap: 10px;
      border-top: 1px solid #334155;
      padding-top: 15px;
    }
    input[type="number"] {
      width: 80px;
      padding: 10px;
      background: var(--bg);
      border: 1px solid #475569;
      color: white;
      border-radius: 6px;
    }

    /* Portfolio Layout */
    .portfolio-section h3 {
      border-bottom: 2px solid #334155;
      padding-bottom: 8px;
      margin-bottom: 15px;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .refresh-badge {
      font-size: 11px;
      background-color: #334155;
      padding: 4px 8px;
      border-radius: 20px;
      color: #94a3b8;
    }
    .portfolio-item {
      background-color: var(--card);
      padding: 15px;
      border-radius: 6px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 10px;
      border-left: 4px solid #475569;
      transition: border-color 0.3s;
    }
    .portfolio-item.profit { border-left-color: var(--green); }
    .portfolio-item.loss { border-left-color: var(--red); }
    
    .portfolio-item h4 { margin: 0 0 5px 0; font-size: 18px; }
    .portfolio-item p { margin: 0; font-size: 13px; color: #94a3b8; }
    
    .profit-text { color: var(--green) !important; font-weight: bold; }
    .loss-text { color: var(--red) !important; font-weight: bold; }
    
    .empty-state { text-align: center; color: #64748b; font-style: italic; margin-top: 20px; }
    .loading { color: #94a3b8; font-style: italic; display: none; margin-bottom: 15px; }
  </style>
</head>
<body>

  <div class="app-container">
    <header>
      <h1>PaperTrade Live Dashboard</h1>
      <div class="financial-card balance-box">
        <p>Cash Balance</p>
        <h2 id="balance-display">500,000.00 Coins</h2>
      </div>
      <div class="financial-card pnl-box">
        <p>Total Live P&L</p>
        <h2 id="total-pnl-display">0.00 Coins</h2>
      </div>
    </header>

    <div class="search-box">
      <input type="text" id="search-input" placeholder="Ticker (e.g., AAPL, TSLA, BTC-USD)">
      <button class="btn-search" onclick="searchStock()">Search</button>
    </div>

    <div class="loading" id="loading-text">Fetching live market streaming feeds...</div>

    <div class="stock-card" id="stock-display-card">
      <div class="stock-header">
        <div>
          <h3 id="display-ticker">AAPL</h3>
          <span style="font-size:12px; color:#94a3b8;">Real-Time Yahoo Price Execution</span>
        </div>
        <div class="price-tag" id="display-price">0.00 Coins</div>
      </div>
      
      <div class="trade-controls">
        <label for="trade-qty">Qty:</label>
        <input type="number" id="trade-qty" min="1" value="1">
        <button class="btn-buy" onclick="executeBuy()">Buy</button>
        <button class="btn-sell" onclick="executeSell()">Sell</button>
      </div>
    </div>

    <div class="portfolio-section">
      <h3>
        <span>Your Holdings / Portfolio</span>
        <span class="refresh-badge" id="refresh-status">Live Tracking Active</span>
      </h3>
      <div id="portfolio-list">
        <p class="empty-state">Your portfolio is currently empty.</p>
      </div>
    </div>
  </div>

  <script>
    // --- Application State ---
    let walletBalance = 500000; 
    let portfolio = {}; // Format: { "AAPL": { qty: 5, avgPrice: 150, currentPrice: 150 } }
    let activeStock = { ticker: "", price: 0 };

    // --- Core Data Engines ---

    // 1. Core Fetch utility targeting hidden Yahoo server endpoints
  async function fetchYahooPrice(ticker) {
      // 1. We use the standard format for allorigins
      const proxyUrl = "https://api.allorigins.win/get?url=";
      const yahooUrl = `https://query1.finance.yahoo.com/v8/finance/chart/${ticker}`;
      
      try {
        // 2. encodeURIComponent cleans up the characters so the proxy understands it cleanly
        const response = await fetch(proxyUrl + encodeURIComponent(yahooUrl));
        if (!response.ok) throw new Error("Network query failed");
        
        const data = await response.json();
        
        // 3. Since we used the /get endpoint, allorigins wraps the answer inside data.contents
        const yahooData = JSON.parse(data.contents);
        
        return yahooData.chart.result[0].meta.regularMarketPrice;
      } catch (err) {
        console.warn(`Fetch err for ${ticker}:`, err);
        return null;
      }
    }

    // 2. Search Handler Function
    async function searchStock() {
      const input = document.getElementById("search-input").value.trim().toUpperCase();
      if (!input) return alert("Please enter a valid stock ticker symbol!");

      const loader = document.getElementById("loading-text");
      const card = document.getElementById("stock-display-card");
      
      loader.style.display = "block";
      card.style.display = "none";

      const price = await fetchYahooPrice(input);
      loader.style.display = "none";

      if (price === null) {
        alert("Verification Failure: Unable to fetch price data. Make sure your ticker is valid and your demo access is turned on at: https://cors-anywhere.herokuapp.com/corsdemo");
        return;
      }

      activeStock.ticker = input;
      activeStock.price = price;

      document.getElementById("display-ticker").innerText = activeStock.ticker;
      document.getElementById("display-price").innerText = `${activeStock.price.toLocaleString(undefined, {minimumFractionDigits: 2})} Coins`;
      card.style.display = "block";
    }

    // 3. Trade Routing Execution: BUY
    function executeBuy() {
      const qty = parseInt(document.getElementById("trade-qty").value);
      if (isNaN(qty) || qty <= 0) return alert("Please provide a real share quantity target.");

      const totalCost = activeStock.price * qty;
      if (totalCost > walletBalance) return alert("Transaction Aborted: Insufficient virtual cash pools!");

      walletBalance -= totalCost;

      if (portfolio[activeStock.ticker]) {
        const existingQty = portfolio[activeStock.ticker].qty;
        const currentTotalInvested = (existingQty * portfolio[activeStock.ticker].avgPrice) + totalCost;
        portfolio[activeStock.ticker].qty += qty;
        portfolio[activeStock.ticker].avgPrice = currentTotalInvested / portfolio[activeStock.ticker].qty;
        portfolio[activeStock.ticker].currentPrice = activeStock.price; // match initial tracking price
      } else {
        portfolio[activeStock.ticker] = {
          qty: qty,
          avgPrice: activeStock.price,
          currentPrice: activeStock.price
        };
      }

      updateDashboard();
      alert(`Asset Acquired: Purchased ${qty} shares of ${activeStock.ticker}!`);
    }

    // 4. Trade Routing Execution: SELL
    function executeSell() {
      const qty = parseInt(document.getElementById("trade-qty").value);
      if (isNaN(qty) || qty <= 0) return alert("Please provide a real share quantity target.");

      const ticker = activeStock.ticker;
      if (!portfolio[ticker] || portfolio[ticker].qty < qty) {
        return alert(`Transaction Aborted: Short position limits exceeded for ${ticker}!`);
      }

      const revenue = activeStock.price * qty;
      walletBalance += revenue;
      portfolio[ticker].qty -= qty;

      if (portfolio[ticker].qty === 0) {
        delete portfolio[ticker];
      }

      updateDashboard();
      alert(`Asset Liquidated: Successfully sold ${qty} shares of ${ticker}!`);
    }

    // 5. Interface Synchronization and P&L Math Computation Engine
    function updateDashboard() {
      // Balance Rendering
      document.getElementById("balance-display").innerText = `${walletBalance.toLocaleString(undefined, {minimumFractionDigits: 2})} Coins`;

      const container = document.getElementById("portfolio-list");
      container.innerHTML = "";

      const assets = Object.keys(portfolio);
      if (assets.length === 0) {
        container.innerHTML = `<p class="empty-state">Your portfolio is currently empty.</p>`;
        document.getElementById("total-pnl-display").innerText = "0.00 Coins";
        document.getElementById("total-pnl-display").className = "";
        return;
      }

      let globalPortfolioPnL = 0;

      assets.forEach(symbol => {
        const holding = portfolio[symbol];
        
        // Math Processing Metrics
        const totalCostBasis = holding.qty * holding.avgPrice;
        const currentMarketValue = holding.qty * holding.currentPrice;
        const individualStockPnL = currentMarketValue - totalCostBasis;
        
        globalPortfolioPnL += individualStockPnL;

        // Visual Layout Styling Modifiers based on profit margins
        let pnlClass = "";
        let rowIndicatorStyle = "";
        if (individualStockPnL > 0) {
          pnlClass = "profit-text";
          rowIndicatorStyle = "profit";
        } else if (individualStockPnL < 0) {
          pnlClass = "loss-text";
          rowIndicatorStyle = "loss";
        }

        const card = document.createElement("div");
        card.className = `portfolio-item ${rowIndicatorStyle}`;
        card.innerHTML = `
          <div>
            <h4>${symbol}</h4>
            <p>Units: <strong>${holding.qty}</strong> | Avg Cost: ${holding.avgPrice.toFixed(2)}</p>
            <p>Live Price: <span style="color:white; font-weight:500;">${holding.currentPrice.toFixed(2)}</span></p>
          </div>
          <div style="text-align: right;">
            <p class="${pnlClass}" style="font-size:16px;">
              ${individualStockPnL >= 0 ? '+' : ''}${individualStockPnL.toLocaleString(undefined, {minimumFractionDigits: 2})}
            </p>
            <p style="font-size: 11px;">Equity Value: ${currentMarketValue.toLocaleString(undefined, {minimumFractionDigits: 2})}</p>
          </div>
        `;
        container.appendChild(card);
      });

      // Update Top Master Total P&L Card Summary
      const masterPnLDisplay = document.getElementById("total-pnl-display");
      masterPnLDisplay.innerText = `${globalPortfolioPnL >= 0 ? '+' : ''}${globalPortfolioPnL.toLocaleString(undefined, {minimumFractionDigits: 2})} Coins`;
      
      if (globalPortfolioPnL > 0) {
        masterPnLDisplay.className = "profit-text";
      } else if (globalPortfolioPnL < 0) {
        masterPnLDisplay.className = "loss-text";
      } else {
        masterPnLDisplay.className = "";
      }
    }

    // --- Background Real-Time Synchronization Engine ---
    // Runs an asynchronous pricing update loop across all active assets every 4 seconds
    setInterval(async function() {
      const assetsToUpdate = Object.keys(portfolio);
      
      if (assetsToUpdate.length === 0 && !activeStock.ticker) return;
      
      const badge = document.getElementById("refresh-status");
      badge.innerText = "Syncing Feeds...";
      badge.style.backgroundColor = "#1e3a8a";

      // 1. Update the portfolio assets prices
      for (const symbol of assetsToUpdate) {
        const freshPrice = await fetchYahooPrice(symbol);
        if (freshPrice !== null) {
          portfolio[symbol].currentPrice = freshPrice;
        }
      }

      // 2. Also keep the active searched stock matching the background updates
      if (activeStock.ticker) {
        const freshActivePrice = await fetchYahooPrice(activeStock.ticker);
        if (freshActivePrice !== null) {
          activeStock.price = freshActivePrice;
          document.getElementById("display-price").innerText = `${activeStock.price.toLocaleString(undefined, {minimumFractionDigits: 2})} Coins`;
        }
      }

      // 3. Re-render display states
      updateDashboard();
      
      badge.innerText = "Live Tracking Active";
      badge.style.backgroundColor = "#334155";
    }, 9000); // 4-second interval loop balances live speed with server limits
  </script>
</body>
</html>

Dhaval Visariya

AMFI Registered Distributor (ARN-327925) | Software Developer & Finance Enthusiast

Connect with me:
LinkedIn | Telegram | YouTube | GitHub | Medium

Disclaimer: I am an AMFI Registered Mutual Fund Distributor. Views expressed here are for educational purposes. Mutual Fund investments are subject to market risks, read all scheme related documents carefully.
📧 Contact: info.apollofinancialservices@gmail.com

Comments

Popular posts from this blog

Nifty 30,000: The JP Morgan Blueprint for the Indo-US "Trade Bull Run" 🚀🇮🇳🇺🇸

NISM Series XV Research Analyst: Complete Study Material & Notes by Dhaval Visariya

NISM 15 Research Analyst Mock Test 1: 600 MCQ Series (2026 Updated)