xxxxxxxxxx
679
let stockData = {};
let selectedStock = null;
let currentDay = 0;
let cash = 10000;
let portfolio = {};
let marketSummary = {
sp500: 4700,
nasdaq: 15700,
trend: "bullish"
};
let chartType = "line";
let timeframe = "1m";
let canvas;
let canvasWidth = 800;
let canvasHeight = 400;
// News items
let newsItems = [
{ title: "Fed Keeps Interest Rates Steady", content: "The Federal Reserve announced it will maintain current interest rates.", date: "Day 1", impact: { tech: 0.005, energy: 0.002, finance: 0.008 } },
{ title: "New Tech Breakthrough Announced", content: "Major tech company unveils groundbreaking AI technology.", date: "Day 3", impact: { tech: 0.025, consumer: 0.01 } },
{ title: "Oil Prices Drop on Supply News", content: "Global oil prices fall as supply increases.", date: "Day 7", impact: { energy: -0.03, transportation: 0.01 } },
{ title: "Retail Sales Exceed Expectations", content: "Consumer spending higher than expected in quarterly report.", date: "Day 14", impact: { retail: 0.02, consumer: 0.015 } },
{ title: "Major Bank Reports Strong Earnings", content: "Leading financial institution beats earnings expectations.", date: "Day 21", impact: { finance: 0.018 } }
];
// Stock definitions
const stocksList = [
{ symbol: "AAPL", name: "Apple Inc.", sector: "tech", price: 175, volatility: 0.015 },
{ symbol: "MSFT", name: "Microsoft Corp.", sector: "tech", price: 320, volatility: 0.014 },
{ symbol: "AMZN", name: "Amazon.com Inc.", sector: "retail", price: 140, volatility: 0.022 },
{ symbol: "TSLA", name: "Tesla Inc.", sector: "automotive", price: 220, volatility: 0.035 },
{ symbol: "GOOG", name: "Alphabet Inc.", sector: "tech", price: 135, volatility: 0.016 },
{ symbol: "META", name: "Meta Platforms", sector: "tech", price: 315, volatility: 0.021 },
{ symbol: "XOM", name: "Exxon Mobil", sector: "energy", price: 110, volatility: 0.013 },
{ symbol: "JPM", name: "JPMorgan Chase", sector: "finance", price: 153, volatility: 0.014 },
{ symbol: "WMT", name: "Walmart Inc.", sector: "retail", price: 165, volatility: 0.01 },
{ symbol: "DIS", name: "Walt Disney Co.", sector: "consumer", price: 95, volatility: 0.018 }
];
// Initialize stock data with 250 days of history
function initializeStockData() {
stocksList.forEach(stock => {
stockData[stock.symbol] = {
name: stock.name,
sector: stock.sector,
data: generateStockHistory(stock, 250),
color: getRandomColor(),
owned: 0
};
});
selectedStock = stocksList[0].symbol;
updatePortfolioValue();
updateStocksList();
}
// Generate realistic stock history
function generateStockHistory(stock, days) {
let price = stock.price;
let history = [];
let trend = (Math.random() > 0.5) ? 0.0005 : -0.0003; // Slight trend
for (let i = 0; i < days; i++) {
let change = (Math.random() - 0.5) * stock.volatility + trend;
price *= (1 + change);
// Add some randomness to volume
let volume = Math.floor(Math.random() * 1000000) + 500000;
// Generate OHLC data
let open = price;
let high = price * (1 + Math.random() * 0.01);
let low = price * (1 - Math.random() * 0.01);
let close = price * (1 + (Math.random() - 0.5) * 0.005);
history.push({
day: i,
price: close,
open: open,
high: high,
low: low,
close: close,
volume: volume
});
}
return history;
}
// Update stock prices for a new day
function updateStockPrices(days = 1) {
let newsApplied = false;
for (let d = 0; d < days; d++) {
currentDay++;
// Check for news events on this day
let todayNews = newsItems.filter(news => news.date === `Day ${currentDay}`);
if (todayNews.length > 0) {
newsApplied = true;
updateNewsTab(todayNews);
}
// Update each stock
Object.keys(stockData).forEach(symbol => {
const stock = stockData[symbol];
const lastPrice = stock.data[stock.data.length - 1].close;
const stockInfo = stocksList.find(s => s.symbol === symbol);
// Base volatility
let change = (Math.random() - 0.5) * stockInfo.volatility;
// Apply market trend
change += (marketSummary.trend === "bullish") ? 0.001 : -0.001;
// Apply news impact if any
if (todayNews.length > 0) {
todayNews.forEach(news => {
if (news.impact[stockInfo.sector]) {
change += news.impact[stockInfo.sector];
}
});
}
// Calculate new prices
let open = lastPrice;
let close = lastPrice * (1 + change);
let high = Math.max(open, close) * (1 + Math.random() * 0.005);
let low = Math.min(open, close) * (1 - Math.random() * 0.005);
let volume = Math.floor(Math.random() * 1000000) + 500000;
stock.data.push({
day: stock.data.length,
price: close,
open: open,
high: high,
low: low,
close: close,
volume: volume
});
});
// Occasionally change market trend
if (Math.random() < 0.03) {
marketSummary.trend = (marketSummary.trend === "bullish") ? "bearish" : "bullish";
}
// Update indexes
marketSummary.sp500 *= 1 + (Math.random() - 0.4) * 0.01;
marketSummary.nasdaq *= 1 + (Math.random() - 0.4) * 0.015;
}
// Update UI
updateStocksList();
updateMarketSummary();
updatePortfolioValue();
// If news was applied, highlight the news tab
if (newsApplied) {
document.querySelector('[data-tab="news"]').style.backgroundColor = "#f9a825";
setTimeout(() => {
document.querySelector('[data-tab="news"]').style.backgroundColor = "";
}, 3000);
}
}
// Update the news tab with new items
function updateNewsTab(newItems) {
const newsList = document.getElementById('news-list');
newItems.forEach(item => {
const newsItem = document.createElement('div');
newsItem.className = 'news-item';
newsItem.innerHTML = `
<h4>${item.title}</h4>
<p>${item.content}</p>
<span class="news-date">${item.date}</span>
`;
newsList.prepend(newsItem);
});
}
// Update market summary
function updateMarketSummary() {
document.getElementById('sp500').textContent = `${marketSummary.sp500.toFixed(2)} ${getChangeIcon(Math.random() - 0.4)}`;
document.getElementById('nasdaq').textContent = `${marketSummary.nasdaq.toFixed(2)} ${getChangeIcon(Math.random() - 0.4)}`;
document.getElementById('market-trend').textContent = marketSummary.trend.charAt(0).toUpperCase() + marketSummary.trend.slice(1);
}
// Get change icon (up or down arrow)
function getChangeIcon(change) {
return change >= 0 ?
'<span class="positive">▲</span>' :
'<span class="negative">▼</span>';
}
// Handle stock buying
function buyStock() {
const quantity = parseInt(document.getElementById('quantity').value);
if (isNaN(quantity) || quantity <= 0) {
alert("Please enter a valid quantity.");
return;
}
const stock = stockData[selectedStock];
const currentPrice = stock.data[stock.data.length - 1].close;
const totalCost = currentPrice * quantity;
if (totalCost > cash) {
alert("Insufficient funds!");
return;
}
// Update portfolio and cash
if (!portfolio[selectedStock]) {
portfolio[selectedStock] = {
quantity: 0,
avgPrice: 0,
totalCost: 0
};
}
// Update average price
const newTotalCost = portfolio[selectedStock].totalCost + totalCost;
const newQuantity = portfolio[selectedStock].quantity + quantity;
portfolio[selectedStock].avgPrice = newTotalCost / newQuantity;
portfolio[selectedStock].quantity = newQuantity;
portfolio[selectedStock].totalCost = newTotalCost;
stock.owned += quantity;
cash -= totalCost;
updateCashDisplay();
updatePortfolioValue();
updatePortfolioTab();
}
// Handle stock selling
function sellStock() {
const quantity = parseInt(document.getElementById('quantity').value);
if (isNaN(quantity) || quantity <= 0) {
alert("Please enter a valid quantity.");
return;
}
const stock = stockData[selectedStock];
if (!portfolio[selectedStock] || portfolio[selectedStock].quantity < quantity) {
alert("You don't own enough shares to sell!");
return;
}
const currentPrice = stock.data[stock.data.length - 1].close;
const saleValue = currentPrice * quantity;
// Update portfolio and cash
portfolio[selectedStock].quantity -= quantity;
if (portfolio[selectedStock].quantity === 0) {
portfolio[selectedStock].avgPrice = 0;
portfolio[selectedStock].totalCost = 0;
} else {
portfolio[selectedStock].totalCost = portfolio[selectedStock].avgPrice * portfolio[selectedStock].quantity;
}
stock.owned -= quantity;
cash += saleValue;
updateCashDisplay();
updatePortfolioValue();
updatePortfolioTab();
}
// Update cash display
function updateCashDisplay() {
document.getElementById('cash-balance').textContent = `Cash: $${cash.toFixed(2)}`;
}
// Calculate total portfolio value
function updatePortfolioValue() {
let totalValue = cash;
Object.keys(stockData).forEach(symbol => {
const stock = stockData[symbol];
if (stock.owned > 0) {
const currentPrice = stock.data[stock.data.length - 1].close;
totalValue += stock.owned * currentPrice;
}
});
document.getElementById('portfolio-value').textContent = `Portfolio: $${totalValue.toFixed(2)}`;
}
// Update portfolio tab
function updatePortfolioTab() {
const holdingsList = document.getElementById('holdings-list');
holdingsList.innerHTML = '';
let hasHoldings = false;
Object.keys(portfolio).forEach(symbol => {
if (portfolio[symbol].quantity > 0) {
hasHoldings = true;
const stock = stockData[symbol];
const currentPrice = stock.data[stock.data.length - 1].close;
const value = portfolio[symbol].quantity * currentPrice;
const profit = value - portfolio[symbol].totalCost;
const profitPercent = (profit / portfolio[symbol].totalCost) * 100;
const holdingItem = document.createElement('div');
holdingItem.className = 'stock-card';
holdingItem.innerHTML = `
<div class="symbol">${symbol}</div>
<div>Shares: ${portfolio[symbol].quantity}</div>
<div>Avg Price: $${portfolio[symbol].avgPrice.toFixed(2)}</div>
<div>Current: $${currentPrice.toFixed(2)}</div>
<div>Value: $${value.toFixed(2)}</div>
<div class="change ${profit >= 0 ? 'positive' : 'negative'}">
Profit: $${profit.toFixed(2)} (${profitPercent.toFixed(2)}%)
</div>
`;
// Add click event to select this stock
holdingItem.addEventListener('click', () => {
selectedStock = symbol;
updateStocksList();
redrawChart();
});
holdingsList.appendChild(holdingItem);
}
});
if (!hasHoldings) {
holdingsList.innerHTML = '<p>You don\'t own any stocks yet.</p>';
}
}
// Update stocks list
function updateStocksList() {
const stocksList = document.getElementById('stocks-list');
stocksList.innerHTML = '';
Object.keys(stockData).forEach(symbol => {
const stock = stockData[symbol];
const data = stock.data;
const current = data[data.length - 1].close;
const previous = data[data.length - 2].close;
const change = ((current - previous) / previous) * 100;
const stockCard = document.createElement('div');
stockCard.className = `stock-card ${symbol === selectedStock ? 'selected' : ''}`;
stockCard.innerHTML = `
<div class="symbol">${symbol}</div>
<div class="price">$${current.toFixed(2)}</div>
<div class="change ${change >= 0 ? 'positive' : 'negative'}">
${change >= 0 ? '+' : ''}${change.toFixed(2)}%
</div>
${stock.owned > 0 ? `<div>Owned: ${stock.owned}</div>` : ''}
`;
stockCard.addEventListener('click', () => {
selectedStock = symbol;
updateStocksList();
redrawChart();
});
stocksList.appendChild(stockCard);
});
// Update buy/sell buttons state
document.getElementById('sell-button').disabled =
!portfolio[selectedStock] || portfolio[selectedStock].quantity <= 0;
}
// Get a random color for stock charts
function getRandomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
function setup() {
canvas = createCanvas(canvasWidth, canvasHeight);
canvas.parent('canvas-container');
initializeStockData();
updateMarketSummary();
redrawChart();
// Add event listeners
document.getElementById('timeframe-select').addEventListener('change', (e) => {
timeframe = e.target.value;
redrawChart();
});
document.getElementById('chart-type').addEventListener('change', (e) => {
chartType = e.target.value;
redrawChart();
});
document.getElementById('buy-button').addEventListener('click', buyStock);
document.getElementById('sell-button').addEventListener('click', sellStock);
document.getElementById('next-day').addEventListener('click', () => {
updateStockPrices(1);
redrawChart();
});
document.getElementById('next-week').addEventListener('click', () => {
updateStockPrices(5);
redrawChart();
});
document.getElementById('next-month').addEventListener('click', () => {
updateStockPrices(21);
redrawChart();
});
document.getElementById('add-indicator').addEventListener('click', () => {
const indicators = ['SMA', 'EMA', 'RSI', 'MACD'];
const indicator = indicators[Math.floor(Math.random() * indicators.length)];
alert(`${indicator} indicator added to chart.`);
});
// Tab switching
document.querySelectorAll('.tab-button').forEach(button => {
button.addEventListener('click', (e) => {
const tab = e.target.getAttribute('data-tab');
// Hide all tabs
document.querySelectorAll('.tab-content').forEach(content => {
content.style.display = 'none';
});
// Show selected tab
document.getElementById(`${tab}-tab`).style.display = 'block';
// Update active tab button
document.querySelectorAll('.tab-button').forEach(btn => {
btn.classList.remove('active');
});
e.target.classList.add('active');
});
});
// Initialize portfolio tab
updatePortfolioTab();
// Initialize news tab
updateNewsTab(newsItems.filter(news => news.date === "Day 1"));
}
function draw() {
// p5.js continuously calls this, but we'll only redraw when needed
}
function redrawChart() {
clear();
background(255);
if (!selectedStock || !stockData[selectedStock]) return;
const stock = stockData[selectedStock];
const data = stock.data;
// Determine data range based on timeframe
let dataPoints;
switch(timeframe) {
case '1d':
dataPoints = data.slice(-2);
break;
case '5d':
dataPoints = data.slice(-6);
break;
case '1m':
dataPoints = data.slice(-22);
break;
case '3m':
dataPoints = data.slice(-63);
break;
case '1y':
dataPoints = data.slice(-252);
break;
default:
dataPoints = data.slice(-22);
}
// Draw chart title
fill(0);
textSize(16);
textAlign(LEFT, TOP);
text(`${selectedStock} - ${stock.name}`, 20, 10);
// Draw current price
const currentPrice = data[data.length-1].close;
const prevPrice = data[data.length-2].close;
const priceChange = currentPrice - prevPrice;
const priceChangePercent = (priceChange / prevPrice) * 100;
textAlign(RIGHT, TOP);
text(`$${currentPrice.toFixed(2)} ${priceChange >= 0 ? '▲' : '▼'} ${Math.abs(priceChangePercent).toFixed(2)}%`, width - 20, 10);
// Determine min and max values for scaling
let minVal = Infinity;
let maxVal = -Infinity;
if (chartType === 'volume') {
for (let i = 0; i < dataPoints.length; i++) {
minVal = Math.min(minVal, 0);
maxVal = Math.max(maxVal, dataPoints[i].volume);
}
} else {
for (let i = 0; i < dataPoints.length; i++) {
if (chartType === 'candle') {
minVal = Math.min(minVal, dataPoints[i].low);
maxVal = Math.max(maxVal, dataPoints[i].high);
} else {
minVal = Math.min(minVal, dataPoints[i].close);
maxVal = Math.max(maxVal, dataPoints[i].close);
}
}
}
// Add some padding
const range = maxVal - minVal;
minVal -= range * 0.05;
maxVal += range * 0.05;
// Draw chart based on type
if (chartType === 'line') {
drawLineChart(dataPoints, minVal, maxVal);
} else if (chartType === 'candle') {
drawCandlestickChart(dataPoints, minVal, maxVal);
} else if (chartType === 'volume') {
drawVolumeChart(dataPoints, minVal, maxVal);
}
// Draw time axis
drawTimeAxis(dataPoints);
// Draw price axis
drawPriceAxis(minVal, maxVal);
}
function drawLineChart(dataPoints, minVal, maxVal) {
const chartColor = stockData[selectedStock].color;
stroke(chartColor);
strokeWeight(2);
noFill();
beginShape();
for (let i = 0; i < dataPoints.length; i++) {
const x = map(i, 0, dataPoints.length - 1, 50, width - 20);
const y = map(dataPoints[i].close, minVal, maxVal, height - 40, 40);
vertex(x, y);
}
endShape();
// Draw dots at each data point
fill(chartColor);
noStroke();
for (let i = 0; i < dataPoints.length; i++) {
const x = map(i, 0, dataPoints.length - 1, 50, width - 20);
const y = map(dataPoints[i].close, minVal, maxVal, height - 40, 40);
ellipse(x, y, 5, 5);
}
}
function drawCandlestickChart(dataPoints, minVal, maxVal) {
strokeWeight(1);
for (let i = 0; i < dataPoints.length; i++) {
const x = map(i, 0, dataPoints.length - 1, 50, width - 20);
const candleWidth = (((width - 20) - 50) / dataPoints.length) * 0.8;
const high = map(dataPoints[i].high, minVal, maxVal, height - 40, 40);
const low = map(dataPoints[i].low, minVal, maxVal, height - 40, 40);
const open = map(dataPoints[i].open, minVal, maxVal, height - 40, 40);
const close = map(dataPoints[i].close, minVal, maxVal, height - 40, 40);
// Draw line from high to low
stroke(0);
line(x, high, x, low);
// Draw candle body
noStroke();
if (close < open) {
// Bearish - red
fill(255, 59, 59);
} else {
// Bullish - green
fill(0, 191, 0);
}
rect(x - candleWidth/2, Math.min(open, close), candleWidth, Math.abs(close - open));
}
}
function drawVolumeChart(dataPoints, minVal, maxVal) {
fill(100, 100, 255, 180);
noStroke();
const barWidth = (((width - 20) - 50) / dataPoints.length) * 0.8;
for (let i = 0; i < dataPoints.length; i++) {
const x = map(i, 0, dataPoints.length - 1, 50, width - 20);
const y = map(dataPoints[i].volume, minVal, maxVal, height - 40, 40);
// Draw volume bar
rect(x - barWidth/2, y, barWidth, height - 40 - y);
}
}
function drawTimeAxis(dataPoints) {
stroke(200);
strokeWeight(1);
line(50, height - 40, width - 20, height - 40);
// Draw time labels
fill(0);
noStroke();
textSize(10);
textAlign(CENTER, TOP);
const labelCount = Math.min(dataPoints.length, 5);
for (let i = 0; i < labelCount; i++) {
const index = Math.floor(i * (dataPoints.length - 1) / (labelCount - 1));
const x = map(index, 0, dataPoints.length - 1, 50, width - 20);
// Format date based on timeframe
let label;
if (timeframe === '1d') {
label = `Hour ${index * 2}`;
} else {
label = `Day ${dataPoints[index].day}`;
}
text(label, x, height - 35);
}
}
function drawPriceAxis(minVal, maxVal) {
stroke(200);
strokeWeight(1);
line(50, 40, 50, height - 40);
// Draw price labels
fill(0);
noStroke();
textSize(10);
textAlign(RIGHT, CENTER);
const stepCount = 5;
for (let i = 0; i <= stepCount; i++) {
const value = minVal + (maxVal - minVal) * (i / stepCount);
const y = map(value, minVal, maxVal, height - 40, 40);
line(45, y, 50, y);
let label;
if (chartType === 'volume') {
label = Math.floor(value / 1000) + "K";
} else {
label = "$" + value.toFixed(2);
}
text(label, 40, y);
}
}
// Initialize the application when the document is ready
window.onload = function() {
// Check if p5.js has loaded
if (typeof setup === 'function') {
setup();
} else {
console.error('p5.js not loaded');
}
};