From edf98dda63a008a1c5b3e6dbb422865e498c1cc0 Mon Sep 17 00:00:00 2001 From: MuslemRahimi Date: Thu, 31 Oct 2024 17:04:04 +0100 Subject: [PATCH] ui fixes --- .../[tickerID]/forecast/+page.server.ts | 25 ++- .../stocks/[tickerID]/forecast/+page.svelte | 159 +++++++++++++++--- .../[tickerID]/forecast/analyst/+page.svelte | 12 +- 3 files changed, 164 insertions(+), 32 deletions(-) diff --git a/src/routes/stocks/[tickerID]/forecast/+page.server.ts b/src/routes/stocks/[tickerID]/forecast/+page.server.ts index 0762cadb..01c6375d 100644 --- a/src/routes/stocks/[tickerID]/forecast/+page.server.ts +++ b/src/routes/stocks/[tickerID]/forecast/+page.server.ts @@ -1,11 +1,9 @@ export const load = async ({ locals, params }) => { + const { apiURL, apiKey } = locals; + const postData = { + ticker: params.tickerID, + }; const getAnalystEstimate = async () => { - const { apiURL, apiKey } = locals; - - const postData = { - ticker: params.tickerID, - }; - // make the POST request to the endpoint const response = await fetch(apiURL + "/analyst-estimate", { method: "POST", @@ -21,8 +19,23 @@ export const load = async ({ locals, params }) => { return output; }; + const getAnalystInsight = async () => { + const response = await fetch(apiURL + "/analyst-insight", { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-API-KEY": apiKey, + }, + body: JSON.stringify(postData), + }); + + const output = await response.json(); + return output; + }; + // Make sure to return a promise return { getAnalystEstimate: await getAnalystEstimate(), + getAnalystInsight: await getAnalystInsight(), }; }; diff --git a/src/routes/stocks/[tickerID]/forecast/+page.svelte b/src/routes/stocks/[tickerID]/forecast/+page.svelte index e5705555..7c9b5d8c 100644 --- a/src/routes/stocks/[tickerID]/forecast/+page.svelte +++ b/src/routes/stocks/[tickerID]/forecast/+page.svelte @@ -7,7 +7,17 @@ } from "$lib/store"; import { abbreviateNumber } from "$lib/utils"; + import { Chart } from "svelte-echarts"; + import { init, use } from "echarts/core"; + import { BarChart } from "echarts/charts"; + import { GridComponent, TooltipComponent } from "echarts/components"; + import { CanvasRenderer } from "echarts/renderers"; + import { onMount } from "svelte"; + export let data; + + use([BarChart, GridComponent, TooltipComponent, CanvasRenderer]); + let index = 0; let changeRevenue = 0; let changeNetIncome = 0; @@ -74,6 +84,95 @@ } }; + function getPlotOptions() { + if (!rawAnalystList || rawAnalystList.length === 0) { + return null; + } + + // Define categories in the exact order you specified + const categories = ["Strong Buy", "Buy", "Hold", "Sell", "Strong Sell"]; + const colors = ["#008A00", "#31B800", "#FF9E21", "#D9220E", "#9E190A"]; + + // Create a consistent mapping for data + const formattedData = rawAnalystList.map((item) => + categories.map((cat) => item[cat] || 0), + ); + + // Normalize data to percentages + const normalizedData = formattedData.map((row) => { + const total = row.reduce((sum, val) => sum + val, 0); + return row.map((val) => (total > 0 ? (val / total) * 100 : 0)); + }); + + // Calculate total percentage for each category across all dates + const totalData = []; + for (let i = 0; i < categories.length; ++i) { + let sum = 0; + for (let j = 0; j < normalizedData.length; ++j) { + sum += normalizedData[j][i]; + } + totalData.push(sum / normalizedData.length); + } + + // Define series based on categories with color mapping + const series = categories.map((name, idx) => ({ + name, + type: "bar", + stack: "total", + barWidth: "60%", + data: normalizedData.map((row) => row[idx]), + itemStyle: { + color: colors[idx], + }, + tooltip: { + valueFormatter: (value) => `${value.toFixed(2)}%`, + }, + })); + + // Define chart option + const option = { + grid: { + left: "2%", + right: "2%", + bottom: "10%", + top: "5%", + containLabel: true, + }, + tooltip: { + trigger: "axis", + axisPointer: { + type: "shadow", + }, + }, + legend: { + data: categories, + bottom: 0, + }, + xAxis: { + type: "category", + data: rawAnalystList.map((item) => item.date), + axisLabel: { + color: "#fff", + }, + }, + yAxis: { + type: "value", + max: 100, + + axisLabel: { + show: false, // Hide y-axis labels + }, + splitLine: { + show: false, + }, + }, + series, + animation: false, + }; + + return option; + } + if (data?.getAnalystEstimate?.length !== 0) { index = findIndex(data?.getAnalystEstimate); @@ -97,6 +196,8 @@ changeEBITDA = calculateChange(estimatedEbitdaAvg, ebitda); changeEPS = calculateChange(estimatedEpsAvg, eps); } + + let optionsData = getPlotOptions() || null; @@ -258,29 +359,30 @@ class="flex flex-col justify-between p-1 lg:max-w-[32%] text-white" >
-

Analyst Ratings

-

- According to {numOfAnalyst} stock analyst, the rating for GameStop - is "{consensusRating}". This means that the analyst believes - this stock is likely to lead to {[ - "Strong Sell", - "Sell", - ]?.includes(consensusRating) - ? "lower" - : ["Strong Buy", "Buy"]?.includes(consensusRating) - ? "higher" - : "similar"} returns than market as a whole. -

+

Latest Analyst Report

+ {#if Object?.keys(data?.getAnalystInsight)?.length > 0} +

{data?.getAnalystInsight?.insight}

+ {:else} +

+ According to {numOfAnalyst} stock analyst, the rating for GameStop + is "{consensusRating}". This means that the analyst believes + this stock is likely to lead to {[ + "Strong Sell", + "Sell", + ]?.includes(consensusRating) + ? "lower" + : ["Strong Buy", "Buy"]?.includes(consensusRating) + ? "higher" + : "similar"} returns than market as a whole. +

+ {/if}
-
- +
+ {#if optionsData !== null} + + {/if}
+ + diff --git a/src/routes/stocks/[tickerID]/forecast/analyst/+page.svelte b/src/routes/stocks/[tickerID]/forecast/analyst/+page.svelte index c0d43644..5bf0ea75 100644 --- a/src/routes/stocks/[tickerID]/forecast/analyst/+page.svelte +++ b/src/routes/stocks/[tickerID]/forecast/analyst/+page.svelte @@ -250,23 +250,23 @@ > - Analyst - Firm - Rating - Action - Price Target - Date