Optimize stock screener data filtering and unit conversion

This commit is contained in:
MuslemRahimi 2024-09-06 01:01:10 +02:00
parent 05bc36f56e
commit 88d379d08d

View File

@ -1,16 +1,12 @@
import { sectorList, listOfRelevantCountries } from "$lib/utils"; 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( function convertUnitToValue(
input: string | number | string[] input: string | number | string[]
): number | string[] { ): number | string[] {
// Handle arrays directly if (Array.isArray(input)) return input;
if (Array.isArray(input)) {
return input; // Return the array as-is, conversion not needed
}
if (typeof input === "number") { if (typeof input === "number") return input;
return input; // If it's already a number, return it directly.
}
if (typeof input !== "string") { if (typeof input !== "string") {
throw new TypeError( throw new TypeError(
@ -18,50 +14,37 @@ function convertUnitToValue(
); );
} }
// Handle specific non-numeric cases const lowerInput = input.toLowerCase();
if (
input.toLowerCase() === "any" || // Pre-compute the set for quick lookups
[ const nonNumericValues = new Set([
...sectorList, "any",
...listOfRelevantCountries, ...sectorList,
"Hold", ...listOfRelevantCountries,
"Sell", "hold",
"Buy", "sell",
]?.includes(input) "buy",
) { ]);
return "any"; // Return a special value for "any" that represents a non-restrictive filter
} if (nonNumericValues.has(lowerInput)) return "any";
// Handle percentage values by stripping the "%" sign and converting to a number
if (input.endsWith("%")) { if (input.endsWith("%")) {
const numericValue = parseFloat(input.slice(0, -1)); const numericValue = parseFloat(input.slice(0, -1));
if (isNaN(numericValue)) { if (isNaN(numericValue)) {
throw new Error(`Unable to convert ${input} to a number`); throw new Error(`Unable to convert ${input} to a number`);
} }
return numericValue; // Convert percentage to a decimal return numericValue;
} }
const units = { const units = { B: 1_000_000_000, M: 1_000_000, K: 1_000 };
B: 1_000_000_000,
M: 1_000_000,
K: 1_000,
};
const match = input.match(/^(\d+(\.\d+)?)([BMK])?$/); const match = input.match(/^(\d+(\.\d+)?)([BMK])?$/);
if (match) { if (match) {
const value = parseFloat(match[1]); const value = parseFloat(match[1]);
const unit = match[3] as keyof typeof units; const unit = match[3] as keyof typeof units;
return unit ? value * units[unit] : value;
// If there's a unit, multiply the value by the unit's multiplier
if (unit) {
return value * units[unit];
} else {
// If no unit, return the value directly
return value;
}
} }
// If input can't be parsed, throw an error
const numericValue = parseFloat(input); const numericValue = parseFloat(input);
if (isNaN(numericValue)) { if (isNaN(numericValue)) {
throw new Error(`Unable to convert ${input} to a number`); throw new Error(`Unable to convert ${input} to a number`);
@ -70,54 +53,44 @@ function convertUnitToValue(
return numericValue; return numericValue;
} }
// Filter the stock screener data based on the provided rules
async function filterStockScreenerData(stockScreenerData, ruleOfList) { async function filterStockScreenerData(stockScreenerData, ruleOfList) {
return stockScreenerData?.filter((item) => { return stockScreenerData?.filter((item) => {
for (const rule of ruleOfList) { return ruleOfList.every((rule) => {
const itemValue = item[rule.name]; const itemValue = item[rule.name];
const ruleValue = convertUnitToValue(rule.value); const ruleValue = convertUnitToValue(rule.value);
if (["trendAnalysis", "fundamentalAnalysis"].includes(rule.name)) { if (["trendAnalysis", "fundamentalAnalysis"].includes(rule.name)) {
const accuracy = item[rule.name]?.accuracy; const accuracy = item[rule.name]?.accuracy;
if (rule.condition === "over" && accuracy <= ruleValue) { if (rule.condition === "over" && accuracy <= ruleValue) return false;
return false; if (rule.condition === "under" && accuracy > ruleValue) return false;
} else if (rule.condition === "under" && accuracy > ruleValue) {
return false;
}
} else if (["analystRating", "sector", "country"].includes(rule.name)) { } else if (["analystRating", "sector", "country"].includes(rule.name)) {
if (rule.value === "any") { if (rule.value === "any") return true;
// Skip filtering if the value is "any"
continue;
}
// Handle the case where rule.value can be a list of items if (Array.isArray(ruleValue) && !ruleValue.includes(itemValue))
if (Array.isArray(ruleValue) && !ruleValue.includes(itemValue)) {
return false; return false;
} else if (!Array.isArray(ruleValue) && itemValue !== ruleValue) { if (!Array.isArray(ruleValue) && itemValue !== ruleValue) return false;
return false;
}
} else { } else {
if ( if (
rule.condition === "over" && rule.condition === "over" &&
itemValue !== null && itemValue !== null &&
itemValue <= ruleValue itemValue <= ruleValue
) { )
return false; return false;
} else if ( if (
rule.condition === "under" && rule.condition === "under" &&
itemValue !== null && itemValue !== null &&
itemValue > ruleValue itemValue > ruleValue
) { )
return false; return false;
}
} }
} return true;
return true; });
}); });
} }
onmessage = async (event: MessageEvent) => { onmessage = async (event: MessageEvent) => {
const stockScreenerData = event.data?.stockScreenerData; const { stockScreenerData, ruleOfList } = event.data || {};
const ruleOfList = event.data?.ruleOfList;
const filteredData = await filterStockScreenerData( const filteredData = await filterStockScreenerData(
stockScreenerData, stockScreenerData,
@ -125,9 +98,6 @@ onmessage = async (event: MessageEvent) => {
); );
postMessage({ message: "success", filteredData }); postMessage({ message: "success", filteredData });
// Sending data back to the main thread
//postMessage({ message: 'Data received in the worker', ticker, apiURL });
}; };
export {}; export {};