add analyst section to screener

This commit is contained in:
MuslemRahimi 2024-12-18 15:25:16 +01:00
parent f2f2319da6
commit a8ae4ed633
2 changed files with 142 additions and 18 deletions

View File

@ -1272,11 +1272,9 @@
defaultCondition: "over", defaultCondition: "over",
defaultValue: "any", defaultValue: "any",
}, },
analystRating: { analystRating: {
label: "Analyst Rating", label: "Analyst Rating",
step: ["Strong Buy", "Buy", "Hold", "Sell", "Strong Sell"], step: ["Strong Buy", "Buy", "Hold", "Sell", "Strong Sell"],
defaultCondition: "", defaultCondition: "",
defaultValue: "any", defaultValue: "any",
category: ["Most Popular", "Forecasts, Analysts & Price Targets"], category: ["Most Popular", "Forecasts, Analysts & Price Targets"],
@ -1537,13 +1535,16 @@
ruleName = ""; ruleName = "";
selectedPopularStrategy = ""; selectedPopularStrategy = "";
selectedStrategy = item?.id ?? ""; selectedStrategy = item?.id ?? "";
ruleOfList = ruleOfList =
strategyList?.find((item) => item.id === selectedStrategy)?.rules ?? []; strategyList?.find((item) => item.id === selectedStrategy)?.rules ?? [];
ruleOfList.forEach((rule) => { ruleOfList.forEach((rule) => {
ruleCondition[rule.name] = ruleCondition[rule.name] =
rule.condition || allRules[rule.name].defaultCondition; rule.condition || allRules[rule.name].defaultCondition;
valueMappings[rule.name] = rule.value || allRules[rule.name].defaultValue; valueMappings[rule.name] = rule.value || allRules[rule.name].defaultValue;
}); });
if (ruleOfList?.length === 0) { if (ruleOfList?.length === 0) {
filteredData = []; filteredData = [];
displayResults = []; displayResults = [];
@ -1564,6 +1565,7 @@
) // Only include specific rules ) // Only include specific rules
?.map((rule) => [rule.name, new Set(rule.value)]), // Create Map from filtered rules ?.map((rule) => [rule.name, new Set(rule.value)]), // Create Map from filtered rules
); );
} }
function changeRule(state: string) { function changeRule(state: string) {
@ -1591,7 +1593,7 @@
}; };
const loadWorker = async () => { const loadWorker = async () => {
if (displayTableTab === "performance" || hoverStatus) { if (['performance', 'analysts']?.includes(displayTableTab) || hoverStatus) {
syncWorker.postMessage({ syncWorker.postMessage({
stockScreenerData, stockScreenerData,
ruleOfList: [...ruleOfList, ...otherTabRules], ruleOfList: [...ruleOfList, ...otherTabRules],
@ -1605,7 +1607,7 @@
}; };
const updateStockScreenerData = async () => { const updateStockScreenerData = async () => {
if (displayTableTab === "performance" || hoverStatus) { if (["performance","analysts"]?.includes(displayTableTab) || hoverStatus) {
downloadWorker.postMessage({ downloadWorker.postMessage({
ruleOfList: [...ruleOfList, ...otherTabRules], ruleOfList: [...ruleOfList, ...otherTabRules],
}); });
@ -1814,7 +1816,7 @@ const handleKeyDown = (event) => {
} }
$: { $: {
if (ruleOfList) { if (ruleOfList ) {
const ruleToUpdate = ruleOfList?.find((rule) => rule.name === ruleName); const ruleToUpdate = ruleOfList?.find((rule) => rule.name === ruleName);
if (ruleToUpdate) { if (ruleToUpdate) {
ruleToUpdate.value = valueMappings[ruleToUpdate.name]; ruleToUpdate.value = valueMappings[ruleToUpdate.name];
@ -2226,6 +2228,11 @@ const handleKeyDown = (event) => {
{ key: "name", label: "Name", align: "left" }, { key: "name", label: "Name", align: "left" },
{ key: "marketCap", label: "Market Cap", align: "right" }, { key: "marketCap", label: "Market Cap", align: "right" },
], ],
analysts: [
{ key: "symbol", label: "Symbol", align: "left" },
{ key: "name", label: "Name", align: "left" },
{ key: "marketCap", label: "Market Cap", align: "right" },
],
filters: [ filters: [
{ key: "symbol", label: "Symbol", align: "left" }, { key: "symbol", label: "Symbol", align: "left" },
{ key: "name", label: "Name", align: "left" }, { key: "name", label: "Name", align: "left" },
@ -2239,6 +2246,11 @@ const handleKeyDown = (event) => {
name: { order: "none", type: "string" }, name: { order: "none", type: "string" },
marketCap: { order: "none", type: "number" }, marketCap: { order: "none", type: "number" },
}, },
analysts: {
symbol: { order: "none", type: "string" },
name: { order: "none", type: "string" },
marketCap: { order: "none", type: "number" },
},
filters: { filters: {
symbol: { order: "none", type: "string" }, symbol: { order: "none", type: "string" },
name: { order: "none", type: "string" }, name: { order: "none", type: "string" },
@ -2254,7 +2266,7 @@ const handleKeyDown = (event) => {
sortOrders = { ...(baseSortOrdersMap[displayTableTab] || {}) }; sortOrders = { ...(baseSortOrdersMap[displayTableTab] || {}) };
const rulesList = const rulesList =
displayTableTab === "performance" ? tabRuleList : displayRules; ["performance","analysts"]?.includes(displayTableTab) ? tabRuleList : displayRules;
rulesList?.forEach((rule) => { rulesList?.forEach((rule) => {
if (rule.rule !== "marketCap") { if (rule.rule !== "marketCap") {
columns.push({ columns.push({
@ -2288,9 +2300,23 @@ const handleKeyDown = (event) => {
.filter(Boolean); .filter(Boolean);
await updateStockScreenerData(); await updateStockScreenerData();
} } else if (displayTableTab === "analysts") {
} hoverStatus = false;
otherTabRules = [
{ name: "marketCap", value: "any" },
{ name: "analystRating", value: "any" },
{ name: "analystCounter", value: "any" },
{ name: "priceTarget", value: "any" },
{ name: "upside", value: "any" },
];
tabRuleList = otherTabRules
.map((rule) => allRows.find((row) => row.rule === rule.name))
.filter(Boolean);
await updateStockScreenerData();
}
}
/*
async function handleMouseOver() { async function handleMouseOver() {
if (displayTableTab !== "performance") { if (displayTableTab !== "performance") {
hoverStatus = true; hoverStatus = true;
@ -2305,9 +2331,24 @@ const handleKeyDown = (event) => {
?.map((rule) => allRows.find((row) => row.rule === rule.name)) ?.map((rule) => allRows.find((row) => row.rule === rule.name))
?.filter(Boolean); ?.filter(Boolean);
await updateStockScreenerData();
} else if (displayTableTab !== "analysts") {
hoverStatus = true;
otherTabRules = [
{ name: "marketCap", value: "any" },
{ name: "analystRating", value: "any" },
{ name: "analystCounter", value: "any" },
{ name: "priceTarget", value: "any" },
{ name: "upside", value: "any" },
];
tabRuleList = otherTabRules
?.map((rule) => allRows.find((row) => row.rule === rule.name))
?.filter(Boolean);
await updateStockScreenerData(); await updateStockScreenerData();
} }
} }
*/
</script> </script>
<svelte:head> <svelte:head>
@ -3058,7 +3099,7 @@ const handleKeyDown = (event) => {
> >
<nav class="w-full flex flex-row items-center py-2.5 sm:py-3 lg:py-1"> <nav class="w-full flex flex-row items-center py-2.5 sm:py-3 lg:py-1">
<ul <ul
class="flex flex-row items-center space-x-2 whitespace-nowrap text-base" class="flex flex-row overflow-x-auto items-center space-x-2 whitespace-nowrap text-base"
> >
<li> <li>
<button <button
@ -3093,7 +3134,7 @@ const handleKeyDown = (event) => {
</li> </li>
<li> <li>
<button <button
on:mouseover={handleMouseOver}
on:click={() => changeTab("performance")} on:click={() => changeTab("performance")}
class="text-[1rem] sm:text-lg block text-white rounded-md px-2 py-1 focus:outline-none sm:hover:bg-primary {displayTableTab === class="text-[1rem] sm:text-lg block text-white rounded-md px-2 py-1 focus:outline-none sm:hover:bg-primary {displayTableTab ===
'performance' 'performance'
@ -3103,6 +3144,17 @@ const handleKeyDown = (event) => {
Performance Performance
</button> </button>
</li> </li>
<li>
<button
on:click={() => changeTab("analysts")}
class="text-[1rem] sm:text-lg block text-white rounded-md px-2 py-1 focus:outline-none sm:hover:bg-primary {displayTableTab ===
'analysts'
? 'font-semibold bg-primary'
: ''}"
>
Analysts
</button>
</li>
</ul> </ul>
<div class="w-fit ml-auto hidden sm:inline-block"> <div class="w-fit ml-auto hidden sm:inline-block">
<DownloadData <DownloadData
@ -3206,9 +3258,7 @@ const handleKeyDown = (event) => {
<tbody> <tbody>
{#each displayResults as item (item?.symbol)} {#each displayResults as item (item?.symbol)}
<tr <tr
on:click={() => {
handleSave(false);
}}
class="sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] bg-[#09090B] border-b-[#09090B] odd:bg-secondary" class="sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] bg-[#09090B] border-b-[#09090B] odd:bg-secondary"
> >
<td class="border-b-[#09090B] whitespace-nowrap"> <td class="border-b-[#09090B] whitespace-nowrap">
@ -3259,9 +3309,7 @@ const handleKeyDown = (event) => {
<tbody> <tbody>
{#each displayResults as item (item?.symbol)} {#each displayResults as item (item?.symbol)}
<tr <tr
on:click={() => {
handleSave(false);
}}
class="sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] bg-[#09090B] border-b-[#09090B] odd:bg-secondary" class="sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] bg-[#09090B] border-b-[#09090B] odd:bg-secondary"
> >
<td class="border-b-[#09090B] whitespace-nowrap"> <td class="border-b-[#09090B] whitespace-nowrap">
@ -3307,6 +3355,82 @@ const handleKeyDown = (event) => {
</tbody> </tbody>
</table> </table>
</div> </div>
{:else if displayTableTab === "analysts"}
<div class="w-full rounded-md overflow-x-scroll">
<table
class="table table-sm table-compact w-full bg-[#09090B] border-bg-[#09090B]"
>
<thead>
<TableHeader {columns} {sortOrders} {sortData} />
</thead>
<tbody>
{#each displayResults as item (item?.symbol)}
<tr
class="sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] bg-[#09090B] border-b-[#09090B] odd:bg-secondary"
>
<td class="border-b-[#09090B] whitespace-nowrap">
<a
href={"/stocks/" + item?.symbol}
class="sm:hover:text-white text-blue-400 text-sm sm:text-[1rem]"
>{item?.symbol}</a
>
</td>
<td
class="whitespace-nowrap text-[1rem] text-white border-b-[#09090B]"
>
{item?.name?.length > charNumber
? item?.name?.slice(0, charNumber) + "..."
: item?.name}
</td>
{#each tabRuleList as row (row?.rule)}
<td
class="whitespace-nowrap text-sm sm:text-[1rem] text-end text-white border-b-[#09090B]"
>
{#if row?.rule === "marketCap"}
{abbreviateNumber(item[row?.rule])}
{:else if ['analystCounter','priceTarget']?.includes(row?.rule)}
<span class="text-white"
>{abbreviateNumber(
item[row?.rule],
)}</span
>
{:else if row?.rule === 'upside'}
{#if item[row?.rule] > 0}
<span class="text-[#00FC50]"
>+{item[row?.rule]?.toFixed(2)}%</span
>
{:else if item[row?.rule] < 0}
<span class="text-[#FF2F1F]"
>{item[row?.rule]?.toFixed(2)}%</span
>
{:else}
<span class="text-[#fff]"
>n/a</span
>
{/if}
{:else if row?.rule === 'analystRating'}
{#if ["Strong Buy", "Buy"].includes(item[row?.rule])}
<span class="text-[#00FC50]">{item[row?.rule]}</span>
{:else if ["Strong Sell", "Sell"].includes(item[row?.rule])}
<span class="text-[#FF2F1F]">{item[row?.rule]}</span>
{:else if item[row?.rule] === "Hold"}
<span class="text-[#FFA838]">{item[row?.rule]}</span>
{:else}
-
{/if}
{/if}
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
</div>
{/if} {/if}
{:else} {:else}
<div <div

View File

@ -1133,7 +1133,7 @@
class="flex flex-col border-b border-gray-600 py-1 sm:table-row sm:py-0" class="flex flex-col border-b border-gray-600 py-1 sm:table-row sm:py-0"
><td ><td
class="whitespace-nowrap px-0.5 py-[1px] xs:px-1 sm:py-2 text-[1rem]" class="whitespace-nowrap px-0.5 py-[1px] xs:px-1 sm:py-2 text-[1rem]"
>Market Cap</td ><a href={`/stocks/${$stockTicker}/statistics/market-cap`} class="sm:hover:text-blue-400 text-white underline underline-offset-4">Market Cap</a></td
> >
<td <td
class="whitespace-nowrap px-0.5 py-[1px] text-left text-sm font-semibold xs:px-1 sm:py-2 sm:text-right sm:text-[1rem]" class="whitespace-nowrap px-0.5 py-[1px] text-left text-sm font-semibold xs:px-1 sm:py-2 sm:text-right sm:text-[1rem]"