add earnings suprise
This commit is contained in:
parent
f1c2ec0a13
commit
23761e042c
81
src/lib/components/EarningsSurprise.svelte
Normal file
81
src/lib/components/EarningsSurprise.svelte
Normal 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}
|
||||||
@ -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),
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user