update ai forecast page

This commit is contained in:
MuslemRahimi 2025-03-12 23:48:24 +01:00
parent e2987da793
commit a046f9a904
3 changed files with 326 additions and 248 deletions

View File

@ -0,0 +1,59 @@
<script lang="ts">
export let data;
</script>
<section class="w-full overflow-hidden">
<div class="w-full overflow-hidden m-auto">
<div class="sm:p-0 flex justify-center w-full m-auto overflow-hidden">
<div
class="relative flex flex-col lg:flex-row justify-center items-start overflow-hidden w-full"
>
<main class="w-full lg:w-3/4 lg:pr-10">
<slot />
</main>
<aside class="inline-block relative w-full lg:w-1/4 mt-3">
{#if !["Pro", "Plus"]?.includes(data?.user?.tier) || data?.user?.freeTrial}
<div
class="w-full border border-gray-300 dark:border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer sm:hover:shadow-lg dark:sm:hover:bg-secondary transition ease-out duration-100"
>
<a
href="/pricing"
class="w-auto lg:w-full p-1 flex flex-col m-auto px-2 sm:px-0"
>
<div class="w-full flex justify-between items-center p-3 mt-3">
<h2 class="text-start text-xl font-semibold sm:ml-3">
Pro Subscription
</h2>
</div>
<span class=" p-3 sm:ml-3 sm:mr-3 -mt-4">
Upgrade now for unlimited access to all data and tools.
</span>
</a>
</div>
{/if}
<div
class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded-md h-fit pb-4 mt-4"
>
<h3 class="p-2 pt-4 text-xl font-semibold">Revenue Definition</h3>
<div class=" p-2">
Revenue, also called sales, is the amount of money a company
receives from its business activities, such as sales of products
or services. Revenue does not take any expenses into account and
is therefore different from profits.
</div>
<div class="px-2">
<a
href="/blog/article/revenue"
class="flex justify-center items-center rounded cursor-pointer w-full py-2 mt-3 text-[1rem] text-center font-semibold text-white dark:text-black m-auto sm:hover:bg-blue-600 dark:sm:hover:bg-gray-300 bg-[#3B82F6] dark:bg-[#fff] transition duration-100"
>
Full Definition
</a>
</div>
</div>
</aside>
</div>
</div>
</div>
</section>

View File

@ -23,10 +23,27 @@ export const load = async ({ locals, params }) => {
return output; return output;
}; };
const getHistoricalPrice = async () => {
const postData = { ticker: params.tickerID, timePeriod: "max" };
const response = await fetch(apiURL + "/historical-price", {
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 // Make sure to return a promise
return { return {
getPriceAnalysis: await getPriceAnalysis(), getPriceAnalysis: await getPriceAnalysis(),
getHistoricalPrice: await getHistoricalPrice(),
}; };
}; };

View File

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { displayCompanyName, stockTicker, screenWidth } from "$lib/store"; import { displayCompanyName, stockTicker, screenWidth } from "$lib/store";
import { removeCompanyStrings } from "$lib/utils";
import Infobox from "$lib/components/Infobox.svelte"; import Infobox from "$lib/components/Infobox.svelte";
import highcharts from "$lib/highcharts.ts"; import highcharts from "$lib/highcharts.ts";
import { mode } from "mode-watcher"; import { mode } from "mode-watcher";
@ -39,160 +40,173 @@
consensusRating = "Hold"; consensusRating = "Hold";
} }
function getPieChart() { function getAIScorePlot() {
let value; // Assume data.getHistoricalPrice contains objects with a "time" field (e.g. "2015-01-02")
// Determine the value based on the consensus rating const historicalData = data?.getHistoricalPrice || [];
switch (consensusRating) {
case "Strong Sell": // Pre-defined backtest dates and scores
value = 0.5; const backtestList = [
break; { date: "2023-03-31", yTest: 1, yPred: 1, score: 9 },
case "Sell": { date: "2023-06-30", yTest: 0, yPred: 1, score: 9 },
value = 1.5; { date: "2023-09-30", yTest: 0, yPred: 1, score: 9 },
break; { date: "2023-12-31", yTest: 0, yPred: 1, score: 7 },
case "Hold": { date: "2024-03-31", yTest: 1, yPred: 1, score: 9 },
value = 2.5; { date: "2024-06-30", yTest: 1, yPred: 1, score: 9 },
break; { date: "2024-09-30", yTest: 1, yPred: 1, score: 9 },
case "Buy": { date: "2024-12-31", yTest: 0, yPred: 1, score: 9 },
value = 3.5; ];
break;
case "Strong Buy": // Append the latest historical date (using "time") if available. Note that this entry may not include a score.
value = 4.5; if (historicalData && historicalData.length) {
break; const latest = historicalData.at(-1);
default: backtestList.push({ date: latest.time });
value = 0.5;
break;
} }
// For each backtest entry, find the historical price with the closest available time.
// Then, if a score exists, attach a marker and data label based on the score.
const processedData = backtestList.map((item) => {
const dateStr = item.date;
const targetTime = new Date(dateStr).getTime();
let closestPoint = historicalData[0];
let minDiff = Infinity;
historicalData.forEach((point) => {
const pointTime = new Date(point.time).getTime();
const diff = Math.abs(pointTime - targetTime);
if (diff < minDiff) {
minDiff = diff;
closestPoint = point;
}
});
// Base data point with x as the target date timestamp and y as the close price from the closest historical point
const dataPoint = { x: targetTime, y: closestPoint.close };
// If a score is provided, add marker configuration based on its value.
if (item.hasOwnProperty("score")) {
let markerColor, markerSymbol;
if (item.score > 6) {
// Bullish: green marker with an upward triangle
markerColor = "#2ecc71";
markerSymbol = "triangle-up";
} else if (item.score < 5) {
// Bearish: red marker with a downward triangle
markerColor = "#e74c3c";
markerSymbol = "triangle-down";
} else {
// Neutral (score exactly 5): yellow marker with a circle
markerColor = "#f1c40f";
markerSymbol = "circle";
}
dataPoint.marker = {
symbol: markerSymbol,
radius: 4,
fillColor: markerColor,
lineWidth: 2,
lineColor: $mode === "light" ? "black" : "white",
};
dataPoint.dataLabels = {
enabled: true,
format: String(item.score),
style: {
color: $mode === "light" ? "black" : "white",
fontWeight: "bold",
fontSize: "14px",
},
y: -10,
};
}
return dataPoint;
});
// Highcharts options for plotting the data with markers.
const options = { const options = {
chart: {
backgroundColor: $mode === "light" ? "#fff" : "#09090B",
height: 360,
animation: false,
},
title: {
text: `<h3 class="mt-3 mb-1 text-[1rem] sm:text-lg">Historical AI Score Performance</h3>`,
style: {
color: $mode === "light" ? "black" : "white",
// Using inline CSS for margin-top and margin-bottom
},
useHTML: true, // Enable HTML to apply custom class styling
},
xAxis: {
gridLineWidth: 1,
gridLineColor: $mode === "light" ? "#d1d5dc" : "#111827",
type: "datetime",
labels: {
style: {
color: $mode === "light" ? "black" : "white",
},
formatter: function () {
const date = new Date(this.value);
return date.toLocaleDateString("en-US", {
month: "short",
year: "numeric",
});
},
},
},
tooltip: {
enabled: false,
},
yAxis: {
title: {
text: "",
},
labels: {
style: {
color: $mode === "light" ? "black" : "white",
},
formatter: function () {
return `$${this.value.toFixed(0)}`;
},
},
gridLineWidth: 1,
gridLineColor: $mode === "light" ? "#d1d5dc" : "#111827",
},
series: [
{
name: "Close Price",
data: processedData,
color: $mode === "light" ? "#007050" : "#fff",
animation: false,
marker: {
enabled: true,
},
lineWidth: 2,
},
],
plotOptions: {
series: {
enableMouseTracking: false,
states: {
hover: {
enabled: false,
},
},
marker: {
states: {
hover: {
enabled: false,
},
},
},
},
},
legend: { legend: {
enabled: false, enabled: false,
}, },
credits: { credits: {
enabled: false, enabled: false,
}, },
chart: {
type: "gauge",
backgroundColor: $mode === "light" ? "#fff" : "#09090B",
plotBackgroundColor: $mode === "light" ? "#fff" : "#09090B",
animation: false,
},
title: {
text: null,
},
yAxis: {
min: 0,
max: 5,
tickPosition: "inside",
tickLength: 20,
tickWidth: 0,
minorTickInterval: null,
lineWidth: 0,
// Remove numeric tick labels
labels: {
enabled: false,
},
plotBands: [
{
from: 0,
to: 1,
color: "#9E190A",
thickness: 40,
borderRadius: "0px",
},
{
from: 1,
to: 2,
color: "#D9220E",
thickness: 40,
borderRadius: "0px",
},
{
from: 2,
to: 3,
color: "#f5b700",
thickness: 40,
borderRadius: "0px",
},
{
from: 3,
to: 4,
color: "#31B800",
thickness: 40,
borderRadius: "0px",
},
{
from: 4,
to: 5,
color: "#008A00",
thickness: 40,
borderRadius: "0px",
},
],
},
pane: {
startAngle: -90,
endAngle: 89.9,
background: null,
center: ["50%", "50%"],
size: "70%",
},
series: [
{
name: "Rating",
data: [value],
animation: false,
dataLabels: {
enabled: true,
useHTML: true,
borderWidth: 0,
backgroundColor: "transparent",
style: {
textOutline: "none",
fontSize: "16px",
fontWeight: "bold",
},
formatter: function () {
const rating = this.y;
let ratingText = "";
let textColor = "";
if (rating < 1) {
ratingText = "Strong Sell";
textColor = "#D9220E";
} else if (rating < 2) {
ratingText = "Sell";
textColor = "#D9220E";
} else if (rating < 3) {
ratingText = "Hold";
textColor = "#f5b700";
} else if (rating < 4) {
ratingText = "Buy";
textColor = "#31B800";
} else {
ratingText = "Strong Buy";
textColor = "#31B800";
}
// "Analyst Consensus:" in white, rating in color
return `
<span class="text-lg text-muted dark:text-white">Analyst Consensus: </span>
<span class="text-lg" style="color:${textColor};">${ratingText}</span>
`;
},
},
dial: {
radius: "80%",
backgroundColor: "#2A2E39",
baseWidth: 12,
baseLength: "0%",
rearLength: "0%",
},
pivot: {
backgroundColor: "#2A2E39",
radius: 8,
},
},
],
}; };
return options; return options;
@ -457,13 +471,13 @@
return options; return options;
} }
let optionsPieChart = null;
let config = null; let config = null;
let configScore = null;
$: { $: {
if ($mode) { if ($mode) {
optionsPieChart = getPieChart() || null; configScore = getAIScorePlot() || null;
config = getPriceForecastChart() || null; config = getAIScorePlot() || null;
} }
} }
</script> </script>
@ -473,62 +487,48 @@
description={`Discover our AI-powered forecast for ${$displayCompanyName} (${$stockTicker}). Get in-depth analyst ratings, price targets, upgrades, and downgrades from top Wall Street experts. Stay ahead of market trends and make smarter investment decisions.`} description={`Discover our AI-powered forecast for ${$displayCompanyName} (${$stockTicker}). Get in-depth analyst ratings, price targets, upgrades, and downgrades from top Wall Street experts. Stay ahead of market trends and make smarter investment decisions.`}
/> />
<section class="w-full overflow-hidden min-h-screen"> <section class="w-full overflow-hidden h-full">
<div class="w-full flex h-full overflow-hidden"> <div class="w-full flex justify-center w-full sm-auto h-full overflow-hidden">
<div <div
class="w-full relative flex justify-center items-center overflow-hidden" class="w-full relative flex justify-center items-center overflow-hidden"
> >
<div class="sm:pl-4 sm:pt-4 w-full m-auto mt-2 sm:mt-0"> <main class="w-full">
<h1 class="mb-px text-xl sm:text-2xl font-bold bp:text-3xl sm:pl-1"> <div class="sm:pl-7 sm:pb-7 sm:pt-7 m-auto mt-2 sm:mt-0">
{$displayCompanyName} AI Forecast <div class="">
<h1 class="text-xl sm:text-2xl font-bold">
{removeCompanyStrings($displayCompanyName)} Trend Forecast
</h1> </h1>
</div>
{#if Object?.keys(data?.getPriceAnalysis)?.length > 0} {#if Object?.keys(data?.getPriceAnalysis)?.length > 0}
<div class="w-full mb-6 mt-3"> <div class="w-full mb-6 mt-3">
<div <Infobox
class="rounded-sm border border-gray-300 dark:border-gray-600 p-0.5 xs:p-1 md:flex md:flex-col md:space-y-4 md:divide-y md:p-4 lg:flex-row lg:space-x-4 lg:space-y-0 lg:divide-x lg:divide-y-0 divide-gray-300 dark:divide-gray-600" text={`Using our AI model trained on historical data, we generated a
> 12month forecast for ${$displayCompanyName}
<div (${$stockTicker}) stock. The model estimates a median target price
class="p-3 md:flex md:space-x-4 md:p-0 lg:block lg:max-w-[32%] lg:space-x-0" of ${medianPriceTarget}—ranging from a low of {lowPriceTarget}
> to a high of ${highPriceTarget}—which suggests ${
medianChange > 0 ? "an increase" : "a decrease"
} of ${medianChange}% compared to the current price
of ${price}.`}
/>
<div> <div>
<div class="flex items-baseline justify-between"> <div class="grow pt-5">
<h2 class="mb-1 text-xl font-bold">Stock Price Forecast</h2>
<span></span>
</div>
<p>
Using our AI model trained on historical data, we generated
a 12month forecast for {$displayCompanyName}
({$stockTicker}) stock. The model estimates a median target
price of {medianPriceTarget}—ranging from a low of {lowPriceTarget}
to a high of {highPriceTarget}—which suggests {medianChange >
0
? "an increase"
: "a decrease"} of {medianChange}% compared to the current
price of {price}.
</p>
</div>
<div>
<div>
<div
class="max-h-[225px]"
use:highcharts={optionsPieChart}
></div>
</div>
</div>
</div>
<div class="grow pt-2 md:pt-4 lg:pl-4 lg:pt-0">
<div <div
class="chart mt-5 sm:mt-0 sm:border sm:border-gray-300 dark:border-gray-800 rounded" class="chart mt-5 sm:mt-0 sm:border sm:border-gray-300 dark:border-gray-800 rounded"
use:highcharts={config} use:highcharts={config}
></div> ></div>
<div <div
class="hide-scroll mb-1 mt-2 overflow-x-auto px-1.5 text-center md:mb-0 md:px-0 lg:mt-2" class="hide-scroll mb-1 mt-2 overflow-x-auto px-1.5 text-center md:mb-0 md:px-0 lg:mt-5"
> >
<table class="w-full text-right text-tiny xs:text-sm"> <table class="w-full text-right text-tiny xs:text-sm">
<thead <thead
><tr ><tr
class="border-b border-gray-300 dark:border-gray-600 font-normal text-sm sm:text-[1rem]" class="border-b border-gray-300 dark:border-gray-600 font-normal text-sm sm:text-[1rem]"
><th class="py-[3px] text-left font-semibold lg:py-0.5" ><th
class="py-[3px] text-left font-semibold lg:py-0.5"
>Target</th >Target</th
> <th class="font-semibold">Low</th> > <th class="font-semibold">Low</th>
<th class="font-semibold">Average</th> <th class="font-semibold">Average</th>
@ -541,7 +541,8 @@
class="border-b border-gray-300 dark:border-gray-600 font-normal text-sm sm:text-[1rem]" class="border-b border-gray-300 dark:border-gray-600 font-normal text-sm sm:text-[1rem]"
><td class="py-[3px] text-left lg:py-0.5">Price</td> ><td class="py-[3px] text-left lg:py-0.5">Price</td>
<td>${lowPriceTarget}</td> <td>${lowPriceTarget}</td>
<td>${avgPriceTarget}</td> <td>${medianPriceTarget}</td> <td>${avgPriceTarget}</td>
<td>${medianPriceTarget}</td>
<td>${highPriceTarget}</td></tr <td>${highPriceTarget}</td></tr
> >
<tr class="text-sm sm:text-[1rem]" <tr class="text-sm sm:text-[1rem]"
@ -581,6 +582,7 @@
<Infobox text="No AI Price Forecast available right now" /> <Infobox text="No AI Price Forecast available right now" />
{/if} {/if}
</div> </div>
</main>
</div> </div>
</div> </div>
</section> </section>