ui fix
This commit is contained in:
parent
396f07292d
commit
57fa0224bf
@ -1120,7 +1120,7 @@
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-sm md:">
|
||||
<tbody class="text-sm md:text-[1rem]">
|
||||
<tr>
|
||||
<td class="py-2 md:py-3">Full Market Access</td>
|
||||
<td class="py-2 md:py-3 text-center">❌</td>
|
||||
|
||||
67
src/routes/stocks/[tickerID]/forecast/ai/+layout.server.ts
Normal file
67
src/routes/stocks/[tickerID]/forecast/ai/+layout.server.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { error, fail, redirect } from "@sveltejs/kit";
|
||||
import { validateData } from "$lib/utils";
|
||||
import { loginUserSchema, registerUserSchema } from "$lib/schemas";
|
||||
|
||||
export const load = async ({ locals, params }) => {
|
||||
const { apiURL, apiKey } = locals;
|
||||
const postData = {
|
||||
ticker: params.tickerID,
|
||||
};
|
||||
|
||||
|
||||
const getPriceAnalysis = async () => {
|
||||
const response = await fetch(apiURL + "/price-analysis", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-API-KEY": apiKey,
|
||||
},
|
||||
body: JSON.stringify(postData),
|
||||
});
|
||||
|
||||
const output = await response.json();
|
||||
return output;
|
||||
};
|
||||
|
||||
const getAIScore = async () => {
|
||||
const response = await fetch(apiURL + "/ai-score", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-API-KEY": apiKey,
|
||||
},
|
||||
body: JSON.stringify(postData),
|
||||
});
|
||||
|
||||
const output = await response.json();
|
||||
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(),
|
||||
getAIScore: await getAIScore(),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
@ -33,17 +33,20 @@
|
||||
</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">AI Score 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>
|
||||
<!--
|
||||
{#if data?.getAIScore?.backtest?.length > 0}
|
||||
<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">
|
||||
AI Score 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"
|
||||
@ -53,21 +56,23 @@
|
||||
</a>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</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">
|
||||
AI Trend Forecast 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>
|
||||
<!--
|
||||
{#if Object?.keys(data?.getPriceAnalysis)?.length > 0}
|
||||
<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">
|
||||
AI Trend Forecast 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"
|
||||
@ -77,7 +82,8 @@
|
||||
</a>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,201 +0,0 @@
|
||||
import { error, fail, redirect } from "@sveltejs/kit";
|
||||
import { validateData } from "$lib/utils";
|
||||
import { loginUserSchema, registerUserSchema } from "$lib/schemas";
|
||||
|
||||
export const load = async ({ locals, params }) => {
|
||||
const { apiURL, apiKey } = locals;
|
||||
const postData = {
|
||||
ticker: params.tickerID,
|
||||
};
|
||||
|
||||
|
||||
const getPriceAnalysis = async () => {
|
||||
const response = await fetch(apiURL + "/price-analysis", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-API-KEY": apiKey,
|
||||
},
|
||||
body: JSON.stringify(postData),
|
||||
});
|
||||
|
||||
const output = await response.json();
|
||||
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(),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
export const actions = {
|
||||
login: async ({ url, request, locals }) => {
|
||||
|
||||
const path = url?.href?.replace("/oauth2","")
|
||||
|
||||
const { formData, errors } = await validateData(
|
||||
await request.formData(),
|
||||
loginUserSchema,
|
||||
);
|
||||
|
||||
if (errors) {
|
||||
return fail(400, {
|
||||
data: formData,
|
||||
errors: errors.fieldErrors,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await locals.pb
|
||||
.collection("users")
|
||||
.authWithPassword(formData.email, formData.password);
|
||||
|
||||
/*
|
||||
if (!locals.pb?.authStore?.model?.verified) {
|
||||
locals.pb.authStore.clear();
|
||||
return {
|
||||
notVerified: true,
|
||||
};
|
||||
}
|
||||
*/
|
||||
} catch (err) {
|
||||
console.log("Error: ", err);
|
||||
error(err.status, err.message);
|
||||
}
|
||||
|
||||
redirect(302, path);
|
||||
},
|
||||
|
||||
register: async ({ url, locals, request }) => {
|
||||
const path = url?.href?.replace("/oauth2","")
|
||||
|
||||
const { formData, errors } = await validateData(
|
||||
await request.formData(),
|
||||
registerUserSchema,
|
||||
);
|
||||
|
||||
if (errors) {
|
||||
return fail(400, {
|
||||
data: formData,
|
||||
errors: errors.fieldErrors,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
let newUser = await locals.pb.collection("users").create(formData);
|
||||
/*
|
||||
await locals.pb?.collection('users').update(
|
||||
newUser?.id, {
|
||||
'freeTrial' : true,
|
||||
'tier': 'Pro', //Give new users a free trial for the Pro Subscription
|
||||
});
|
||||
*/
|
||||
await locals.pb.collection("users")?.requestVerification(formData.email);
|
||||
} catch (err) {
|
||||
console.log("Error: ", err);
|
||||
error(err.status, err.message);
|
||||
}
|
||||
|
||||
try {
|
||||
await locals.pb
|
||||
.collection("users")
|
||||
.authWithPassword(formData.email, formData.password);
|
||||
} catch (err) {
|
||||
console.log("Error: ", err);
|
||||
error(err.status, err.message);
|
||||
}
|
||||
|
||||
redirect(303, path);
|
||||
},
|
||||
|
||||
oauth2: async ({ url, locals, request, cookies }) => {
|
||||
|
||||
const path = url?.href?.replace("/oauth2","")
|
||||
const authMethods = (await locals?.pb
|
||||
?.collection("users")
|
||||
?.listAuthMethods())?.oauth2;
|
||||
|
||||
|
||||
const data = await request?.formData();
|
||||
const providerSelected = data?.get("provider");
|
||||
|
||||
if (!authMethods) {
|
||||
return {
|
||||
authProviderRedirect: "",
|
||||
authProviderState: "",
|
||||
};
|
||||
}
|
||||
const redirectURL = `${url.origin}/oauth`;
|
||||
|
||||
const targetItem = authMethods?.providers?.findIndex(
|
||||
(item) => item?.name === providerSelected,
|
||||
);
|
||||
//console.log("==================")
|
||||
//console.log(authMethods.authProviders)
|
||||
//console.log('target item is: ', targetItem)
|
||||
|
||||
const provider = authMethods.providers[targetItem];
|
||||
const authProviderRedirect = `${provider.authUrl}${redirectURL}`;
|
||||
const state = provider.state;
|
||||
const verifier = provider.codeVerifier;
|
||||
|
||||
|
||||
|
||||
cookies.set("state", state, {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
secure: true,
|
||||
path: "/",
|
||||
maxAge: 60 * 60,
|
||||
});
|
||||
|
||||
cookies.set("verifier", verifier, {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
secure: true,
|
||||
path: "/",
|
||||
maxAge: 60 * 60,
|
||||
});
|
||||
|
||||
cookies.set("provider", providerSelected, {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
secure: true,
|
||||
path: "/",
|
||||
maxAge: 60 * 60,
|
||||
});
|
||||
|
||||
cookies.set("path", path, {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
secure: true,
|
||||
path: "/",
|
||||
maxAge: 60,
|
||||
});
|
||||
|
||||
redirect(302, authProviderRedirect);
|
||||
},
|
||||
|
||||
};
|
||||
@ -9,6 +9,13 @@
|
||||
|
||||
export let data;
|
||||
|
||||
const formatDateToQuarter = (timestamp) => {
|
||||
const date = new Date(timestamp);
|
||||
const year = date.getFullYear().toString().slice(-2); // Get last two digits of the year
|
||||
const quarter = Math.floor(date.getMonth() / 3) + 1; // Determine quarter (Q1-Q4)
|
||||
return `Q${quarter} '${year}`;
|
||||
};
|
||||
|
||||
const price = data?.getStockQuote?.price?.toFixed(2) || 0;
|
||||
|
||||
const calculatePriceChange = (targetPrice) =>
|
||||
@ -24,104 +31,115 @@
|
||||
const avgChange = calculatePriceChange(avgPriceTarget);
|
||||
const highChange = calculatePriceChange(highPriceTarget);
|
||||
|
||||
let consensusRating;
|
||||
// Assume data.getHistoricalPrice contains objects with a "time" field (e.g. "2015-01-02")
|
||||
const historicalData = data?.getHistoricalPrice || [];
|
||||
|
||||
if (avgChange < -20) {
|
||||
consensusRating = "Strong Sell";
|
||||
} else if (avgChange < -10) {
|
||||
consensusRating = "Sell";
|
||||
} else if (avgChange <= 15) {
|
||||
consensusRating = "Hold";
|
||||
} else if (avgChange >= 35) {
|
||||
consensusRating = "Strong Buy";
|
||||
} else if (avgChange >= 20) {
|
||||
consensusRating = "Buy";
|
||||
} else {
|
||||
consensusRating = "Hold";
|
||||
const backtestList = data?.getAIScore?.backtest || [];
|
||||
|
||||
// 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 });
|
||||
}
|
||||
|
||||
function getAIScorePlot() {
|
||||
// Assume data.getHistoricalPrice contains objects with a "time" field (e.g. "2015-01-02")
|
||||
const historicalData = data?.getHistoricalPrice || [];
|
||||
// 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;
|
||||
|
||||
// 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,
|
||||
};
|
||||
historicalData.forEach((point) => {
|
||||
const pointTime = new Date(point.time).getTime();
|
||||
const diff = Math.abs(pointTime - targetTime);
|
||||
if (diff < minDiff) {
|
||||
minDiff = diff;
|
||||
closestPoint = point;
|
||||
}
|
||||
|
||||
return dataPoint;
|
||||
});
|
||||
|
||||
// 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;
|
||||
});
|
||||
|
||||
const tableDates = processedData
|
||||
?.slice(0, -1)
|
||||
?.map((item) => formatDateToQuarter(item.x));
|
||||
|
||||
const tableScore = processedData
|
||||
?.slice(0, -1)
|
||||
?.map((item) => item?.dataLabels?.format);
|
||||
|
||||
// Compute percentage change
|
||||
const tableQuarterChange = processedData
|
||||
?.slice(0, -1)
|
||||
.map((item, index, arr) => {
|
||||
const prevY = arr[index - 1]?.y; // Get the previous value
|
||||
if (prevY == null || item.y == null) return null; // Handle missing values
|
||||
const change = ((item.y - prevY) / prevY) * 100; // Calculate percentage change
|
||||
return {
|
||||
quarter: tableDates[index],
|
||||
change: Number(change?.toFixed(2)), // Format to 2 decimal places
|
||||
};
|
||||
})
|
||||
?.filter(Boolean); // Remove null values
|
||||
|
||||
// Compute Average Return
|
||||
const returns = processedData
|
||||
?.slice(1) // Skip the first value since there's no previous value for it
|
||||
?.map((item, index) => {
|
||||
const prevY = processedData[index]?.y;
|
||||
if (prevY == null || item.y == null) return null;
|
||||
const returnPercentage = ((item.y - prevY) / prevY) * 100;
|
||||
return returnPercentage;
|
||||
})
|
||||
.filter(Boolean); // Remove null values
|
||||
|
||||
const avgReturn =
|
||||
returns?.reduce((sum, returnPercentage) => sum + returnPercentage, 0) /
|
||||
returns?.length;
|
||||
|
||||
function getAIScorePlot() {
|
||||
const solidData = processedData.slice(0, -1);
|
||||
const lastTwoPoints = processedData.slice(-2); // Extract the last two points
|
||||
|
||||
@ -423,19 +441,61 @@
|
||||
>
|
||||
<main class="w-full">
|
||||
<div class="sm:pl-7 sm:pb-7 sm:pt-7 m-auto mt-2 sm:mt-0">
|
||||
{#if Object?.keys(data?.getPriceAnalysis)?.length > 0}
|
||||
{#if data?.getAIScore?.backtest?.length > 0}
|
||||
<div class="">
|
||||
<h1 class="text-xl sm:text-2xl font-bold">
|
||||
{removeCompanyStrings($displayCompanyName)} AI Score
|
||||
{removeCompanyStrings($displayCompanyName)} AI Score Forecast
|
||||
</h1>
|
||||
</div>
|
||||
<div class="w-full mb-10 mt-3">
|
||||
<Infobox
|
||||
text={`Our AI Score Model indicates a bullish outlook on Tesla with a score of 9. Key stats: Accuracy 50%, Precision 50%, F1 Score 67%, Recall 100%, ROC AUC 50%. Backtest results show consistent bullish predictions, with scores of 9 in most periods. Despite moderate accuracy, high recall ensures no bullish signals are missed. While improvements are needed, our model suggests strong upside potential for Tesla.`}
|
||||
/>
|
||||
<div
|
||||
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mb-4 mt-3"
|
||||
>
|
||||
<div
|
||||
class="shadow-md bg-gray-100 dark:bg-gray-800/30 rounded-lg p-4"
|
||||
>
|
||||
<div class="text-sm sm:text-[1rem] mb-2 flex items-center">
|
||||
<span>Score Accuracy</span>
|
||||
</div>
|
||||
<div class="flex items-baseline">
|
||||
<span class="text-xl font-bold"
|
||||
>{data?.getAIScore?.accuracy
|
||||
? data?.getAIScore?.accuracy + "%"
|
||||
: "n/a"}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="shadow-md bg-gray-100 dark:bg-gray-800/30 rounded-lg p-4"
|
||||
>
|
||||
<div class="text-sm sm:text-[1rem] mb-2 flex items-center">
|
||||
<span>Latest Forecast</span>
|
||||
</div>
|
||||
<div class="flex items-baseline">
|
||||
<span class="text-xl font-bold">Bullish</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="shadow-md bg-gray-100 dark:bg-gray-800/30 rounded-lg p-4"
|
||||
>
|
||||
<div class="text-sm sm:text-[1rem] mb-2 flex items-center">
|
||||
<span>Avg Return</span>
|
||||
</div>
|
||||
<div class="flex items-baseline">
|
||||
<span
|
||||
class="text-xl font-bold {avgReturn >= 0
|
||||
? "before:content-['+'] text-green-600 dark:text-[#00FC50]"
|
||||
: 'text-red-600 dark:text-[#FF2F1F]'}"
|
||||
>{avgReturn?.toFixed(2)}%</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="grow pt-5">
|
||||
<div class="grow">
|
||||
<div
|
||||
class="chart mt-5 shadow-sm sm:mt-0 sm:border sm:border-gray-300 dark:border-gray-800 rounded"
|
||||
use:highcharts={configScore}
|
||||
@ -444,57 +504,58 @@
|
||||
<div
|
||||
class="no-scrollbar 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="table table-sm table-compact 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]"
|
||||
class="border-b border-gray-300 dark:border-gray-600 font-normal text-sm sm:text-[1rem] whitespace-nowrap"
|
||||
><th
|
||||
class="py-[3px] text-left font-semibold lg:py-0.5"
|
||||
class="py-[3px] text-left font-semibold lg:py-0.5 text-muted dark:text-white"
|
||||
>Date</th
|
||||
> <th class="font-semibold">Q1 '23</th>
|
||||
<th class="font-semibold">Q2 '23</th>
|
||||
<th class="font-semibold">Q3 '23</th>
|
||||
<th class="font-semibold">Q4 '23</th></tr
|
||||
></thead
|
||||
>
|
||||
{#each tableDates as item}
|
||||
<th
|
||||
class="py-[3px] text-left font-semibold lg:py-0.5 text-muted dark:text-white"
|
||||
>{item}</th
|
||||
>
|
||||
{/each}
|
||||
</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">Score</td>
|
||||
<td>8 (Bullish)</td>
|
||||
<td>9 (Bullish)</td>
|
||||
<td>8 (Bullish)</td>
|
||||
<td>7 (Hold)</td></tr
|
||||
class=" border-b border-gray-300 dark:border-gray-600 font-normal text-sm sm:text-[1rem] whitespace-nowrap"
|
||||
>
|
||||
<tr class="text-sm sm:text-[1rem]"
|
||||
><td class="py-[3px] text-left lg:py-0.5"
|
||||
<td class="py-[3px] text-left lg:py-0.5 text-[1rem]"
|
||||
>Score</td
|
||||
>
|
||||
{#each tableScore as val}
|
||||
<td
|
||||
class="text-right whitespace-nowrap text-[1rem]"
|
||||
>
|
||||
{val}
|
||||
{[10, 9, 8, 7]?.includes(Number(val))
|
||||
? "(Bullish)"
|
||||
: [6, 5, 4]?.includes(Number(val))
|
||||
? "(Hold)"
|
||||
: "(Sell)"}
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
<tr
|
||||
class=" font-normal text-sm sm:text-[1rem] whitespace-nowrap"
|
||||
><td class="py-[3px] text-left lg:py-0.5 text-[1rem]"
|
||||
>QoQ 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
|
||||
{#each tableQuarterChange as item}
|
||||
<td
|
||||
class="text-[1rem] {item?.change > 0
|
||||
? "before:content-['+'] text-green-600 dark:text-[#00FC50]"
|
||||
: 'text-red-600 dark:text-[#FF2F1F]'}"
|
||||
>{item?.change}%</td
|
||||
>
|
||||
{/each}
|
||||
</tr></tbody
|
||||
>
|
||||
</table>
|
||||
</div>
|
||||
@ -502,9 +563,13 @@
|
||||
<p class="mt-4">
|
||||
Following the AI Score for {removeCompanyStrings(
|
||||
$displayCompanyName,
|
||||
)} the model shows that the total return would be
|
||||
<strong>+22.2%</strong>, with a maximum drawdown of
|
||||
<strong>-12%</strong> based on the backtesting results.
|
||||
)} the model shows that the average return would be
|
||||
<span
|
||||
class="font-semibold {avgReturn >= 0
|
||||
? "before:content-['+'] text-green-600 dark:text-[#00FC50]"
|
||||
: 'text-red-600 dark:text-[#FF2F1F]'}"
|
||||
>{avgReturn?.toFixed(2)}%</span
|
||||
> based on the backtesting results.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -519,14 +584,7 @@
|
||||
</div>
|
||||
<div class="w-full mb-6 mt-3">
|
||||
<Infobox
|
||||
text={`Using our AI model trained on historical data, we generated a
|
||||
12‑month 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}.`}
|
||||
text={`Using our AI model trained on historical seasonal data, we generated a 12-month forecast for ${removeCompanyStrings($displayCompanyName)}. The model predicts a median target price of ${medianPriceTarget}, ranging from ${lowPriceTarget} to ${highPriceTarget}, indicating a ${medianChange > 0 ? "potential increase" : "potential decrease"} of ${medianChange}% from the current price of ${price}.`}
|
||||
/>
|
||||
|
||||
<div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user