diff --git a/src/routes/api/indicator-data/+server.ts b/src/routes/api/indicator-data/+server.ts new file mode 100644 index 00000000..952ce430 --- /dev/null +++ b/src/routes/api/indicator-data/+server.ts @@ -0,0 +1,24 @@ +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ request, locals }) => { + const data = await request.json(); + const { apiURL, apiKey } = locals; + + const postData = { + ruleOfList: data?.ruleOfList, + tickerList: data?.tickerList, + }; + + const response = await fetch(apiURL + "/indicator-data", { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-API-KEY": apiKey, + }, + body: JSON.stringify(postData), + }); + + const output = await response?.json(); + + return new Response(JSON.stringify(output)); +}; diff --git a/src/routes/stock-screener/+page.svelte b/src/routes/stock-screener/+page.svelte index 91b92808..36ded9dc 100644 --- a/src/routes/stock-screener/+page.svelte +++ b/src/routes/stock-screener/+page.svelte @@ -689,6 +689,7 @@ $: { if (order) { const key = sortByKeys[sortBy]; // Use the mapping to get the key displayResults = sortItems(filteredData, key)?.slice(0, 50); + } } @@ -770,7 +771,6 @@ async function handleChangeValue(value) { - async function popularStrategy(state: string) { ruleOfList = []; const strategies = { diff --git a/src/routes/watchlist/stocks/+page.svelte b/src/routes/watchlist/stocks/+page.svelte index 9ad86ea6..7a4ee55a 100644 --- a/src/routes/watchlist/stocks/+page.svelte +++ b/src/routes/watchlist/stocks/+page.svelte @@ -5,38 +5,52 @@ import toast from 'svelte-french-toast'; import { onDestroy, onMount } from 'svelte'; import Input from '$lib/components/Input.svelte'; -import WatchListCard from '$lib/components/WatchListCard.svelte'; import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js"; import { Button } from "$lib/components/shadcn/button/index.js"; -import { writable } from 'svelte/store'; export let data; let searchQuery = ''; -let shouldLoadWorker = writable(false); let watchList: any[] = []; let news = []; -let indicatorList = ['Volume', 'Market Cap', 'Price', 'Change', 'EPS', 'PE']; -indicatorList = indicatorList.sort((a, b) => a.localeCompare(b)); +let allRows = [ + { name: 'Volume', rule: 'volume' }, + { name: 'Market Cap', rule: 'marketCap' }, + { name: 'Price', rule: 'price' }, + { name: 'Change', rule: 'changesPercentage' }, + { name: 'EPS', rule: 'eps' }, + { name: 'PE', rule: 'pe' }, + { name: 'AI Score', rule: 'score' }, + { name: 'Revenue', rule: 'revenue'}, + { name: 'Net Income', rule: 'netIncome'}, + { name: 'Free Cash Flow', rule: 'freeCashFlow'} +]; + +let ruleOfList = [ + { name: 'Volume', rule: 'volume' }, + { name: 'Market Cap', rule: 'marketCap' }, + { name: 'Price', rule: 'price' }, + { name: 'Change', rule: 'changesPercentage' }, +]; let isLoaded = false; -let downloadWorker: Worker | undefined; +//let downloadWorker: Worker | undefined; let displayWatchList; let allList = data?.getAllWatchlist; - +/* const handleDownloadMessage = (event) => { - stockScreenerData = event?.data?.stockScreenerData; - shouldLoadWorker.set(true); - + isLoaded = false; + watchList = event?.data?.watchlistData ?? []; + isLoaded = true; }; const updateStockScreenerData = async () => { - downloadWorker.postMessage({ indicatorList}); + downloadWorker.postMessage({ ruleOfList: ruleOfList, tickerList: watchList?.map(item => item?.symbol)}); }; - +*/ async function getWatchlistData() { @@ -53,9 +67,8 @@ async function getWatchlistData() const output = await response?.json(); -watchList = output?.at(0); -news = output[1]; - + watchList = output?.data; + news = output?.news; } async function createWatchList(event) { @@ -194,15 +207,16 @@ if(allList?.length !== 0) displayWatchList = ''; } await getWatchlistData(); - + /* if (!downloadWorker) { const DownloadWorker = await import('./workers/downloadWorker?worker'); downloadWorker = new DownloadWorker.default(); downloadWorker.onmessage = handleDownloadMessage; } + */ -isLoaded = true; - + checkedItems = new Set(ruleOfList.map(item => item.name)) + isLoaded = true; }); onDestroy( () => { @@ -225,9 +239,9 @@ function handleInput(event) { if (searchQuery.length > 0) { - const rawList = indicatorList + const rawList = allRows testList = rawList?.filter(item => { - const index = item?.toLowerCase(); + const index = item?.name?.toLowerCase(); // Check if country starts with searchQuery return index?.startsWith(searchQuery); }) || []; @@ -235,7 +249,7 @@ function handleInput(event) { }, 50); } -let checkedItems = new Set(indicatorList); +let checkedItems; function isChecked(item) { return checkedItems?.has(item); @@ -245,12 +259,27 @@ async function handleChangeValue(value) { if (checkedItems.has(value)) { checkedItems.delete(value); // Remove the value if it's already in the Set } else { - checkedItems?.add(value); // Add the value if it's not in the Set + checkedItems.add(value); // Add the value if it's not in the Set + // Update ruleOfList based on checked items from indicatorList } - - indicatorList = [...indicatorList] -} + ruleOfList = allRows.filter(item => checkedItems.has(item.name)); // Assuming each item has a `value` property + allRows = [...allRows]; + ruleOfList = [...ruleOfList]; + allRows = allRows + ?.filter(item => checkedItems.has(item.name) || !checkedItems.has(item.name)) + ?.sort((a, b) => { + const isAChecked = checkedItems.has(a.name); + const isBChecked = checkedItems.has(b.name); + + // Sort checked items first + if (isAChecked !== isBChecked) return isAChecked ? -1 : 1; + + // Sort alphabetically if both are checked or both are unchecked + return a.name.localeCompare(b.name); + }); + +} @@ -292,8 +321,7 @@ $: { - - +
@@ -309,7 +337,7 @@ $: { {#if isLoaded} -
+
@@ -349,7 +377,7 @@ $: {
Delete
- +
@@ -430,64 +468,60 @@ $: { Symbol Company - {#each indicatorList as item} - {#if isChecked(item)} - {item} - {/if} + + {#each ruleOfList as item} + {#if isChecked(item?.name)} + {item?.name} + {/if} {/each} {#each watchList as item} - - - {item?.symbol} + {item?.symbol} - {item?.name?.length > charNumber ? item?.name?.slice(0,charNumber) + "..." : item?.name} + {item?.name?.length > charNumber ? item?.name?.slice(0, charNumber) + "..." : item?.name} + {#each ruleOfList as row} + {#if isChecked(row?.name)} + + {#if item?.[row?.rule] !== undefined} + {#if ['marketCap', 'volume','revenue','netIncome','freeCashFlow'].includes(row?.rule)} + {abbreviateNumber(item[row?.rule])} + {:else if ['eps', 'pe', 'price','freeCashFlow'].includes(row?.rule)} + {item[row?.rule] !== null ? item[row?.rule]?.toFixed(2) : '-'} + {:else if ['changesPercentage'].includes(row?.rule)} + {#if item[row?.rule] >= 0} + +{item[row?.rule]?.toFixed(2)}% + {:else} + {item[row?.rule]?.toFixed(2)}% + {/if} + {:else if "score" === row?.rule} + {#if ['Strong Buy', 'Buy'].includes(item[row?.rule])} + {item[row?.rule]} + {:else if ['Strong Sell', 'Sell'].includes(item[row?.rule])} + {item[row?.rule]} + {:else if item[row?.rule] === 'Hold'} + {item[row?.rule]} + {/if} + {/if} - - {item?.eps !== null ? item?.eps?.toFixed(2) : '-'} - - - - {item?.pe !== null ? item?.pe?.toFixed(2) : '-'} - - - - {abbreviateNumber(item?.volume)} - - - - {abbreviateNumber(item?.marketCap)} - - - - - {item.price?.toFixed(2)} - - - - {#if item?.changesPercentage >=0} - +{item?.changesPercentage?.toFixed(2)}% - {:else} - {item?.changesPercentage?.toFixed(2)}% - {/if} - - + {:else} + - + {/if} + + + {/if} + {/each} - - - - - {/each} + {/each} diff --git a/src/routes/watchlist/stocks/workers/downloadWorker.ts b/src/routes/watchlist/stocks/workers/downloadWorker.ts index c4885396..3db58135 100644 --- a/src/routes/watchlist/stocks/workers/downloadWorker.ts +++ b/src/routes/watchlist/stocks/workers/downloadWorker.ts @@ -1,11 +1,11 @@ // Cache to store previous requests let cache = new Map(); -const getStockScreenerData = async (rules) => { +const getWatchlistData = async (rules, tickerList) => { console.log("Checking cache and fetching new data if needed"); // Extract the rule names - let getRuleOfList = rules?.map((rule) => rule.name) || []; + let getRuleOfList = rules?.map((rule) => rule.rule) || []; // Convert the rule set into a string key for the cache const ruleKey = JSON.stringify(getRuleOfList); @@ -17,8 +17,8 @@ const getStockScreenerData = async (rules) => { } // Fetch new data if it's not in the cache - const postData = { ruleOfList: getRuleOfList }; - const response = await fetch("/api/stock-screener-data", { + const postData = { ruleOfList: getRuleOfList, tickerList: tickerList }; + const response = await fetch("/api/indicator-data", { method: "POST", headers: { "Content-Type": "application/json", @@ -27,7 +27,7 @@ const getStockScreenerData = async (rules) => { }); const output = await response.json(); - + console.log(output); // Store the new data in the cache cache.set(ruleKey, output); @@ -35,23 +35,10 @@ const getStockScreenerData = async (rules) => { }; onmessage = async (event) => { - const { ruleOfList } = event.data || {}; + const { ruleOfList, tickerList } = event.data || {}; + const watchlistData = await getWatchlistData(ruleOfList, tickerList); - const output = await getStockScreenerData(ruleOfList); - - const stockScreenerData = output?.filter((item) => - Object?.values(item)?.every( - (value) => - value !== null && - value !== undefined && - (typeof value !== "object" || - Object.values(value)?.every( - (subValue) => subValue !== null && subValue !== undefined - )) - ) - ); - - postMessage({ message: "success", stockScreenerData }); + postMessage({ message: "success", watchlistData }); }; export {};