update sma/ema rules

This commit is contained in:
MuslemRahimi 2024-09-10 10:23:42 +02:00
parent 6f5ca8982e
commit a6b5d56a66
2 changed files with 99 additions and 43 deletions

View File

@ -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) {
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content class="w-56 h-fit max-h-72 overflow-y-auto scroller">
{#if !['analystRating','sector','country']?.includes(row?.rule)}
{#if !['ema20', 'analystRating','sector','country']?.includes(row?.rule)}
<DropdownMenu.Label class="absolute mt-2 h-11 border-gray-800 border-b -top-1 z-20 fixed sticky bg-[#09090B]">
<div class="flex items-center justify-start gap-x-1">
<div class="relative inline-block flex flex-row items-center justify-center">
@ -876,16 +885,33 @@ function handleInput(event) {
</div>
{/if}
<DropdownMenu.Group class="min-h-10 mt-2">
{#if !['analystRating','sector','country']?.includes(row?.rule)}
{#if !['ema20','analystRating','sector','country']?.includes(row?.rule)}
{#each row?.step as newValue}
<DropdownMenu.Item class="sm:hover:bg-[#27272A]">
<button on:click={() => {handleChangeValue(newValue)}} class="block w-full border-b border-gray-600 px-4 py-1.5 text-left text-sm sm:text-[1rem] rounded text-white last:border-0 sm:hover:bg-[#27272A]
focus:bg-blue-100 focus:text-gray-900 focus:outline-none">
<button on:click={() => {handleChangeValue(newValue)}} class="block w-full border-b border-gray-600 px-4 py-1.5 text-left text-sm sm:text-[1rem] rounded text-white last:border-0 sm:hover:bg-[#27272A] focus:bg-blue-100 focus:text-gray-900 focus:outline-none">
{ruleCondition[row?.rule] !== undefined ? ruleCondition[row?.rule] : ''} {newValue}
</button>
</DropdownMenu.Item>
{/each}
{:else if ['ema20']?.includes(row?.rule)}
{#each row?.step as item}
<DropdownMenu.Item class="sm:hover:bg-[#27272A]">
<div class="flex items-center" on:click|capture={(event) => event.preventDefault()}>
<label on:click={() => {handleChangeValue(item)}} class="cursor-pointer text-white" for={item}>
<input type="checkbox" checked={ruleOfList?.some(rule =>
rule?.name === row?.rule &&
Array?.isArray(rule?.value) &&
rule?.value.includes(item))}
class="h-4 w-4 rounded bg-dark-500 border border-gray-500 text-blue-600 focus:ring-blue-500" id={ruleOfList?.some(rule =>
rule?.name === row?.rule &&
Array?.isArray(rule?.value) &&
rule?.value.includes(item))}>
<span class="ml-2">{item}</span>
</label>
</div>
</DropdownMenu.Item>
{/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}
<DropdownMenu.Item class="sm:hover:bg-[#27272A]">
@ -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}

View File

@ -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 || {};