374 lines
19 KiB
Svelte
374 lines
19 KiB
Svelte
<script lang="ts">
|
|
|
|
import {numberOfUnreadNotification, displayCompanyName, stockTicker, currentPortfolioPrice} from '$lib/store';
|
|
import InfoModal from '$lib/components/InfoModal.svelte';
|
|
import InfiniteLoading from '$lib/components/InfiniteLoading.svelte';
|
|
import { goto } from '$app/navigation';
|
|
import UpgradeToPro from '$lib/components/UpgradeToPro.svelte';
|
|
|
|
export let data;
|
|
|
|
let analystRating = data?.getAnalystRating ?? {};
|
|
|
|
let rawData = [];
|
|
let historyList = []
|
|
let priceTarget = 'n/a';
|
|
let numOfAnalyst = 0;
|
|
let consensusRating = 'n/a';
|
|
let changesPercentage = 0;
|
|
|
|
const tabs = [
|
|
{
|
|
title: "All Analysts",
|
|
},
|
|
{
|
|
title: "Top Analysts",
|
|
}
|
|
];
|
|
|
|
let activeIdx = 0;
|
|
|
|
function changeTab(index) {
|
|
activeIdx = index;
|
|
|
|
if(Object?.keys(analystRating)?.length !== 0) {
|
|
|
|
numOfAnalyst = analystRating?.numOfAnalyst;
|
|
priceTarget = analystRating?.priceTarget
|
|
consensusRating = analystRating?.consensusRating;
|
|
changesPercentage = ((priceTarget/$currentPortfolioPrice -1)*100)?.toFixed(2) ?? 0;
|
|
|
|
}
|
|
if (activeIdx === 1) {
|
|
rawData = data?.getAnalystTickerHistory?.filter(item => item?.analystScore >=4)
|
|
historyList = rawData?.slice(0,10)
|
|
}
|
|
else {
|
|
rawData = data?.getAnalystTickerHistory ?? [];
|
|
historyList = rawData?.slice(0,10)
|
|
}
|
|
}
|
|
|
|
async function infiniteHandler({ detail: { loaded, complete } })
|
|
{
|
|
if (historyList?.length === rawData?.length) {
|
|
complete();
|
|
} else {
|
|
const nextIndex = historyList?.length;
|
|
const newArticles = rawData?.slice(nextIndex, nextIndex + 5);
|
|
historyList = [...historyList, ...newArticles];
|
|
loaded();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
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 <=4;
|
|
}
|
|
|
|
|
|
changeTab(0)
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<svelte:head>
|
|
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width" />
|
|
<title>
|
|
{$numberOfUnreadNotification > 0 ? `(${$numberOfUnreadNotification})` : ''} {$displayCompanyName} ({$stockTicker}) Analyst Ratings · stocknear
|
|
</title>
|
|
<meta name="description" content={`A list of analyst ratings for Advanced Micro Devices (AMD) stock. See upgrades, downgrades, price targets and more from top Wall Street stock analysts.`} />
|
|
|
|
<!-- Other meta tags -->
|
|
<meta property="og:title" content={`${$displayCompanyName} (${$stockTicker}) Analyst Ratings · stocknear`}/>
|
|
<meta property="og:description" content={`A list of analyst ratings for Advanced Micro Devices (AMD) stock. See upgrades, downgrades, price targets and more from top Wall Street stock analysts.`} />
|
|
<meta property="og:type" content="website"/>
|
|
<!-- Add more Open Graph meta tags as needed -->
|
|
|
|
<!-- Twitter specific meta tags -->
|
|
<meta name="twitter:card" content="summary_large_image"/>
|
|
<meta name="twitter:title" content={`${$displayCompanyName} (${$stockTicker}) Analyst Ratings · stocknear`}/>
|
|
<meta name="twitter:description" content={`A list of analyst ratings for Advanced Micro Devices (AMD) stock. See upgrades, downgrades, price targets and more from top Wall Street stock analysts.`} />
|
|
<!-- Add more Twitter meta tags as needed -->
|
|
|
|
</svelte:head>
|
|
|
|
|
|
|
|
|
|
|
|
<section class="bg-[#09090B] overflow-hidden text-white h-full mb-40 sm:mb-0 w-full">
|
|
<div class="flex justify-center m-auto h-full overflow-hidden w-full">
|
|
<div class="relative flex justify-center items-center overflow-hidden w-full">
|
|
<div class="sm:p-7 w-full m-auto mt-2 sm:mt-0">
|
|
<div class="mb-6">
|
|
<h2 class="text-2xl sm:text-3xl text-gray-200 font-bold mb-4">
|
|
Analyst Ratings
|
|
</h2>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{#if historyList?.length !== 0}
|
|
|
|
<div class="stats stats-horizontal bg-[#272727] w-full rounded-lg pr-6 sm:pr-0">
|
|
|
|
<div class="grid grid-cols-2">
|
|
|
|
<div class="stat">
|
|
<div class="flex flex-row items-center">
|
|
<label for="totalAnalystInfo" class="cursor-pointer stat-title text-md sm:text-lg font-medium text-gray-300">
|
|
Total Analyst
|
|
</label>
|
|
<InfoModal
|
|
title={"Total Analyst"}
|
|
content={"The total number of analyst who provided a rating in the past 12 months."}
|
|
id={"totalAnalystInfo"}
|
|
/>
|
|
</div>
|
|
<div class="stat-value mt-1 text-lg text-gray-200 font-semibold">
|
|
{numOfAnalyst}
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="stat">
|
|
<div class="flex flex-row items-center">
|
|
<label for="consensusRatingInfo" class="cursor-pointer stat-title text-md sm:text-lg font-medium text-gray-300">
|
|
Consensus Rating
|
|
</label>
|
|
<InfoModal
|
|
title={"Consensus Rating"}
|
|
content={`The average analyst rating for ${$stockTicker} is standardized to align with categories: Strong Buy, Buy, Hold, Sell, and Strong Sell.`}
|
|
id={"consensusRatingInfo"}
|
|
/>
|
|
</div>
|
|
|
|
<div class="stat-value font-semibold {['Strong Buy', 'Buy']?.includes(consensusRating) ? 'text-[#10DB06]' : consensusRating === 'Hold' ? 'text-[#FF7070]' : ['Strong Sell','Sell']?.includes(consensusRating) ? 'text-[#FF2F1F]' : 'text-gray-200'} mt-1 text-lg">
|
|
{consensusRating}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat">
|
|
<div class="flex flex-row items-center">
|
|
<label for="priceTargetInfo" class="cursor-pointer stat-title text-md sm:text-lg font-medium text-gray-300">
|
|
Price Target
|
|
</label>
|
|
<InfoModal
|
|
title={"Price Target"}
|
|
content={"The average 12-month price target"}
|
|
id={"priceTargetInfo"}
|
|
/>
|
|
</div>
|
|
<div class="stat-value font-semibold text-lg text-gray-200">
|
|
${priceTarget}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat">
|
|
<div class="flex flex-row items-center">
|
|
<label for="upsideInfo" class="cursor-pointer stat-title text-md sm:text-lg font-medium text-gray-300">
|
|
Upside
|
|
</label>
|
|
<InfoModal
|
|
title={"Upside"}
|
|
content={"The average price target's percentage difference from the current stock price."}
|
|
id={"upsideInfo"}
|
|
/>
|
|
</div>
|
|
<div class="stat-value font-semibold text-lg text-gray-200">
|
|
<div class="flex flex-row items-center">
|
|
{#if changesPercentage >=0}
|
|
<span class="text-[#10DB06]">+{changesPercentage}%</span>
|
|
{:else}
|
|
<span class="text-[#FF2F1F]">{changesPercentage}% </span>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<h3 class="mt-10 text-2xl sm:text-3xl text-gray-200 font-bold mb-4 text-center sm:text-start">
|
|
Ratings History
|
|
</h3>
|
|
|
|
|
|
|
|
|
|
<div class="mt-5 text-white p-3 sm:p-5 mb-5 rounded-lg sm:flex sm:flex-row sm:items-center border border-slate-800 text-sm sm:text-[1rem]">
|
|
<svg class="w-6 h-6 flex-shrink-0 inline-block sm:mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill='#a474f6' d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24m-4 48a12 12 0 1 1-12 12a12 12 0 0 1 12-12m12 112a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 0 16"/></svg>
|
|
In previous years, Wall Street analysts have given {$displayCompanyName} a total of {data?.getAnalystTickerHistory?.length} ratings.
|
|
</div>
|
|
|
|
|
|
<div class="flex flex-col items-end w-full">
|
|
<InfoModal
|
|
title={"Top Analysts"}
|
|
content={"Filter for analysts rated 4+ stars focusing on their win rate and average return per rating. Analysts with 4+ stars typically exhibit both high accuracy and high return per rating."}
|
|
id={"topAnalystsInfo"}
|
|
/>
|
|
|
|
<div class="ml-auto">
|
|
<div class="bg-[#313131] w-fit relative flex flex-wrap items-center justify-center rounded-lg p-1 mt-4">
|
|
{#each tabs as item, i}
|
|
<button
|
|
on:click={() => changeTab(i)}
|
|
class="group relative z-[1] rounded-full px-6 py-2 {activeIdx === i
|
|
? 'z-0'
|
|
: ''} "
|
|
>
|
|
{#if activeIdx === i}
|
|
<div
|
|
class="absolute inset-0 rounded-lg bg-purple-600"
|
|
></div>
|
|
{/if}
|
|
<span
|
|
class="relative text-sm block font-medium duration-200 text-white">
|
|
{item.title}
|
|
</span>
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sm:w-full m-auto mt-10 ">
|
|
|
|
|
|
<div class="w-full m-auto rounded-none sm:rounded-lg mb-4 overflow-x-scroll sm:overflow-hidden">
|
|
<table class="table table-sm table-compact no-scrollbar rounded-none sm:rounded-md w-full bg-[#09090B] border-bg-[#09090B] m-auto">
|
|
<thead class="">
|
|
<tr class="">
|
|
<td class="text-white font-semibold text-[1rem] text-start">Analyst</td>
|
|
<td class="text-white font-semibold text-[1rem] text-start">Rating</td>
|
|
<td class="text-white font-semibold text-[1rem] text-end">Date</td>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{#each (data?.user?.tier === 'Pro' ? historyList : historyList?.slice(0,3)) as item,index}
|
|
<tr on:click={() => goto(`/analysts/${item?.analystId}`)} class="cursor-pointer {latestInfoDate(item?.date) ? 'bg-[#F9AB00] bg-opacity-[0.1]' : 'odd:bg-[#27272A]'} border-b-[#09090B] {index+1 === historyList?.slice(0,3)?.length && data?.user?.tier !== 'Pro' ? 'opacity-[0.1]' : ''}">
|
|
<td class="text-sm sm:text-[1rem] whitespace-nowrap text-start">
|
|
<div class="flex flex-col items-start">
|
|
<span class="text-blue-400">{item?.analyst_name} </span>
|
|
<span class="text-white">{item?.analyst?.length > 15 ? item?.analyst?.slice(0,15) + '...' : item?.analyst}</span>
|
|
|
|
<div class="flex flex-row items-center mt-1">
|
|
{#each Array.from({ length: 5 }) as _, i}
|
|
{#if i < Math.floor(item?.analystScore)}
|
|
<svg class="w-3 h-3 text-yellow-300" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 22 20">
|
|
<path d="M20.924 7.625a1.523 1.523 0 0 0-1.238-1.044l-5.051-.734-2.259-4.577a1.534 1.534 0 0 0-2.752 0L7.365 5.847l-5.051.734A1.535 1.535 0 0 0 1.463 9.2l3.656 3.563-.863 5.031a1.532 1.532 0 0 0 2.226 1.616L11 17.033l4.518 2.375a1.534 1.534 0 0 0 2.226-1.617l-.863-5.03L20.537 9.2a1.523 1.523 0 0 0 .387-1.575Z"/>
|
|
</svg>
|
|
{:else}
|
|
<svg class="w-3 h-3 text-gray-300 dark:text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 22 20">
|
|
<path d="M20.924 7.625a1.523 1.523 0 0 0-1.238-1.044l-5.051-.734-2.259-4.577a1.534 1.534 0 0 0-2.752 0L7.365 5.847l-5.051.734A1.535 1.535 0 0 0 1.463 9.2l3.656 3.563-.863 5.031a1.532 1.532 0 0 0 2.226 1.616L11 17.033l4.518 2.375a1.534 1.534 0 0 0 2.226-1.617l-.863-5.03L20.537 9.2a1.523 1.523 0 0 0 .387-1.575Z"/>
|
|
</svg>
|
|
{/if}
|
|
{/each}
|
|
|
|
<span class="ml-1 text-sm">
|
|
({item?.analystScore !== null ? item?.analystScore : 0})
|
|
</span>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</td>
|
|
|
|
<td class="text-sm sm:text-[1rem] whitespace-nowrap text-center text-white">
|
|
<div class="flex flex-col items-start">
|
|
<span class="text-[1rem] font-medium {['Strong Buy', 'Buy']?.includes(item?.rating_current) ? 'text-[#10DB06]' : item?.rating_current === 'Hold' ? 'text-[#FF7070]' : ['Strong Sell','Sell']?.includes(item?.rating_current) ? 'text-[#FF2F1F]' : ''}">
|
|
{item?.rating_current}
|
|
</span>
|
|
<span class="font-medium text-white">{item?.action_company?.replace('Initiates Coverage On','Initiates')}</span>
|
|
<div class="flex flex-row items-center">
|
|
{#if Math?.ceil(item?.adjusted_pt_prior) !== 0}
|
|
<span class="text-gray-100 font-normal">${Math?.ceil(item?.adjusted_pt_prior)}</span>
|
|
<svg class="w-3 h-3 ml-1 mr-1 inline-block" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 12h16m0 0l-6-6m6 6l-6 6"/></svg>
|
|
<span class="text-white font-semibold">${Math?.ceil(item?.adjusted_pt_current)}</span>
|
|
{:else if Math?.ceil(item?.adjusted_pt_current) !== 0}
|
|
<span class="text-white font-semibold">${Math?.ceil(item?.adjusted_pt_current)}</span>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
|
|
<td class="text-white text-end font-medium text-sm sm:text-[1rem] whitespace-nowrap">
|
|
<div class="flex flex-col items-end">
|
|
{#if latestInfoDate(item?.date)}
|
|
<label class="bg-[#2D4F8A] text-white font-medium text-xs rounded-lg px-2 py-0.5 ml-3 mb-1">
|
|
New
|
|
</label>
|
|
{/if}
|
|
{new Date(item?.date).toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', daySuffix: '2-digit' })}
|
|
</div>
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
|
|
{/each}
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
</div>
|
|
</div>
|
|
{#if data?.user?.tier === 'Pro'}
|
|
<InfiniteLoading on:infinite={infiniteHandler} />
|
|
{/if}
|
|
|
|
<UpgradeToPro data={data} title="Get stock forecasts from Wall Street's highest rated professionals"/>
|
|
|
|
{:else}
|
|
<div class="w-full flex justify-start items-center m-auto mt-10 mb-6">
|
|
<div class="text-center w-fit text-gray-100 text-sm sm:text-[1rem] rounded-lg h-auto border border-slate-800 p-4">
|
|
<svg class="w-5 h-5 inline-block sm:mr-1 flex-shrink-0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#a474f6" d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24m-4 48a12 12 0 1 1-12 12a12 12 0 0 1 12-12m12 112a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 0 16"/></svg>
|
|
Wall Street analysts have not provided any ratings for {$displayCompanyName}
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|