/******************************************* * trade.js *******************************************/ // Grabbing DOM references related to placing trades const tradeAction = document.getElementById("tradeAction"); const stockSymbol = document.getElementById("stockSymbol"); const tradeQuantity = document.getElementById("tradeQuantity"); const placeTradeBtn = document.getElementById("placeTradeBtn"); const tradeHistoryBody = document.getElementById("tradeHistoryBody"); const orderType = document.getElementById("orderType"); const limitStopPriceInput = document.getElementById("limitStopPrice"); const expirationSelect = document.getElementById("expirationSelect"); // Preview elements const tradePreview = document.getElementById("tradePreview"); const previewPrice = document.getElementById("previewPrice"); const previewCost = document.getElementById("previewCost"); const previewFreeCash = document.getElementById("previewFreeCash"); const previewPortfolioPercent = document.getElementById("previewPortfolioPercent"); const previewCurrentPrice = document.getElementById("previewCurrentPrice"); // Event listener: Show/hide limit/stop price and expiration based on order type orderType.addEventListener("change", () => { if (orderType.value === "limit" || orderType.value === "stop_loss") { limitStopPriceInput.style.display = "inline-block"; expirationSelect.style.display = "inline-block"; } else { limitStopPriceInput.style.display = "none"; expirationSelect.style.display = "none"; } updateTradePreview(); }); // Place Trade button logic placeTradeBtn.addEventListener("click", async () => { if (!isLoggedIn()) { alert("Please log in first."); return; } const ordType = orderType.value; const expiration = (ordType === "limit" || ordType === "stop_loss") ? expirationSelect.value : null; const username = getCurrentUsername(); const isAdmin = (username === "Opulentissimus"); // If user is NOT admin, enforce market hours for Day orders / market orders if ((ordType === "limit" || ordType === "stop_loss") && expiration === "day" && !isAdmin) { if (!isMarketOpen()) { alert("Cannot place 'Day' orders when the market is closed. Please select 'Good til Canceled (GTC)' or wait until market is open."); return; } } if (ordType === "market" && !isAdmin) { if (!isMarketOpen()) { alert("The market is currently closed. You cannot place market trades at this time."); return; } } const user = auth.currentUser; if (!user) return; const action = tradeAction.value; const symbol = stockSymbol.value.trim().toUpperCase(); const qty = parseInt(tradeQuantity.value, 10); const thresholdPx = parseFloat(limitStopPriceInput.value); if (!symbol || !qty || qty <= 0) { alert("Please enter a valid symbol and quantity."); return; } // Validate symbol with a real-time price fetch const validPrice = await fetchStockPrice(symbol); if (!validPrice) { alert("Invalid symbol or price not found. Trade canceled."); return; } // Additional checks for SELL limit or stop orders if (action === "SELL") { if (ordType === "limit" && thresholdPx < validPrice) { alert("For SELL limit orders, the limit price must be above or equal to the current price."); return; } if (ordType === "stop_loss" && thresholdPx > validPrice) { alert("For SELL stop loss orders, the stop price must be below or equal to the current price."); return; } } if ((ordType === "limit" || ordType === "stop_loss") && (isNaN(thresholdPx) || thresholdPx <= 0)) { alert("Please enter a valid limit/stop price."); return; } const balanceKey = `pt_balance_${username}`; const portfolioKey = `pt_portfolio_${username}`; let currentBalance = parseFloat(localStorage.getItem(balanceKey) || "100000"); let portfolio = JSON.parse(localStorage.getItem(portfolioKey) || "{}"); // If limit/stop order, store in localStorage with status=OPEN if (ordType !== "market") { // Check if SELLing more shares than available (including open orders), // or if BUYing more than available cash if (action === "SELL") { let sharesOwned = portfolio[symbol] || 0; if (qty > sharesOwned) { alert(`Cannot place SELL for ${qty} shares. You only have ${sharesOwned} of ${symbol}.`); return; } // Check open SELL orders const limitOrdersKey = "pt_limit_orders"; let limitStopOrders = JSON.parse(localStorage.getItem(limitOrdersKey) || "[]"); let openSellQty = 0; for (let o of limitStopOrders) { if (o.user === username && o.symbol === symbol && o.action === "SELL" && o.status === "OPEN") { openSellQty += o.quantity; } } let available = sharesOwned - openSellQty; if (qty > available) { alert(`You already have SELL orders for ${openSellQty} shares. You own ${sharesOwned} total. Cannot place SELL for ${qty} more. (Remaining available: ${available} shares).`); return; } } else { // BUY let neededCash = thresholdPx * qty; if (neededCash > currentBalance) { alert(`Cannot BUY ${qty} shares at $${thresholdPx}. You only have $${currentBalance.toFixed(2)} cash.`); return; } // Check other open BUY orders const limitOrdersKey = "pt_limit_orders"; let limitStopOrders = JSON.parse(localStorage.getItem(limitOrdersKey) || "[]"); let openBuyCost = 0; for (let o of limitStopOrders) { if (o.user === username && o.symbol === symbol && o.action === "BUY" && o.status === "OPEN") { openBuyCost += o.limitPrice * o.quantity; } } if ((neededCash + openBuyCost) > currentBalance) { alert(`You have open BUY orders already costing $${openBuyCost.toFixed(2)}. This new order needs $${neededCash.toFixed(2)}. Total needed is $${(neededCash + openBuyCost).toFixed(2)}, but you only have $${currentBalance.toFixed(2)}.`); return; } } // Place limit/stop order const limitOrdersKey = "pt_limit_orders"; let limitStopOrders = JSON.parse(localStorage.getItem(limitOrdersKey) || "[]"); const now = new Date(); let newOrder = { user: username, orderType: ordType, symbol: symbol, action: action, quantity: qty, limitPrice: thresholdPx, expiration: expiration, currentPrice: validPrice, timestamp: now.toISOString(), status: "OPEN" }; limitStopOrders.push(newOrder); localStorage.setItem(limitOrdersKey, JSON.stringify(limitStopOrders)); alert(`${formatOrderType(ordType)} order placed! ${action} ${qty} of ${symbol} @ $${thresholdPx}`); // Re-render "Outgoing Orders" by re-loading positions loadPositions(); } else { // Market order await executeMarketOrder(username, action, symbol, qty, validPrice); // Refresh the trade history, positions, etc. loadTradeHistory(); loadPositions(); updateProfileUI(); } // Clear fields stockSymbol.value = ""; tradeQuantity.value = ""; limitStopPriceInput.value = ""; expirationSelect.value = "day"; updateTradePreview(); }); // Actually perform a Market Order window.executeMarketOrder = async function(username, action, symbol, qty, validPrice) { const balanceKey = `pt_balance_${username}`; const portfolioKey = `pt_portfolio_${username}`; let currentBalance = parseFloat(localStorage.getItem(balanceKey) || "100000"); let portfolio = JSON.parse(localStorage.getItem(portfolioKey) || "{}"); let trades = JSON.parse(localStorage.getItem("pt_trades") || "[]"); const tradeCost = validPrice * qty; if (action === "SELL") { const sharesOwned = portfolio[symbol] || 0; if (qty > sharesOwned) { alert(`Cannot sell ${qty} shares; you own only ${sharesOwned}.`); return; } currentBalance += tradeCost; portfolio[symbol] = sharesOwned - qty; if (portfolio[symbol] <= 0) { delete portfolio[symbol]; } } else { // BUY if (tradeCost > currentBalance) { alert(`Insufficient cash. Need $${tradeCost.toFixed(2)} but have $${currentBalance.toFixed(2)}.`); return; } currentBalance -= tradeCost; portfolio[symbol] = (portfolio[symbol] || 0) + qty; } localStorage.setItem(balanceKey, currentBalance.toFixed(2)); localStorage.setItem(portfolioKey, JSON.stringify(portfolio)); const now = new Date(); trades.push({ user: username, symbol: symbol, action: action, quantity: qty, price: validPrice.toFixed(2), total: tradeCost.toFixed(2), timestamp: now.toISOString() }); localStorage.setItem("pt_trades", JSON.stringify(trades)); alert(`Market order executed: ${action} ${qty} of ${symbol} @ $${validPrice.toFixed(2)}`); }; // Load the user's trade history window.loadTradeHistory = function() { const user = auth.currentUser; if (!user) return; const username = user.displayName || user.email; const trades = JSON.parse(localStorage.getItem("pt_trades") || "[]"); const userTrades = trades.filter(t => t.user === username); tradeHistoryBody.innerHTML = ""; userTrades.forEach(t => { const row = document.createElement("tr"); row.innerHTML = `