/******************************* positions.js *******************************/ // DOM references for positions const positionsTableBody = document.getElementById("positionsTableBody"); const positionsBalanceEl = document.getElementById("positionsBalance"); const positionsValueEl = document.getElementById("positionsValue"); const outgoingOrdersBody = document.getElementById("outgoingOrdersBody"); // For the pie chart let pieChart = null; // A function to fetch stock price from Alpha Vantage async function fetchStockPrice(symbol) { const alphaVantageApiKey = "WC2FR71J0JHE3JP5"; symbol = symbol.toUpperCase().replace(/^NASDAQ:/, ""); const url = `https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${symbol}&apikey=${alphaVantageApiKey}`; try { const resp = await fetch(url); if (!resp.ok) { throw new Error("Alpha Vantage error: " + resp.status); } const data = await resp.json(); if (!data["Global Quote"] || !data["Global Quote"]["05. price"]) { return null; } const priceStr = data["Global Quote"]["05. price"]; const price = parseFloat(priceStr); if (isNaN(price)) return null; return price; } catch (err) { console.error("fetchStockPrice error:", err); return null; } } // Also used by watchlist, so keep it in a global scope if needed window.fetchOverview = async function(symbol) { const alphaVantageApiKey = "WC2FR71J0JHE3JP5"; symbol = symbol.toUpperCase(); const url = `https://www.alphavantage.co/query?function=OVERVIEW&symbol=${symbol}&apikey=${alphaVantageApiKey}`; try { const resp = await fetch(url); if (!resp.ok) { throw new Error("Alpha Vantage error: " + resp.status); } const data = await resp.json(); if (!data.Symbol) { return null; } return data; } catch (err) { console.error("fetchOverview error:", err); return null; } }; function formatOrderType(ordType) { switch(ordType) { case "market": return "Market"; case "limit": return "Limit"; case "stop_loss": return "Stop Loss"; default: return ordType; } } // Check open orders async function checkOpenOrders(username) { const limitOrdersKey = "pt_limit_orders"; let allOrders = JSON.parse(localStorage.getItem(limitOrdersKey) || "[]"); let userOrders = allOrders.filter(o => o.user === username && o.status === "OPEN"); if (userOrders.length === 0) return; const portfolioKey = `pt_portfolio_${username}`; const balanceKey = `pt_balance_${username}`; let portfolio = JSON.parse(localStorage.getItem(portfolioKey) || "{}"); let currentBalance = parseFloat(localStorage.getItem(balanceKey) || "100000"); let trades = JSON.parse(localStorage.getItem("pt_trades") || "[]"); let changed = false; const now = new Date(); for (let order of userOrders) { const orderTime = new Date(order.timestamp); const timeDifference = (now - orderTime) / 1000; // Wait at least 60s or so before checking if (timeDifference < 60) { continue; } let currentPrice = await fetchStockPrice(order.symbol); if (!currentPrice || isNaN(currentPrice)) continue; let fill_condition = false; if (order.orderType === "limit") { if (order.action === "BUY" && currentPrice <= order.limitPrice) { fill_condition = true; } else if (order.action === "SELL" && currentPrice >= order.limitPrice) { fill_condition = true; } } else if (order.orderType === "stop_loss") { if (order.action === "BUY" && currentPrice >= order.limitPrice) { fill_condition = true; } else if (order.action === "SELL" && currentPrice <= order.limitPrice) { fill_condition = true; } } // Handle day vs GTC expiration function isMarketOpen() { const easternTime = new Date(new Date().toLocaleString("en-US", { timeZone: "America/New_York" })); const day = easternTime.getDay(); const hours = easternTime.getHours(); const minutes = easternTime.getMinutes(); if (day === 0 || day === 6) return false; if (hours < 9 || (hours === 9 && minutes < 30)) return false; if (hours > 16 || (hours === 16 && minutes > 0)) return false; return true; } if (order.orderType === "limit" || order.orderType === "stop_loss") { if (order.expiration === "day") { if (!isMarketOpen()) { order.status = "CANCELLED"; changed = true; continue; } } } if (fill_condition) { let totalCost = currentPrice * order.quantity; if (order.action === "SELL") { let sharesOwned = portfolio[order.symbol] || 0; if (order.quantity > sharesOwned) { continue; } currentBalance += totalCost; portfolio[order.symbol] = sharesOwned - order.quantity; if (portfolio[order.symbol] <= 0) { delete portfolio[order.symbol]; } } else { if (totalCost > currentBalance) { continue; } currentBalance -= totalCost; portfolio[order.symbol] = (portfolio[order.symbol] || 0) + order.quantity; } let nowFormatted = new Date().toLocaleString(); trades.push({ user: order.user, symbol: order.symbol, action: order.action, quantity: order.quantity, price: currentPrice.toFixed(2), total: totalCost.toFixed(2), timestamp: nowFormatted }); order.status = "FILLED"; changed = true; } } if (changed) { localStorage.setItem(portfolioKey, JSON.stringify(portfolio)); localStorage.setItem(balanceKey, currentBalance.toFixed(2)); localStorage.setItem("pt_trades", JSON.stringify(trades)); localStorage.setItem(limitOrdersKey, JSON.stringify(allOrders)); alert("Some of your limit/stop loss orders have been processed."); loadPositions(); if (window.updateProfileUI) { window.updateProfileUI(); } } } // Cancel an order function cancelOrder(timestamp) { if (!confirm("Are you sure you want to cancel this trade?")) { return; } const user = window.auth.currentUser; if (!user) return; const username = user.displayName || user.email; const limitOrdersKey = "pt_limit_orders"; let allOrders = JSON.parse(localStorage.getItem(limitOrdersKey) || "[]"); let orderIndex = allOrders.findIndex(o => o.user === username && o.timestamp === timestamp && o.status === "OPEN"); if (orderIndex === -1) { alert("Order not found or already processed."); return; } allOrders[orderIndex].status = "CANCELLED"; localStorage.setItem(limitOrdersKey, JSON.stringify(allOrders)); alert("Order has been cancelled."); loadPositions(); } // Listener for the cancel buttons outgoingOrdersBody.addEventListener("click", (e) => { if (e.target.classList.contains("cancel-order-btn")) { const timestamp = e.target.getAttribute("data-timestamp"); cancelOrder(timestamp); } }); // The main function to load positions window.loadPositions = async function() { const user = window.auth.currentUser; if (!user) return; const username = user.displayName || user.email; await checkOpenOrders(username); positionsTableBody.innerHTML = ""; outgoingOrdersBody.innerHTML = ""; const balanceKey = `pt_balance_${username}`; const portfolioKey = `pt_portfolio_${username}`; const currentBalance = parseFloat(localStorage.getItem(balanceKey) || "100000"); const portfolio = JSON.parse(localStorage.getItem(portfolioKey) || "{}"); let totalPositionsValue = 0; const symbols = Object.keys(portfolio); const symbolValueMap = {}; const trades = JSON.parse(localStorage.getItem("pt_trades") || "[]"); const userTrades = trades.filter(t => t.user === username); for (let s of symbols) { const qty = portfolio[s]; if (qty <= 0) continue; const price = await fetchStockPrice(s); let displayPrice = "N/A"; let value = 0; if (price && !isNaN(price)) { displayPrice = `$${price.toFixed(2)}`; value = price * qty; } totalPositionsValue += value; symbolValueMap[s] = value; // Calculate cost basis let totalBought = 0; let totalBoughtQty = 0; let totalSold = 0; let totalSoldQty = 0; userTrades.forEach(trade => { if (trade.symbol === s) { if (trade.action === "BUY") { totalBought += parseFloat(trade.price) * parseInt(trade.quantity, 10); totalBoughtQty += parseInt(trade.quantity, 10); } else if (trade.action === "SELL") { totalSold += parseFloat(trade.price) * parseInt(trade.quantity, 10); totalSoldQty += parseInt(trade.quantity, 10); } } }); const netQty = totalBoughtQty - totalSoldQty; let averageCost = 0; if (netQty > 0) { averageCost = totalBought / totalBoughtQty; } let profitLoss = 0; let percentProfitLoss = 0; if (averageCost > 0 && price && !isNaN(price)) { profitLoss = (price - averageCost) * qty; percentProfitLoss = ((price - averageCost) / averageCost) * 100; } const profitLossFormatted = profitLoss >= 0 ? `+$${profitLoss.toFixed(2)}` : `-$${Math.abs(profitLoss).toFixed(2)}`; const percentProfitLossFormatted = percentProfitLoss >= 0 ? `+${percentProfitLoss.toFixed(2)}%` : `-${Math.abs(percentProfitLoss).toFixed(2)}%`; const profitLossStyle = profitLoss >= 0 ? 'style="color: green;"' : 'style="color: red;"'; const percentProfitLossStyle = percentProfitLoss >= 0 ? 'style="color: green;"' : 'style="color: red;"'; const row = document.createElement("tr"); row.innerHTML = `