diff --git a/src/lib/components/EarningsSurprise.svelte b/src/lib/components/EarningsSurprise.svelte new file mode 100644 index 00000000..d086d098 --- /dev/null +++ b/src/lib/components/EarningsSurprise.svelte @@ -0,0 +1,81 @@ + + + +{#if Object?.keys(rawData)?.length !== 0} + +
+ +
+ +
+
+ +
+ + +
+ +
+
+ + {#if data?.user?.tier === 'Pro'} +
+
{$displayCompanyName} has released their quartely earnings on {new Date(rawData?.date)?.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', daySuffix: '2-digit' })}:
+ +
  • + 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'}. +
  • +
  • + 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'}. +
  • +
    + {:else} +
    + + Unlock content with Pro Subscription +
    + {/if} + +
    + + +
    +{/if} \ No newline at end of file diff --git a/src/routes/stocks/[tickerID]/+layout.server.ts b/src/routes/stocks/[tickerID]/+layout.server.ts index aad524b9..2cb97f2c 100644 --- a/src/routes/stocks/[tickerID]/+layout.server.ts +++ b/src/routes/stocks/[tickerID]/+layout.server.ts @@ -1,30 +1,40 @@ const cleanString = (input) => { const substringsToRemove = [ - 'Depositary', 'Inc.', 'Incorporated', 'Holdings', 'Corporation', 'Corporations', - 'LLC', 'Holdings plc American Depositary Shares', 'Holding Corporation', 'Oyj', - 'Company', 'The', 'plc', + "Depositary", + "Inc.", + "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'); - return input?.replace(pattern, '').trim(); + const pattern = new RegExp(`\\b(${substringsToRemove.join("|")})\\b|,`, "gi"); + return input?.replace(pattern, "").trim(); }; const fetchData = async (apiURL, apiKey, endpoint, ticker) => { const response = await fetch(`${apiURL}${endpoint}`, { - method: 'POST', + method: "POST", headers: { "Content-Type": "application/json", - "X-API-KEY": apiKey + "X-API-KEY": apiKey, }, - body: JSON.stringify({ ticker }) + body: JSON.stringify({ ticker }), }); return response.json(); }; const fetchFromFastify = async (fastifyURL, endpoint, userId) => { const response = await fetch(`${fastifyURL}${endpoint}`, { - method: 'POST', + method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ userId }) + body: JSON.stringify({ userId }), }); const { items } = await response.json(); return items; @@ -32,16 +42,18 @@ const fetchFromFastify = async (fastifyURL, endpoint, userId) => { const fetchCommunitySentiment = async (pb, ticker, cookies) => { const cookieVote = cookies.get(`community-sentiment-${ticker}`); - const today = new Date().toISOString().split('T')[0]; - const tomorrow = new Date(new Date().setDate(new Date().getDate() + 1)).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 output = await pb.collection("sentiment").getFullList({ - filter: `ticker="${ticker}" && created >= "${today}" && created < "${tomorrow}"` + filter: `ticker="${ticker}" && created >= "${today}" && created < "${tomorrow}"`, }); return { 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 endpoints = [ - '/similar-stocks', '/stockdeck', '/analyst-summary-rating', '/stock-quote', - '/bull-bear-say','/wiim', '/top-etf-ticker-holder', '/one-day-price','/next-earnings' + "/similar-stocks", + "/stockdeck", + "/analyst-summary-rating", + "/stock-quote", + "/bull-bear-say", + "/wiim", + "/top-etf-ticker-holder", + "/one-day-price", + "/next-earnings", + "/earnings-surprise", ]; const promises = [ - ...endpoints.map(endpoint => fetchData(apiURL, apiKey, endpoint, tickerID)), - fetchFromFastify(fastifyURL, '/all-watchlists', user?.id), + ...endpoints.map((endpoint) => + fetchData(apiURL, apiKey, endpoint, tickerID) + ), + fetchFromFastify(fastifyURL, "/all-watchlists", user?.id), //fetchFromFastify(fastifyURL, '/get-portfolio-data', user?.id), - fetchCommunitySentiment(pb, tickerID, cookies) + fetchCommunitySentiment(pb, tickerID, cookies), ]; const [ - getSimilarStock, getStockDeck, getAnalystRating, getStockQuote, - getBullBearSay, getWhyPriceMoved, getTopETFHolder, getOneDayPrice, getNextEarnings, - getUserWatchlist, getCommunitySentiment + getSimilarStock, + getStockDeck, + getAnalystRating, + getStockQuote, + getBullBearSay, + getWhyPriceMoved, + getTopETFHolder, + getOneDayPrice, + getNextEarnings, + getEarningsSurprise, + getUserWatchlist, + getCommunitySentiment, ] = await Promise.all(promises); - setHeaders({ 'cache-control': 'public, max-age=300' }); + setHeaders({ "cache-control": "public, max-age=300" }); return { getSimilarStock, @@ -79,8 +110,9 @@ export const load = async ({ params, locals, cookies, setHeaders }) => { getTopETFHolder, getOneDayPrice, getNextEarnings, + getEarningsSurprise, getUserWatchlist, getCommunitySentiment, companyName: cleanString(getStockDeck?.at(0)?.companyName), }; -}; \ No newline at end of file +}; diff --git a/src/routes/stocks/[tickerID]/+page.svelte b/src/routes/stocks/[tickerID]/+page.svelte index 4c0a3cc1..b7bd6dcf 100644 --- a/src/routes/stocks/[tickerID]/+page.svelte +++ b/src/routes/stocks/[tickerID]/+page.svelte @@ -37,6 +37,8 @@ import { onDestroy } from "svelte"; import BullBearSay from "$lib/components/BullBearSay.svelte"; import NextEarnings from "$lib/components/NextEarnings.svelte"; + import EarningsSurprise from "$lib/components/EarningsSurprise.svelte"; + import CommunitySentiment from "$lib/components/CommunitySentiment.svelte"; import Lazy from "$lib/components/Lazy.svelte"; import { convertTimestamp } from '$lib/utils'; @@ -1062,6 +1064,10 @@ async function exportData(timePeriod:string) { {/if} +
    + +
    +