This commit is contained in:
MuslemRahimi 2025-03-13 02:23:15 +01:00
parent 396f07292d
commit 57fa0224bf
5 changed files with 307 additions and 377 deletions

View File

@ -1120,7 +1120,7 @@
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody class="text-sm md:"> <tbody class="text-sm md:text-[1rem]">
<tr> <tr>
<td class="py-2 md:py-3">Full Market Access</td> <td class="py-2 md:py-3">Full Market Access</td>
<td class="py-2 md:py-3 text-center"></td> <td class="py-2 md:py-3 text-center"></td>

View 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(),
};
};

View File

@ -33,10 +33,13 @@
</div> </div>
{/if} {/if}
{#if data?.getAIScore?.backtest?.length > 0}
<div <div
class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded-md h-fit pb-4 mt-4" 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> <h3 class="p-2 pt-4 text-xl font-semibold">
AI Score Definition
</h3>
<div class=" p-2"> <div class=" p-2">
Revenue, also called sales, is the amount of money a company Revenue, also called sales, is the amount of money a company
receives from its business activities, such as sales of products receives from its business activities, such as sales of products
@ -54,7 +57,9 @@
</div> </div>
--> -->
</div> </div>
{/if}
{#if Object?.keys(data?.getPriceAnalysis)?.length > 0}
<div <div
class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded-md h-fit pb-4 mt-4" class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded-md h-fit pb-4 mt-4"
> >
@ -78,6 +83,7 @@
</div> </div>
--> -->
</div> </div>
{/if}
</aside> </aside>
</div> </div>
</div> </div>

View File

@ -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);
},
};

View File

@ -9,6 +9,13 @@
export let data; 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 price = data?.getStockQuote?.price?.toFixed(2) || 0;
const calculatePriceChange = (targetPrice) => const calculatePriceChange = (targetPrice) =>
@ -24,37 +31,10 @@
const avgChange = calculatePriceChange(avgPriceTarget); const avgChange = calculatePriceChange(avgPriceTarget);
const highChange = calculatePriceChange(highPriceTarget); const highChange = calculatePriceChange(highPriceTarget);
let consensusRating;
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";
}
function getAIScorePlot() {
// Assume data.getHistoricalPrice contains objects with a "time" field (e.g. "2015-01-02") // Assume data.getHistoricalPrice contains objects with a "time" field (e.g. "2015-01-02")
const historicalData = data?.getHistoricalPrice || []; const historicalData = data?.getHistoricalPrice || [];
// Pre-defined backtest dates and scores const backtestList = data?.getAIScore?.backtest || [];
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. // Append the latest historical date (using "time") if available. Note that this entry may not include a score.
if (historicalData && historicalData.length) { if (historicalData && historicalData.length) {
@ -64,7 +44,7 @@
// For each backtest entry, find the historical price with the closest available 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. // Then, if a score exists, attach a marker and data label based on the score.
const processedData = backtestList.map((item) => { const processedData = backtestList?.map((item) => {
const dateStr = item.date; const dateStr = item.date;
const targetTime = new Date(dateStr).getTime(); const targetTime = new Date(dateStr).getTime();
let closestPoint = historicalData[0]; let closestPoint = historicalData[0];
@ -122,6 +102,44 @@
return dataPoint; 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 solidData = processedData.slice(0, -1);
const lastTwoPoints = processedData.slice(-2); // Extract the last two points const lastTwoPoints = processedData.slice(-2); // Extract the last two points
@ -423,19 +441,61 @@
> >
<main class="w-full"> <main class="w-full">
<div class="sm:pl-7 sm:pb-7 sm:pt-7 m-auto mt-2 sm:mt-0"> <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=""> <div class="">
<h1 class="text-xl sm:text-2xl font-bold"> <h1 class="text-xl sm:text-2xl font-bold">
{removeCompanyStrings($displayCompanyName)} AI Score {removeCompanyStrings($displayCompanyName)} AI Score Forecast
</h1> </h1>
</div> </div>
<div class="w-full mb-10 mt-3"> <div class="w-full mb-10 mt-3">
<Infobox <div
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.`} 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>
<div class="grow pt-5"> <div class="grow">
<div <div
class="chart mt-5 shadow-sm sm:mt-0 sm:border sm:border-gray-300 dark:border-gray-800 rounded" class="chart mt-5 shadow-sm sm:mt-0 sm:border sm:border-gray-300 dark:border-gray-800 rounded"
use:highcharts={configScore} use:highcharts={configScore}
@ -444,57 +504,58 @@
<div <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" 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 <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] whitespace-nowrap"
><th ><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 >Date</th
> <th class="font-semibold">Q1 '23</th> >
<th class="font-semibold">Q2 '23</th> {#each tableDates as item}
<th class="font-semibold">Q3 '23</th> <th
<th class="font-semibold">Q4 '23</th></tr class="py-[3px] text-left font-semibold lg:py-0.5 text-muted dark:text-white"
></thead >{item}</th
>
{/each}
</tr></thead
> >
<tbody <tbody
><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] whitespace-nowrap"
><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
> >
<tr class="text-sm sm:text-[1rem]" <td class="py-[3px] text-left lg:py-0.5 text-[1rem]"
><td class="py-[3px] text-left lg:py-0.5" >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 >QoQ Change</td
> >
{#each tableQuarterChange as item}
<td <td
class={lowChange > 0 class="text-[1rem] {item?.change > 0
? "before:content-['+'] text-green-600 dark:text-[#00FC50]" ? "before:content-['+'] text-green-600 dark:text-[#00FC50]"
: "text-red-600 dark:text-[#FF2F1F]"} : 'text-red-600 dark:text-[#FF2F1F]'}"
>{lowChange}%</td >{item?.change}%</td
> >
<td {/each}
class={avgChange > 0 </tr></tbody
? "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> </table>
</div> </div>
@ -502,9 +563,13 @@
<p class="mt-4"> <p class="mt-4">
Following the AI Score for {removeCompanyStrings( Following the AI Score for {removeCompanyStrings(
$displayCompanyName, $displayCompanyName,
)} the model shows that the total return would be )} the model shows that the average return would be
<strong>+22.2%</strong>, with a maximum drawdown of <span
<strong>-12%</strong> based on the backtesting results. 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> </p>
</div> </div>
</div> </div>
@ -519,14 +584,7 @@
</div> </div>
<div class="w-full mb-6 mt-3"> <div class="w-full mb-6 mt-3">
<Infobox <Infobox
text={`Using our AI model trained on historical data, we generated a 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}.`}
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>