improve performance of screener
This commit is contained in:
parent
18ffb20147
commit
14e5880ab6
9
package-lock.json
generated
9
package-lock.json
generated
@ -37,7 +37,7 @@
|
|||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"d3-hierarchy": "^3.1.2",
|
"d3-hierarchy": "^3.1.2",
|
||||||
"d3-sankey": "^0.12.3",
|
"d3-sankey": "^0.12.3",
|
||||||
"daisyui": "^4.12.3",
|
"daisyui": "^4.12.20",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"date-fns-tz": "^3.1.3",
|
"date-fns-tz": "^3.1.3",
|
||||||
"date-picker-svelte": "^2.12.0",
|
"date-picker-svelte": "^2.12.0",
|
||||||
@ -4971,10 +4971,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/daisyui": {
|
"node_modules/daisyui": {
|
||||||
"version": "4.12.10",
|
"version": "4.12.20",
|
||||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.10.tgz",
|
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.20.tgz",
|
||||||
"integrity": "sha512-jp1RAuzbHhGdXmn957Z2XsTZStXGHzFfF0FgIOZj3Wv9sH7OZgLfXTRZNfKVYxltGUOBsG1kbWAdF5SrqjebvA==",
|
"integrity": "sha512-uHr3SQsd2yTjRdVuswTiqGFvZTxX0sGSBRa8JJdbKgmZCk/kRFh4B7Z2jg9vLIdwsHTHPyPgCkZadQo1ce0tAw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"css-selector-tokenizer": "^0.8",
|
"css-selector-tokenizer": "^0.8",
|
||||||
"culori": "^3",
|
"culori": "^3",
|
||||||
|
|||||||
@ -36,7 +36,7 @@
|
|||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"d3-hierarchy": "^3.1.2",
|
"d3-hierarchy": "^3.1.2",
|
||||||
"d3-sankey": "^0.12.3",
|
"d3-sankey": "^0.12.3",
|
||||||
"daisyui": "^4.12.3",
|
"daisyui": "^4.12.20",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"date-fns-tz": "^3.1.3",
|
"date-fns-tz": "^3.1.3",
|
||||||
"date-picker-svelte": "^2.12.0",
|
"date-picker-svelte": "^2.12.0",
|
||||||
|
|||||||
@ -104,131 +104,114 @@ function convertUnitToValue(input: string | number | string[]) {
|
|||||||
return input; // Return original input in case of any unexpected errors
|
return input; // Return original input in case of any unexpected errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function filterStockScreenerData(stockScreenerData, ruleOfList) {
|
|
||||||
try {
|
// Centralized rule checking logic
|
||||||
return stockScreenerData?.filter((item) => {
|
function createRuleCheck(rule, ruleName, ruleValue) {
|
||||||
return ruleOfList.every((rule) => {
|
// Handle 'any' condition quickly
|
||||||
try {
|
if (rule.value === 'any') return () => true;
|
||||||
|
|
||||||
|
// Categorical checks
|
||||||
|
const categoricalFields = [
|
||||||
|
'analystRating', 'halalStocks', 'score',
|
||||||
|
'sector', 'industry', 'country'
|
||||||
|
];
|
||||||
|
|
||||||
|
if (categoricalFields.includes(rule.name)) {
|
||||||
|
return (item) => {
|
||||||
const itemValue = item[rule.name];
|
const itemValue = item[rule.name];
|
||||||
const ruleValue = convertUnitToValue(rule.value);
|
if (Array.isArray(ruleValue)) {
|
||||||
|
return ruleValue.includes(itemValue);
|
||||||
|
}
|
||||||
|
return itemValue === ruleValue;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Moving averages checks
|
||||||
|
const movingAverageFields = [
|
||||||
|
'ema20', 'ema50', 'ema100', 'ema200',
|
||||||
|
'sma20', 'sma50', 'sma100', 'sma200',
|
||||||
|
'grahamnumber'
|
||||||
|
];
|
||||||
|
|
||||||
|
if (movingAverageFields.includes(ruleName)) {
|
||||||
|
return (item) => {
|
||||||
|
if (Array.isArray(ruleValue)) {
|
||||||
|
return ruleValue.every(condition =>
|
||||||
|
movingAverageConditions[condition]?.(item) ?? true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Between condition
|
||||||
|
if (rule.condition === 'between' && Array.isArray(ruleValue)) {
|
||||||
|
return (item) => {
|
||||||
|
const itemValue = item[rule.name];
|
||||||
|
const [min, max] = ruleValue.map(convertUnitToValue);
|
||||||
|
|
||||||
|
// Handle empty/undefined min and max
|
||||||
|
if ((min === '' || min === undefined || min === null) &&
|
||||||
|
(max === '' || max === undefined || max === null)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (min === '' || min === undefined || min === null) {
|
||||||
|
return itemValue < max;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max === '' || max === undefined || max === null) {
|
||||||
|
return itemValue > min;
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemValue > min && itemValue < max;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default numeric comparisons
|
||||||
|
return (item) => {
|
||||||
|
const itemValue = item[rule.name];
|
||||||
|
|
||||||
|
if (itemValue === null) return false;
|
||||||
|
|
||||||
|
if (rule.condition === 'over') {
|
||||||
|
return itemValue > ruleValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.condition === 'under') {
|
||||||
|
return itemValue < ruleValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default comparison if no specific condition
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function filterStockScreenerData(stockScreenerData, ruleOfList) {
|
||||||
|
// Early return if no data or no rules
|
||||||
|
if (!stockScreenerData?.length || !ruleOfList?.length) {
|
||||||
|
return stockScreenerData || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Precompile rule conditions to avoid repeated checks
|
||||||
|
const compiledRules = ruleOfList.map(rule => {
|
||||||
const ruleName = rule.name.toLowerCase();
|
const ruleName = rule.name.toLowerCase();
|
||||||
|
const ruleValue = convertUnitToValue(rule.value);
|
||||||
|
|
||||||
// If ruleValue is the original input (conversion failed),
|
return {
|
||||||
// we'll treat it as a special case
|
...rule,
|
||||||
if (typeof ruleValue === "string") {
|
compiledCheck: createRuleCheck(rule, ruleName, ruleValue)
|
||||||
// For most string inputs, we'll consider it a match
|
};
|
||||||
if (rule.value === "any") return true;
|
|
||||||
|
|
||||||
// For specific categorical checks
|
|
||||||
if (
|
|
||||||
[
|
|
||||||
"analystRating",
|
|
||||||
"halalStocks",
|
|
||||||
"score",
|
|
||||||
"sector",
|
|
||||||
"industry",
|
|
||||||
"country",
|
|
||||||
].includes(rule.name)
|
|
||||||
) {
|
|
||||||
if (Array.isArray(ruleValue) && !ruleValue.includes(itemValue))
|
|
||||||
return false;
|
|
||||||
if (!Array.isArray(ruleValue) && itemValue !== ruleValue) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For other cases, we'll skip filtering
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle categorical data like analyst ratings, sector, country
|
|
||||||
if (
|
|
||||||
[
|
|
||||||
"analystRating",
|
|
||||||
"halalStocks",
|
|
||||||
"score",
|
|
||||||
"sector",
|
|
||||||
"industry",
|
|
||||||
"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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle moving averages
|
|
||||||
else if (
|
|
||||||
[
|
|
||||||
"ema20",
|
|
||||||
"ema50",
|
|
||||||
"ema100",
|
|
||||||
"ema200",
|
|
||||||
"sma20",
|
|
||||||
"sma50",
|
|
||||||
"sma100",
|
|
||||||
"sma200",
|
|
||||||
"grahamnumber", // grahamNumber into lowerCase form
|
|
||||||
].includes(ruleName)
|
|
||||||
) {
|
|
||||||
if (ruleValue === "any") return true;
|
|
||||||
|
|
||||||
for (const condition of ruleValue) {
|
|
||||||
if (movingAverageConditions[condition]) {
|
|
||||||
if (!movingAverageConditions[condition](item)) return false;
|
|
||||||
} else {
|
|
||||||
// console.warn(`Unknown condition: ${condition}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true; // If all conditions are met
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle "between" condition
|
|
||||||
else if (rule.condition === "between" && Array?.isArray(ruleValue)) {
|
|
||||||
// Convert rule values, ensuring they are valid
|
|
||||||
const [min, max] = ruleValue?.map(convertUnitToValue);
|
|
||||||
|
|
||||||
// Handle the case where one or both values are missing (empty string or undefined)
|
|
||||||
if ((min === "" || min === undefined || min === null) && (max === "" || max === undefined || max === null)) {
|
|
||||||
return true; // If both values are empty or undefined, consider the condition as met (open-ended)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If only one of min or max is missing, handle it as open-ended
|
|
||||||
if (min === "" || min === undefined || min === null) {
|
|
||||||
if (itemValue >= max) return false; // If min is missing, only check against max
|
|
||||||
} else if (max === "" || max === undefined || max === null) {
|
|
||||||
if (itemValue <= min) return false; // If max is missing, only check against min
|
|
||||||
} else {
|
|
||||||
// If both min and max are defined, proceed with the normal comparison
|
|
||||||
if (itemValue <= min || itemValue >= max) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default numeric or string comparison
|
|
||||||
else if (typeof ruleValue === "string") {
|
|
||||||
return true; // Skip non-numeric comparisons
|
|
||||||
} else if (itemValue === null) {
|
|
||||||
return false; // Null values do not meet any condition
|
|
||||||
} else if (rule.condition === "over" && itemValue <= ruleValue) {
|
|
||||||
return false;
|
|
||||||
} else if (rule.condition === "under" && itemValue > ruleValue) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (ruleError) {
|
|
||||||
console.warn(`Error processing rule for item:`, rule, ruleError);
|
|
||||||
return true; // Default to including the item if rule processing fails
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}) || stockScreenerData; // Return original data if filtering completely fails
|
|
||||||
} catch (error) {
|
// Use a more performant filtering method
|
||||||
console.error('Error in filterStockScreenerData:', error);
|
return stockScreenerData?.filter(item =>
|
||||||
return stockScreenerData; // Return original data if any catastrophic error occurs
|
compiledRules?.every(rule => rule.compiledCheck(item))
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onmessage = async (event: MessageEvent) => {
|
onmessage = async (event: MessageEvent) => {
|
||||||
const { stockScreenerData, ruleOfList } = event.data || {};
|
const { stockScreenerData, ruleOfList } = event.data || {};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user