From a6b5d56a667447c3996614b539c940e858b35f97 Mon Sep 17 00:00:00 2001 From: MuslemRahimi Date: Tue, 10 Sep 2024 10:23:42 +0200 Subject: [PATCH] update sma/ema rules --- .../stock-screener/[strategyId]/+page.svelte | 56 +++++++++--- .../[strategyId]/workers/filterWorker.ts | 86 ++++++++++++------- 2 files changed, 99 insertions(+), 43 deletions(-) diff --git a/src/routes/stock-screener/[strategyId]/+page.svelte b/src/routes/stock-screener/[strategyId]/+page.svelte index a6404494..315b40e6 100644 --- a/src/routes/stock-screener/[strategyId]/+page.svelte +++ b/src/routes/stock-screener/[strategyId]/+page.svelte @@ -43,14 +43,14 @@ const allRules = { mfi: { label: 'MFI', step: [90,80,70,60,50,40,30,20], category: 'ta', defaultCondition: 'over', defaultValue: 40 }, cci: { label: 'CCI', step: [250,200,100,50,20,0,-20,-50,-100,-200,-250], category: 'ta', defaultCondition: 'over', defaultValue: 0 }, atr: { label: 'ATR', step: [20,15,10,5,3,1], category: 'ta', defaultCondition: 'over', defaultValue: 10 }, - sma20: { label: 'SMA-20', step: [500,250,100,50,10,1], category: 'ta', defaultCondition: 'over', defaultValue: 10 }, - sma50: { label: 'SMA-50', step: [500,250,100,50,10,1], category: 'ta', defaultCondition: 'over', defaultValue: 10 }, - sma100: { label: 'SMA-100', step: [500,250,100,50,10,1], category: 'ta', defaultCondition: 'over', defaultValue: 10 }, - sma200: { label: 'SMA-200', step: [500,250,100,50,10,1], category: 'ta', defaultCondition: 'over', defaultValue: 10 }, - ema20: { label: 'EMA-20', step: [500,250,100,50,10,1], category: 'ta', defaultCondition: 'over', defaultValue: 10 }, - ema50: { label: 'EMA-50', step: [500,250,100,50,10,1], category: 'ta', defaultCondition: 'over', defaultValue: 10 }, - ema100: { label: 'EMA-100', step: [500,250,100,50,10,1], category: 'ta', defaultCondition: 'over', defaultValue: 10 }, - ema200: { label: 'EMA-200', step: [500,250,100,50,10,1], category: 'ta', defaultCondition: 'over', defaultValue: 10 }, + sma20: { label: 'SMA20', step: ['Stock Price'], category: 'ta', defaultCondition: 'over', defaultValue: 'Stock Price' }, + sma50: { label: 'SMA50', step: ['Stock Price'], category: 'ta', defaultCondition: 'over', defaultValue: 'Stock Price' }, + sma100: { label: 'SMA100', step: ['Stock Price'], category: 'ta', defaultCondition: 'over', defaultValue: 'Stock Price' }, + sma200: { label: 'SMA200', step: ['Stock Price'], category: 'ta', defaultCondition: 'over', defaultValue: 'Stock Price' }, + ema20: { label: 'EMA20', step: ['Stock Price > EMA20', 'EMA20 > EMA50'], category: 'ta', defaultValue: 'any' }, + ema50: { label: 'EMA50', step: ['Stock Price'], category: 'ta', defaultCondition: 'over', defaultValue: 'Stock Price' }, + ema100: { label: 'EMA100', step: ['Stock Price'], category: 'ta', defaultCondition: 'over', defaultValue: 'Stock Price' }, + ema200: { label: 'EMA200', step: ['Stock Price'], category: 'ta', defaultCondition: 'over', defaultValue: 'Stock Price' }, price: { label: 'Stock Price', step: [1000,500,400,300,200,150,100,80,60,50,20,10,1], category: 'fund', defaultCondition: 'over', defaultValue: 10 }, change1W: { label: 'Price Change 1W', step: ['20%','10%','5%','1%','-1%','-5%','-10%','-20%'], category: 'ta', defaultCondition: 'over', defaultValue: '1%' }, @@ -160,7 +160,15 @@ const allRules = { const getStockScreenerData = async (rules) => { console.log('Fetching new data from API'); - const postData = { ruleOfList: rules?.map(rule => rule.name) }; + // Extract the rule names + let getRuleOfList = rules?.map(rule => rule.name) || []; + + // If 'ema20' is included, ensure 'ema50' is also added + if (getRuleOfList?.includes("ema20") && !getRuleOfList?.includes("ema50")) { + getRuleOfList.push("ema50"); + } + console.log(getRuleOfList) + const postData = { ruleOfList: getRuleOfList }; const response = await fetch(data?.apiURL + '/stock-screener-data', { method: 'POST', headers: { @@ -263,6 +271,7 @@ function handleAddRule() { case 'analystRating': case 'sector': case 'country': + case 'ema20': newRule = { name: ruleName, value: Array.isArray(valueMappings[ruleName]) ? valueMappings[ruleName] : [valueMappings[ruleName]] }; // Ensure value is an array break; default: @@ -520,7 +529,7 @@ function changeRuleCondition(name: string, state: string) { async function handleChangeValue(value) { // Check if the current rule is "country" - if (['analystRating','sector','country']?.includes(ruleName)) { + if (['ema20', 'analystRating','sector','country']?.includes(ruleName)) { // Ensure valueMappings[ruleName] is initialized as an array searchQuery = ''; if (!Array.isArray(valueMappings[ruleName])) { @@ -846,7 +855,7 @@ function handleInput(event) { - {#if !['analystRating','sector','country']?.includes(row?.rule)} + {#if !['ema20', 'analystRating','sector','country']?.includes(row?.rule)}
@@ -876,16 +885,33 @@ function handleInput(event) {
{/if} - {#if !['analystRating','sector','country']?.includes(row?.rule)} + {#if !['ema20','analystRating','sector','country']?.includes(row?.rule)} {#each row?.step as newValue} - {/each} + {:else if ['ema20']?.includes(row?.rule)} + {#each row?.step as item} + +
event.preventDefault()}> + +
+
+ {/each} {:else} {#each (testList.length > 0 && searchQuery?.length > 0 ? testList : searchQuery?.length > 0 && testList?.length === 0 ? [] : (row?.rule === 'country' ? listOfRelevantCountries : row?.rule === 'sector' ? sectorList : ['Buy','Hold','Sell'])) as item} @@ -1142,6 +1168,8 @@ function handleInput(event) { {item?.sector} {:else if row?.rule === 'country'} {item?.country} + {:else if row?.rule === 'ema20'} + {item?.ema20} {:else if ['fundamentalAnalysis','trendAnalysis']?.includes(row?.rule)} {item[row?.rule]?.accuracy}% {:else} diff --git a/src/routes/stock-screener/[strategyId]/workers/filterWorker.ts b/src/routes/stock-screener/[strategyId]/workers/filterWorker.ts index 2669c6f8..4133c8eb 100644 --- a/src/routes/stock-screener/[strategyId]/workers/filterWorker.ts +++ b/src/routes/stock-screener/[strategyId]/workers/filterWorker.ts @@ -3,19 +3,15 @@ import { sectorList, listOfRelevantCountries } from "$lib/utils"; // Convert the input to a value or return it as-is if it's already an array function convertUnitToValue( input: string | number | string[] -): number | string[] { +): number | string[] | string { if (Array.isArray(input)) return input; - if (typeof input === "number") return input; - if (typeof input !== "string") { throw new TypeError( `Expected a string or number, but received ${typeof input}` ); } - const lowerInput = input.toLowerCase(); - // Pre-compute the set for quick lookups const nonNumericValues = new Set([ "any", @@ -24,9 +20,9 @@ function convertUnitToValue( "hold", "sell", "buy", + "stock price", // Add "stock price" to non-numeric values ]); - - if (nonNumericValues.has(lowerInput)) return "any"; + if (nonNumericValues.has(lowerInput)) return input; if (input.endsWith("%")) { const numericValue = parseFloat(input.slice(0, -1)); @@ -59,36 +55,68 @@ async function filterStockScreenerData(stockScreenerData, ruleOfList) { return ruleOfList.every((rule) => { const itemValue = item[rule.name]; const ruleValue = convertUnitToValue(rule.value); + const ruleName = rule.name.toLowerCase(); - if (["trendAnalysis", "fundamentalAnalysis"].includes(rule.name)) { - const accuracy = item[rule.name]?.accuracy; + // Handle trend and fundamental analysis + if (["trendanalysis", "fundamentalanalysis"].includes(ruleName)) { + const accuracy = item[ruleName]?.accuracy; if (rule.condition === "over" && accuracy <= ruleValue) return false; if (rule.condition === "under" && accuracy > ruleValue) return false; - } else if (["analystRating", "sector", "country"].includes(rule.name)) { - if (rule.value === "any") return true; - - if (Array.isArray(ruleValue) && !ruleValue.includes(itemValue)) - return false; - if (!Array.isArray(ruleValue) && itemValue !== ruleValue) return false; - } else { - if ( - rule.condition === "over" && - itemValue !== null && - itemValue <= ruleValue - ) - return false; - if ( - rule.condition === "under" && - itemValue !== null && - itemValue > ruleValue - ) - return false; + return true; } + + // Handle categorical data like analyst ratings, sector, country + if (["analystrating", "sector", "country"].includes(ruleName)) { + if (ruleValue === "any") return true; + return Array.isArray(ruleValue) + ? ruleValue.includes(itemValue) + : itemValue === ruleValue; + } + + // Handle moving averages + if ( + [ + "ema20", + "ema50", + "ema100", + "ema200", + "sma20", + "sma50", + "sma100", + "sma200", + ].includes(ruleName) + ) { + if (ruleValue === "any") return true; + + for (const condition of ruleValue) { + if (condition === "Stock Price > EMA20") { + const stockPrice = item["price"]; + const maValue = item["ema20"]; + if (!stockPrice || !maValue) return false; + if (!(stockPrice > maValue)) return false; + } + if (condition === "EMA20 > EMA50") { + const ema20 = item["ema20"]; + const ema50 = item["ema50"]; + if (!ema20 || !ema50) return false; + if (ema20 < ema50) return false; + } + // Add additional conditions here + } + + return true; // If all conditions are met + } + + // Default numeric or string comparison + if (typeof ruleValue === "string") return true; // Skip non-numeric comparisons + if (itemValue === null) return false; // Null values do not meet any condition + if (rule.condition === "over" && itemValue <= ruleValue) return false; + if (rule.condition === "under" && itemValue > ruleValue) return false; + return true; }); }); } - onmessage = async (event: MessageEvent) => { const { stockScreenerData, ruleOfList } = event.data || {};