add earnings suprise

This commit is contained in:
MuslemRahimi 2024-09-11 14:32:39 +02:00
parent f1c2ec0a13
commit 23761e042c
3 changed files with 143 additions and 24 deletions

View File

@ -0,0 +1,81 @@
<script lang='ts'>
import { stockTicker, displayCompanyName } from "$lib/store";
import InfoModal from '$lib/components/InfoModal.svelte';
import { abbreviateNumber } from "$lib/utils";
export let data;
let rawData = {};
function latestInfoDate(inputDate) {
// Convert the input date string to milliseconds since epoch
const inputDateMs = Date?.parse(inputDate);
// Get today's date in milliseconds since epoch
const todayMs = Date?.now();
// Calculate the difference in milliseconds
const differenceInMs = todayMs - inputDateMs;
// Convert milliseconds to days
const differenceInDays = Math?.floor(differenceInMs / (1000 * 60 * 60 * 24));
// Return the difference in days
return differenceInDays <=3;
}
$: {
if($stockTicker && typeof window !== 'undefined') {
rawData = data?.getEarningsSurprise;
}
}
</script>
{#if Object?.keys(rawData)?.length !== 0}
<div class="space-y-3 overflow-hidden">
<!--Start Content-->
<div class="w-auto lg:w-full p-1 flex flex-col m-auto">
<div class="flex flex-col items-center w-full mb-6">
<div class="flex flex-row justify-start mr-auto items-center">
<!--<img class="h-10 inline-block mr-2" src={copilotIcon} />-->
<div class="flex flex-row items-center">
<label for="earningsSurprise" class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-3xl font-bold">
Earnings Surprise
</label>
<InfoModal
title={"Earnings Surprise"}
content={`The earnings surprise is when a company's actual EPS differs from analysts' forecasts. Positive surprises boost stock prices; negative ones can lower them.`}
id={"earningsSurprise"}
/>
</div>
</div>
</div>
{#if data?.user?.tier === 'Pro'}
<div class="text-white text-[1rem] {latestInfoDate(rawData?.date) ? 'bg-[#F9AB00] bg-opacity-[0.1] p-3 rounded-lg' : 'bg-[#27272A]'} ">
<div class="mt-1">{$displayCompanyName} has released their quartely earnings on {new Date(rawData?.date)?.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', daySuffix: '2-digit' })}:</div>
<li class="ml-[20px] sm:ml-[30px]" style="color: #fff; line-height: 22px; margin-top:20px; margin-bottom: 15px; list-style-type: disc;">
Revenue of {abbreviateNumber(rawData?.revenue,true)} {rawData?.revenueSurprise > 0 ? 'exceeds' : 'misses'} estimates by {abbreviateNumber(Math.abs(rawData?.revenueSurprise),true)}, with {((rawData?.revenue/rawData?.revenuePrior-1)*100)?.toFixed(2)}% YoY {(rawData?.revenue/rawData?.revenuePrior-1) < 0 ? 'decline' : 'growth'}.
</li>
<li class="ml-[20px] sm:ml-[30px]" style="color: #fff; line-height: 22px; margin-top:0px; margin-bottom: 15px; list-style-type: disc;">
EPS of ${rawData?.eps} {rawData?.epsSurprise > 0 ? 'exceeds' : 'misses'} estimates by ${rawData?.epsSurprise?.toFixed(2)}, with {(((rawData?.eps - rawData?.epsPrior) / Math.abs(rawData?.epsPrior)) * 100)?.toFixed(2)}% YoY {((rawData?.eps - rawData?.epsPrior) / Math.abs(rawData?.epsPrior)) < 0 ? 'decline' : 'growth'}.
</li>
</div>
{:else}
<div class="shadow-lg shadow-bg-[#000] bg-[#111112] sm:bg-opacity-[0.5] text-sm sm:text-[1rem] rounded-md w-full p-4 min-h-24 mt-4 text-white m-auto flex justify-center rawDatas-center text-center font-semibold">
<svg class="mr-1.5 w-5 h-5 inline-block"xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#A3A3A3" d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"/></svg>
Unlock content with <a class="inline-block ml-2 text-blue-400 hover:sm:text-white" href="/pricing">Pro Subscription</a>
</div>
{/if}
</div>
</div>
{/if}

View File

@ -1,30 +1,40 @@
const cleanString = (input) => { const cleanString = (input) => {
const substringsToRemove = [ const substringsToRemove = [
'Depositary', 'Inc.', 'Incorporated', 'Holdings', 'Corporation', 'Corporations', "Depositary",
'LLC', 'Holdings plc American Depositary Shares', 'Holding Corporation', 'Oyj', "Inc.",
'Company', 'The', 'plc', "Incorporated",
"Holdings",
"Corporation",
"Corporations",
"LLC",
"Holdings plc American Depositary Shares",
"Holding Corporation",
"Oyj",
"Company",
"The",
"plc",
]; ];
const pattern = new RegExp(`\\b(${substringsToRemove.join('|')})\\b|,`, 'gi'); const pattern = new RegExp(`\\b(${substringsToRemove.join("|")})\\b|,`, "gi");
return input?.replace(pattern, '').trim(); return input?.replace(pattern, "").trim();
}; };
const fetchData = async (apiURL, apiKey, endpoint, ticker) => { const fetchData = async (apiURL, apiKey, endpoint, ticker) => {
const response = await fetch(`${apiURL}${endpoint}`, { const response = await fetch(`${apiURL}${endpoint}`, {
method: 'POST', method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-API-KEY": apiKey "X-API-KEY": apiKey,
}, },
body: JSON.stringify({ ticker }) body: JSON.stringify({ ticker }),
}); });
return response.json(); return response.json();
}; };
const fetchFromFastify = async (fastifyURL, endpoint, userId) => { const fetchFromFastify = async (fastifyURL, endpoint, userId) => {
const response = await fetch(`${fastifyURL}${endpoint}`, { const response = await fetch(`${fastifyURL}${endpoint}`, {
method: 'POST', method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId }) body: JSON.stringify({ userId }),
}); });
const { items } = await response.json(); const { items } = await response.json();
return items; return items;
@ -32,16 +42,18 @@ const fetchFromFastify = async (fastifyURL, endpoint, userId) => {
const fetchCommunitySentiment = async (pb, ticker, cookies) => { const fetchCommunitySentiment = async (pb, ticker, cookies) => {
const cookieVote = cookies.get(`community-sentiment-${ticker}`); const cookieVote = cookies.get(`community-sentiment-${ticker}`);
const today = new Date().toISOString().split('T')[0]; const today = new Date().toISOString().split("T")[0];
const tomorrow = new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().split('T')[0]; const tomorrow = new Date(new Date().setDate(new Date().getDate() + 1))
.toISOString()
.split("T")[0];
const output = await pb.collection("sentiment").getFullList({ const output = await pb.collection("sentiment").getFullList({
filter: `ticker="${ticker}" && created >= "${today}" && created < "${tomorrow}"` filter: `ticker="${ticker}" && created >= "${today}" && created < "${tomorrow}"`,
}); });
return { return {
alreadyVoted: cookieVote || null, alreadyVoted: cookieVote || null,
sentimentData: output?.at(0) || {} sentimentData: output?.at(0) || {},
}; };
}; };
@ -50,24 +62,43 @@ export const load = async ({ params, locals, cookies, setHeaders }) => {
const { tickerID } = params; const { tickerID } = params;
const endpoints = [ const endpoints = [
'/similar-stocks', '/stockdeck', '/analyst-summary-rating', '/stock-quote', "/similar-stocks",
'/bull-bear-say','/wiim', '/top-etf-ticker-holder', '/one-day-price','/next-earnings' "/stockdeck",
"/analyst-summary-rating",
"/stock-quote",
"/bull-bear-say",
"/wiim",
"/top-etf-ticker-holder",
"/one-day-price",
"/next-earnings",
"/earnings-surprise",
]; ];
const promises = [ const promises = [
...endpoints.map(endpoint => fetchData(apiURL, apiKey, endpoint, tickerID)), ...endpoints.map((endpoint) =>
fetchFromFastify(fastifyURL, '/all-watchlists', user?.id), fetchData(apiURL, apiKey, endpoint, tickerID)
),
fetchFromFastify(fastifyURL, "/all-watchlists", user?.id),
//fetchFromFastify(fastifyURL, '/get-portfolio-data', user?.id), //fetchFromFastify(fastifyURL, '/get-portfolio-data', user?.id),
fetchCommunitySentiment(pb, tickerID, cookies) fetchCommunitySentiment(pb, tickerID, cookies),
]; ];
const [ const [
getSimilarStock, getStockDeck, getAnalystRating, getStockQuote, getSimilarStock,
getBullBearSay, getWhyPriceMoved, getTopETFHolder, getOneDayPrice, getNextEarnings, getStockDeck,
getUserWatchlist, getCommunitySentiment getAnalystRating,
getStockQuote,
getBullBearSay,
getWhyPriceMoved,
getTopETFHolder,
getOneDayPrice,
getNextEarnings,
getEarningsSurprise,
getUserWatchlist,
getCommunitySentiment,
] = await Promise.all(promises); ] = await Promise.all(promises);
setHeaders({ 'cache-control': 'public, max-age=300' }); setHeaders({ "cache-control": "public, max-age=300" });
return { return {
getSimilarStock, getSimilarStock,
@ -79,6 +110,7 @@ export const load = async ({ params, locals, cookies, setHeaders }) => {
getTopETFHolder, getTopETFHolder,
getOneDayPrice, getOneDayPrice,
getNextEarnings, getNextEarnings,
getEarningsSurprise,
getUserWatchlist, getUserWatchlist,
getCommunitySentiment, getCommunitySentiment,
companyName: cleanString(getStockDeck?.at(0)?.companyName), companyName: cleanString(getStockDeck?.at(0)?.companyName),

View File

@ -37,6 +37,8 @@
import { onDestroy } from "svelte"; import { onDestroy } from "svelte";
import BullBearSay from "$lib/components/BullBearSay.svelte"; import BullBearSay from "$lib/components/BullBearSay.svelte";
import NextEarnings from "$lib/components/NextEarnings.svelte"; import NextEarnings from "$lib/components/NextEarnings.svelte";
import EarningsSurprise from "$lib/components/EarningsSurprise.svelte";
import CommunitySentiment from "$lib/components/CommunitySentiment.svelte"; import CommunitySentiment from "$lib/components/CommunitySentiment.svelte";
import Lazy from "$lib/components/Lazy.svelte"; import Lazy from "$lib/components/Lazy.svelte";
import { convertTimestamp } from '$lib/utils'; import { convertTimestamp } from '$lib/utils';
@ -1062,6 +1064,10 @@ async function exportData(timePeriod:string) {
</div> </div>
{/if} {/if}
<div class="w-full mt-10 sm:mt-0 m-auto sm:pl-6 sm:pb-6 sm:pt-6 {Object?.keys(data?.getEarningsSurprise || {})?.length !== 0 ? '' : 'hidden'}">
<EarningsSurprise {data} />
</div>
<div class="w-full mt-10 sm:mt-0 m-auto sm:pl-6 sm:pb-6 sm:pt-6 {Object?.keys(data?.getNextEarnings || {})?.length !== 0 ? '' : 'hidden'}"> <div class="w-full mt-10 sm:mt-0 m-auto sm:pl-6 sm:pb-6 sm:pt-6 {Object?.keys(data?.getNextEarnings || {})?.length !== 0 ? '' : 'hidden'}">
<NextEarnings {data} /> <NextEarnings {data} />
</div> </div>