-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #488 from bounswe/feature/FE-stock-details-page
Add stock details page
- Loading branch information
Showing
13 changed files
with
691 additions
and
2 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
frontend/src/components/markets/stocks/StockAboutSection.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import React from "react"; | ||
import "../../../styles/markets/stocks/StockOverviewPage.css"; | ||
|
||
|
||
const StockAboutSection = ({ stockDetails }) => { | ||
return ( | ||
<div className="stock-tab-section"> | ||
<h2>General Information</h2> | ||
<div className="stock-tab-section-content"> | ||
<div className="stock-tab-section-item"> | ||
<p><strong>Company:</strong> {stockDetails?.longName}</p> | ||
<p><strong>Industry:</strong> {stockDetails?.industryDisp}</p> | ||
<p><strong>Sector:</strong> {stockDetails?.sectorDisp}</p> | ||
<p><strong>Address:</strong> {stockDetails?.address1}, {stockDetails?.address2}, {stockDetails?.city}, {stockDetails?.country}</p> | ||
<p><strong>Website:</strong> <a href={stockDetails?.website} target="_blank" rel="noreferrer">{stockDetails?.website}</a></p> | ||
<p><strong>Description:</strong> {stockDetails?.longBusinessSummary}</p> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default StockAboutSection; |
184 changes: 184 additions & 0 deletions
184
frontend/src/components/markets/stocks/StockChartSection.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
import React, { useState, useEffect } from "react"; | ||
import { createChart } from "lightweight-charts"; | ||
import "../../../styles/markets/stocks/StockOverviewPage.css"; | ||
import RandomUtil from "../../../utils/randomUtil"; | ||
|
||
|
||
const StockChartSection = ({ indexId, stockData }) => { | ||
const [duration, setDuration] = useState("1D"); | ||
|
||
const generateRandomData = (mean, deviation) => { | ||
const data = new Map([ | ||
["1D", []], | ||
["1W", []], | ||
["1M", []], | ||
["1Y", []], | ||
]); | ||
|
||
let value = mean; | ||
let time = new Date(); // Start from the current date and time | ||
|
||
// Calculate the time for 1 year ago from today | ||
const yearAgo = new Date(); | ||
yearAgo.setFullYear(yearAgo.getFullYear() - 1); // Set to 1 year ago | ||
|
||
const rng = RandomUtil.createGenerator(indexId); | ||
// Loop through the time intervals and generate random data | ||
// 1d: 30 min intervals | ||
// 1w: 4h intervals | ||
// 1m: 12h intervals | ||
// 1y: 1d intervals | ||
const step = [30, 240, 720, 1440]; | ||
const elapsedTimes = [1440, 10080, 43200, 525600]; | ||
const keys = ["1D", "1W", "1M", "1Y"]; | ||
for (let i = 0; i < step.length; i++) { | ||
const interval = step[i]; | ||
time = new Date(); | ||
// 30 min floor | ||
time.setMinutes(Math.floor(time.getMinutes() / interval) * interval); | ||
value = mean; | ||
while (time >= yearAgo) { | ||
|
||
console.log("Value:", value); | ||
const timestamp = Math.floor(time.getTime() / 1000); | ||
const elapsedTime = (new Date() - time) / (1000 * 60); | ||
|
||
if (elapsedTime <= elapsedTimes[i]) { | ||
data.get(keys[i]).push({ time: timestamp, value }); | ||
} | ||
value = value + RandomUtil.generateRandomNumber(rng) * deviation - deviation / 2; | ||
time = new Date(time - interval * 60 * 1000); | ||
} | ||
} | ||
|
||
|
||
data.forEach((seriesData, key) => { | ||
data.set(key, seriesData.reverse()); | ||
}); | ||
|
||
|
||
console.log("Generated data:", data); | ||
return data; | ||
}; | ||
|
||
const mean = stockData.price; | ||
const deviation = 0.2; | ||
const seriesesData = generateRandomData(mean, deviation); | ||
|
||
useEffect(() => { | ||
const container = document.getElementById('tradingview_chart'); | ||
const chartOptions = { | ||
layout: { | ||
textColor: 'black', | ||
background: { type: 'solid', color: 'white' }, | ||
|
||
}, | ||
height: 400, | ||
}; | ||
const chart = createChart(container, chartOptions); | ||
|
||
const resizeHandler = () => { | ||
const width = container.offsetWidth; | ||
const height = container.offsetHeight; | ||
chart.applyOptions({ width, height }); | ||
chart.timeScale().fitContent(); | ||
}; | ||
resizeHandler(); | ||
window.addEventListener('resize', resizeHandler); | ||
|
||
function setChartInterval(interval) { | ||
chart.timeScale().fitContent(); | ||
} | ||
|
||
setChartInterval(duration); | ||
|
||
const intervals = ['1D', '1W', '1M', '1Y']; | ||
intervals.forEach(interval => { | ||
// Create buttons if needed | ||
let button = document.getElementById(interval); | ||
if (button) { | ||
return; | ||
} | ||
button = document.createElement('button'); | ||
button.id = interval; | ||
button.innerText = interval; | ||
button.addEventListener('click', () => { | ||
setDuration(interval); | ||
setChartInterval(interval); | ||
}); | ||
document.getElementById('buttonsContainer').appendChild(button); | ||
}); | ||
|
||
// first vs last point comparison | ||
const inProfit = seriesesData.get(duration)[0].value < seriesesData.get(duration)[seriesesData.get(duration).length - 1].value; | ||
// porfit ratio | ||
const profitRatio = (seriesesData.get(duration)[seriesesData.get(duration).length - 1].value - seriesesData.get(duration)[0].value) / seriesesData.get(duration)[0].value; | ||
// Gradient color | ||
console.log("Profit ratio:", profitRatio); | ||
// First value | ||
console.log("First value:", seriesesData.get(duration)[0].value); | ||
// Last value | ||
console.log("Last value:", seriesesData.get(duration)[seriesesData.get(duration).length - 1].value); | ||
console.log("Last date in readable:", new Date(seriesesData.get(duration)[seriesesData.get(duration).length - 1].time * 1000).toLocaleDateString()); | ||
console.log("First date in readable:", new Date(seriesesData.get(duration)[0].time * 1000).toLocaleDateString()); | ||
|
||
const topAreaColor = inProfit ? 'rgba(0, 150, 136, 0.3)' : 'rgba(255, 82, 82, 0.3)'; | ||
// Gradient fading | ||
const bottomAreaColor = inProfit ? 'rgba(0, 150, 136, 0)' : 'rgba(255, 82, 82, 0)'; | ||
const lineColor = inProfit ? 'rgba(0, 150, 136, 1)' : 'rgba(255, 82, 82, 1)'; | ||
|
||
chart.applyOptions({ | ||
handleScroll: false, | ||
handleScale: false, | ||
timeScale: { | ||
timeVisible: true, | ||
secondsVisible: false, | ||
}, | ||
rightPriceScale: { | ||
scaleMargins: { | ||
top: 0.3, | ||
bottom: 0.25, | ||
}, | ||
}, | ||
crosshair: { | ||
horzLine: { | ||
visible: true, | ||
labelVisible: true, | ||
}, | ||
}, | ||
grid: { | ||
vertLines: { | ||
visible: false, | ||
}, | ||
horzLines: { | ||
visible: false, | ||
}, | ||
}, | ||
}); | ||
|
||
const areaSeries = chart.addAreaSeries({ | ||
topColor: topAreaColor, | ||
bottomColor: bottomAreaColor, | ||
lineColor: lineColor, | ||
lineWidth: 2, | ||
crossHairMarkerVisible: false, | ||
}); | ||
|
||
areaSeries.setData(seriesesData.get(duration)); | ||
|
||
return () => { | ||
window.removeEventListener('resize', resizeHandler); | ||
chart.remove(); | ||
}; | ||
}, [duration]); | ||
|
||
return ( | ||
<div className="stock-tab-section"> | ||
<h3>Price Chart</h3> | ||
<div id="buttonsContainer" className="duration-buttons"></div> | ||
<div id="tradingview_chart"></div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default StockChartSection; |
79 changes: 79 additions & 0 deletions
79
frontend/src/components/markets/stocks/StockMetricsSection.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import React from "react"; | ||
import { FaDollarSign, FaChartLine, FaArrowUp, FaArrowDown } from "react-icons/fa"; | ||
import '../../../styles/markets/stocks/StockOverviewPage.css'; | ||
|
||
const StockMetricsSection = ({ stockDetails }) => { | ||
return ( | ||
<div className="stock-tab-section"> | ||
<h2 className="section-title">Metrics</h2> | ||
<div className="stock-tab-section-content"> | ||
|
||
{/* Price and Valuation */} | ||
<div className="stock-tab-section-item"> | ||
<h3 className="section-subtitle">Price & Valuation</h3> | ||
<p><FaDollarSign className="icon" /> <strong>Current Price:</strong> {stockDetails?.currentPrice}$</p> | ||
<p><FaArrowDown className="icon" /> <strong>52 Week Low:</strong> {stockDetails?.fiftyTwoWeekLow}$</p> | ||
<p><FaArrowUp className="icon" /> <strong>52 Week High:</strong> {stockDetails?.fiftyTwoWeekHigh}$</p> | ||
<p><FaChartLine className="icon" /> <strong>Market Cap:</strong> {formatNumber(stockDetails?.marketCap)}</p> | ||
<p><FaChartLine className="icon" /> <strong>Enterprise Value:</strong> {formatNumber(stockDetails?.enterpriseValue)}</p> | ||
<p><FaChartLine className="icon" /> <strong>Trailing PE:</strong> {stockDetails?.trailingPE}</p> | ||
<p><FaChartLine className="icon" /> <strong>Forward PE:</strong> {stockDetails?.forwardPE}</p> | ||
<p><FaChartLine className="icon" /> <strong>Price to Book:</strong> {stockDetails?.priceToBook}</p> | ||
</div> | ||
|
||
{/* Dividends */} | ||
<div className="stock-tab-section-item"> | ||
<h3 className="section-subtitle">Dividends</h3> | ||
<p><strong>Dividend Rate:</strong> {stockDetails?.dividendRate}%</p> | ||
<p><strong>Dividend Yield:</strong> {stockDetails?.dividendYield}%</p> | ||
<p><strong>Payout Ratio:</strong> {stockDetails?.payoutRatio}%</p> | ||
<p><strong>Five Year Avg Dividend Yield:</strong> {stockDetails?.fiveYearAvgDividendYield}%</p> | ||
<p><strong>Last Dividend Date:</strong> {stockDetails?.lastDividendDate}</p> | ||
</div> | ||
|
||
{/* Financials */} | ||
<div className="stock-tab-section-item"> | ||
<h3 className="section-subtitle">Financials</h3> | ||
<p><strong>Total Revenue:</strong> {formatNumber(stockDetails?.totalRevenue)}</p> | ||
<p><strong>Net Income to Common:</strong> {formatNumber(stockDetails?.netIncomeToCommon)}</p> | ||
<p><strong>Profit Margins:</strong> {stockDetails?.profitMargins}%</p> | ||
<p><strong>Return on Assets:</strong> {stockDetails?.returnOnAssets}%</p> | ||
<p><strong>Return on Equity:</strong> {stockDetails?.returnOnEquity}%</p> | ||
</div> | ||
|
||
{/* Analysts & Recommendations */} | ||
<div className="stock-tab-section-item"> | ||
<h3 className="section-subtitle">Analysts & Recommendations</h3> | ||
<p><strong>Target High Price:</strong> {stockDetails?.targetHighPrice}$</p> | ||
<p><strong>Target Low Price:</strong> {stockDetails?.targetLowPrice}$</p> | ||
<p><strong>Target Mean Price:</strong> {stockDetails?.targetMeanPrice}$</p> | ||
<p><strong>Recommendation:</strong> {stockDetails?.recommendationKey}</p> | ||
<p><strong>Number of Analyst Opinions:</strong> {stockDetails?.numberOfAnalystOpinions}</p> | ||
</div> | ||
|
||
{/* Stock Metrics */} | ||
<div className="stock-tab-section-item"> | ||
<h3 className="section-subtitle">Stock Metrics</h3> | ||
<p><strong>Beta:</strong> {stockDetails?.beta}</p> | ||
<p><strong>Trailing EPS:</strong> {stockDetails?.trailingEps}</p> | ||
<p><strong>Forward EPS:</strong> {stockDetails?.forwardEps}</p> | ||
<p><strong>Revenue per Share:</strong> {stockDetails?.revenuePerShare}$</p> | ||
<p><strong>Total Cash per Share:</strong> {stockDetails?.totalCashPerShare}$</p> | ||
<p><strong>Previous Close:</strong> {stockDetails?.previousClose}$</p> | ||
<p><strong>Day Low:</strong> {stockDetails?.dayLow}$</p> | ||
<p><strong>Day High:</strong> {stockDetails?.dayHigh}$</p> | ||
<p><strong>Volume:</strong> {formatNumber(stockDetails?.volume)}</p> | ||
<p><strong>Average Volume:</strong> {formatNumber(stockDetails?.averageVolume)}</p> | ||
</div> | ||
|
||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
// Function to format large numbers with commas for better readability | ||
const formatNumber = (num) => { | ||
return num ? num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : "N/A"; | ||
} | ||
|
||
export default StockMetricsSection; |
Oops, something went wrong.