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;
};
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
return {
getPriceAnalysis: await getPriceAnalysis(),
getHistoricalPrice: await getHistoricalPrice(),
};
};

View File

@ -1,5 +1,6 @@
<script lang="ts">
import { displayCompanyName, stockTicker, screenWidth } from "$lib/store";
import { removeCompanyStrings } from "$lib/utils";
import Infobox from "$lib/components/Infobox.svelte";
import highcharts from "$lib/highcharts.ts";
import { mode } from "mode-watcher";
@ -39,160 +40,173 @@
consensusRating = "Hold";
}
function getPieChart() {
let value;
// Determine the value based on the consensus rating
switch (consensusRating) {
case "Strong Sell":
value = 0.5;
break;
case "Sell":
value = 1.5;
break;
case "Hold":
value = 2.5;
break;
case "Buy":
value = 3.5;
break;
case "Strong Buy":
value = 4.5;
break;
default:
value = 0.5;
break;
function getAIScorePlot() {
// Assume data.getHistoricalPrice contains objects with a "time" field (e.g. "2015-01-02")
const historicalData = data?.getHistoricalPrice || [];
// Pre-defined backtest dates and scores
const backtestList = [
{ date: "2023-03-31", yTest: 1, yPred: 1, score: 9 },
{ date: "2023-06-30", yTest: 0, yPred: 1, score: 9 },
{ date: "2023-09-30", yTest: 0, yPred: 1, score: 9 },
{ date: "2023-12-31", yTest: 0, yPred: 1, score: 7 },
{ date: "2024-03-31", yTest: 1, yPred: 1, score: 9 },
{ date: "2024-06-30", yTest: 1, yPred: 1, score: 9 },
{ date: "2024-09-30", yTest: 1, yPred: 1, score: 9 },
{ date: "2024-12-31", yTest: 0, yPred: 1, score: 9 },
];
// Append the latest historical date (using "time") if available. Note that this entry may not include a score.
if (historicalData && historicalData.length) {
const latest = historicalData.at(-1);
backtestList.push({ date: latest.time });
}
// 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 = {
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: {
enabled: false,
},
credits: {
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;
@ -457,13 +471,13 @@
return options;
}
let optionsPieChart = null;
let config = null;
let configScore = null;
$: {
if ($mode) {
optionsPieChart = getPieChart() || null;
config = getPriceForecastChart() || null;
configScore = getAIScorePlot() || null;
config = getAIScorePlot() || null;
}
}
</script>
@ -473,114 +487,102 @@
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">
<div class="w-full flex h-full overflow-hidden">
<section class="w-full overflow-hidden h-full">
<div class="w-full flex justify-center w-full sm-auto h-full overflow-hidden">
<div
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">
<h1 class="mb-px text-xl sm:text-2xl font-bold bp:text-3xl sm:pl-1">
{$displayCompanyName} AI Forecast
</h1>
{#if Object?.keys(data?.getPriceAnalysis)?.length > 0}
<div class="w-full mb-6 mt-3">
<div
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"
>
<div
class="p-3 md:flex md:space-x-4 md:p-0 lg:block lg:max-w-[32%] lg:space-x-0"
>
<div>
<div class="flex items-baseline justify-between">
<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
class="chart mt-5 sm:mt-0 sm:border sm:border-gray-300 dark:border-gray-800 rounded"
use:highcharts={config}
></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"
>
<table class="w-full text-right text-tiny xs:text-sm">
<thead
><tr
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"
>Target</th
> <th class="font-semibold">Low</th>
<th class="font-semibold">Average</th>
<th class="font-semibold">Median</th>
<th class="font-semibold">High</th></tr
></thead
>
<tbody
><tr
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>${lowPriceTarget}</td>
<td>${avgPriceTarget}</td> <td>${medianPriceTarget}</td>
<td>${highPriceTarget}</td></tr
<main class="w-full">
<div class="sm:pl-7 sm:pb-7 sm:pt-7 m-auto mt-2 sm:mt-0">
<div class="">
<h1 class="text-xl sm:text-2xl font-bold">
{removeCompanyStrings($displayCompanyName)} Trend Forecast
</h1>
</div>
{#if Object?.keys(data?.getPriceAnalysis)?.length > 0}
<div class="w-full mb-6 mt-3">
<Infobox
text={`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}.`}
/>
<div>
<div class="grow pt-5">
<div
class="chart mt-5 sm:mt-0 sm:border sm:border-gray-300 dark:border-gray-800 rounded"
use:highcharts={config}
></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-5"
>
<table class="w-full text-right text-tiny xs:text-sm">
<thead
><tr
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"
>Target</th
> <th class="font-semibold">Low</th>
<th class="font-semibold">Average</th>
<th class="font-semibold">Median</th>
<th class="font-semibold">High</th></tr
></thead
>
<tr class="text-sm sm:text-[1rem]"
><td class="py-[3px] text-left lg:py-0.5">Change</td>
<td
class={lowChange > 0
? "before:content-['+'] text-green-600 dark:text-[#00FC50]"
: "text-red-600 dark:text-[#FF2F1F]"}
>{lowChange}%</td
<tbody
><tr
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>${lowPriceTarget}</td>
<td>${avgPriceTarget}</td>
<td>${medianPriceTarget}</td>
<td>${highPriceTarget}</td></tr
>
<td
class={avgChange > 0
? "before:content-['+'] text-green-600 dark:text-[#00FC50]"
: "text-red-600 dark:text-[#FF2F1F]"}
>{avgChange}%</td
>
<td
class={medianChange > 0
? "before:content-['+'] text-green-600 dark:text-[#00FC50]"
: "text-red-600 dark:text-[#FF2F1F]"}
>{medianChange}%</td
>
<td
class={highChange > 0
? "before:content-['+'] text-green-600 dark:text-[#00FC50]"
: "text-red-600 dark:text-[#FF2F1F]"}
>{highChange}%</td
></tr
></tbody
>
</table>
<tr class="text-sm sm:text-[1rem]"
><td class="py-[3px] text-left lg:py-0.5">Change</td>
<td
class={lowChange > 0
? "before:content-['+'] text-green-600 dark:text-[#00FC50]"
: "text-red-600 dark:text-[#FF2F1F]"}
>{lowChange}%</td
>
<td
class={avgChange > 0
? "before:content-['+'] text-green-600 dark:text-[#00FC50]"
: "text-red-600 dark:text-[#FF2F1F]"}
>{avgChange}%</td
>
<td
class={medianChange > 0
? "before:content-['+'] text-green-600 dark:text-[#00FC50]"
: "text-red-600 dark:text-[#FF2F1F]"}
>{medianChange}%</td
>
<td
class={highChange > 0
? "before:content-['+'] text-green-600 dark:text-[#00FC50]"
: "text-red-600 dark:text-[#FF2F1F]"}
>{highChange}%</td
></tr
></tbody
>
</table>
</div>
</div>
</div>
</div>
</div>
{:else}
<Infobox text="No AI Price Forecast available right now" />
{/if}
</div>
{:else}
<Infobox text="No AI Price Forecast available right now" />
{/if}
</div>
</main>
</div>
</div>
</section>