upgrade_2
This commit is contained in:
parent
cb4671c3a0
commit
72986fa192
File diff suppressed because one or more lines are too long
@ -19,6 +19,8 @@
|
||||
"express": "4.18.2",
|
||||
"formidable": "1.2.2",
|
||||
"helmet": "4.1.1",
|
||||
"yahoo-finance2": "^2.0.0",
|
||||
"node-cron": "^3.10.0",
|
||||
"json2csv": "^5.0.7",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@ -12,6 +12,29 @@ const { parse } = require('json2csv');
|
||||
|
||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
||||
|
||||
// Market data endpoints
|
||||
const marketDataService = require('../services/marketDataService');
|
||||
|
||||
// Get historical price data for different ranges
|
||||
router.get('/:id/price-history', wrapAsync(async (req, res) => {
|
||||
const stock = await StocksDBApi.findBy({ id: req.params.id });
|
||||
if (!stock) {
|
||||
return res.status(404).send({ message: 'Stock not found' });
|
||||
}
|
||||
const range = req.query.range || 'monthly';
|
||||
const data = await marketDataService.getHistoricalPrices(stock.ticker, range);
|
||||
res.status(200).send(data);
|
||||
}));
|
||||
|
||||
// Get current market price
|
||||
router.get('/:id/price-current', wrapAsync(async (req, res) => {
|
||||
const stock = await StocksDBApi.findBy({ id: req.params.id });
|
||||
if (!stock) {
|
||||
return res.status(404).send({ message: 'Stock not found' });
|
||||
}
|
||||
const price = await marketDataService.getCurrentPrice(stock.ticker);
|
||||
res.status(200).send({ price });
|
||||
}));
|
||||
router.use(checkCrudPermissions('stocks'));
|
||||
|
||||
/**
|
||||
|
||||
32
backend/src/services/marketDataService.js
Normal file
32
backend/src/services/marketDataService.js
Normal file
@ -0,0 +1,32 @@
|
||||
const yahooFinance = require('yahoo-finance2').default;
|
||||
|
||||
async function getCurrentPrice(ticker) {
|
||||
const quote = await yahooFinance.quote(ticker);
|
||||
return quote.regularMarketPrice;
|
||||
}
|
||||
|
||||
async function getHistoricalPrices(ticker, range) {
|
||||
const now = new Date();
|
||||
let days;
|
||||
switch (range) {
|
||||
case 'daily': days = 1; break;
|
||||
case 'weekly': days = 7; break;
|
||||
case 'monthly': days = 30; break;
|
||||
case '3m': days = 90; break;
|
||||
case '6m': days = 180; break;
|
||||
case '1y': days = 365; break;
|
||||
case '3y': days = 365 * 3; break;
|
||||
case '5y': days = 365 * 5; break;
|
||||
default: days = 30;
|
||||
}
|
||||
const from = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
|
||||
const options = { period1: from, period2: now, interval: '1d' };
|
||||
const result = await yahooFinance.historical(ticker, options);
|
||||
// Return array of { date, price }
|
||||
return result.map(item => ({ date: item.date, price: item.close }));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getCurrentPrice,
|
||||
getHistoricalPrices,
|
||||
};
|
||||
1
frontend/json/runtimeError.json
Normal file
1
frontend/json/runtimeError.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { ReactElement, useEffect } from 'react';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import Head from 'next/head';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
@ -17,6 +17,21 @@ import CardBox from '../../components/CardBox';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import BaseDivider from '../../components/BaseDivider';
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
|
||||
import axios from 'axios';
|
||||
import { Line } from 'react-chartjs-2';
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
LineElement,
|
||||
PointElement,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from 'chart.js';
|
||||
|
||||
ChartJS.register(LineElement, PointElement, CategoryScale, LinearScale, Tooltip, Legend);
|
||||
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import FormField from '../../components/FormField';
|
||||
|
||||
@ -34,6 +49,33 @@ const StocksView = () => {
|
||||
function removeLastCharacter(str) {
|
||||
console.log(str, `str`);
|
||||
return str.slice(0, -1);
|
||||
|
||||
// Chart timeframe controls and data state
|
||||
const ranges = ['daily','weekly','monthly','3m','6m','1y','3y','5y'];
|
||||
const [selectedRange, setSelectedRange] = useState('monthly');
|
||||
const [chartData, setChartData] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
axios.get(`/api/stocks/${id}/price-history?range=${selectedRange}`)
|
||||
.then(res => setChartData(res.data))
|
||||
.catch(err => console.error(err));
|
||||
}, [id, selectedRange]);
|
||||
|
||||
// Prepare data for Chart.js
|
||||
const chartLabels = chartData.map(item => dayjs(item.date).format('YYYY-MM-DD'));
|
||||
const chartPrices = chartData.map(item => item.price);
|
||||
const chartJsData = {
|
||||
labels: chartLabels,
|
||||
datasets: [{
|
||||
label: `Price (${selectedRange})`,
|
||||
data: chartPrices,
|
||||
borderColor: '#4F46E5',
|
||||
backgroundColor: 'rgba(79,70,229,0.5)',
|
||||
fill: false,
|
||||
}]
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@ -43,6 +85,27 @@ const StocksView = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<div className="mb-4">
|
||||
<div className="flex space-x-2 mb-4">
|
||||
{ranges.map(range => (
|
||||
<button
|
||||
key={range}
|
||||
onClick={() => setSelectedRange(range)}
|
||||
className={
|
||||
selectedRange === range
|
||||
? 'px-3 py-1 rounded border bg-blue-500 text-white'
|
||||
: 'px-3 py-1 rounded border bg-white text-blue-500'
|
||||
}
|
||||
>
|
||||
{range.toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="h-64 mb-4">
|
||||
<Line data={chartJsData} options={{ responsive: true, maintainAspectRatio: false }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<title>{getPageTitle('View stocks')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user