diff --git a/src/lib/components/Table/DarkPoolTable.svelte b/src/lib/components/Table/DarkPoolTable.svelte
index d1a04c90..96ae6449 100644
--- a/src/lib/components/Table/DarkPoolTable.svelte
+++ b/src/lib/components/Table/DarkPoolTable.svelte
@@ -49,8 +49,8 @@
premium: "none",
assetType: "none",
volume: "none",
- avgVolume: "none",
- dailyVolume: "none",
+ sizeAvgVolRatio: "none",
+ sizeVolRatio: "none",
size: "none",
sector: "none",
};
@@ -128,14 +128,14 @@
const volB = parseFloat(b.volume);
return sortOrder === "asc" ? volA - volB : volB - volA;
},
- dailyVolume: (a, b) => {
- const volA = parseFloat(a.dailyVolumePercentage);
- const volB = parseFloat(b.dailyVolumePercentage);
+ sizeVolRatio: (a, b) => {
+ const volA = parseFloat(a.sizeVolRatio);
+ const volB = parseFloat(b.sizeVolRatio);
return sortOrder === "asc" ? volA - volB : volB - volA;
},
- avgVolume: (a, b) => {
- const volA = parseFloat(a.avgVolumePercentage);
- const volB = parseFloat(b.avgVolumePercentage);
+ sizeAvgVolRatio: (a, b) => {
+ const volA = parseFloat(a.sizeAvgVolRatio);
+ const volB = parseFloat(b.sizeAvgVolRatio);
return sortOrder === "asc" ? volA - volB : volB - volA;
},
assetType: (a, b) => {
@@ -304,16 +304,16 @@
sortData("dailyVolume")}
+ on:click={() => sortData("sizeVolRatio")}
class="td cursor-pointer select-none bg-[#121217] text-slate-300 font-bold text-xs text-start uppercase"
>
% Size / Vol
diff --git a/src/routes/dark-pool-flow/+page.svelte b/src/routes/dark-pool-flow/+page.svelte
index f40ef488..61f5dbdb 100644
--- a/src/routes/dark-pool-flow/+page.svelte
+++ b/src/routes/dark-pool-flow/+page.svelte
@@ -7,7 +7,7 @@
isOpen,
} from "$lib/store";
- import { cn } from "$lib/utils";
+ import { cn, sectorList } from "$lib/utils";
import { onMount, onDestroy } from "svelte";
import toast from "svelte-french-toast";
import { DateFormatter, type DateValue } from "@internationalized/date";
@@ -25,10 +25,11 @@
export let data;
let shouldLoadWorker = writable(false);
- let ruleOfList = data?.getPredefinedCookieRuleOfList || [];
+ let ruleOfList = [];
let displayRules = [];
let filteredData = [];
let filterQuery = $page.url.searchParams.get("query") || "";
+ let pagePathName = $page?.url?.pathname;
let socket: WebSocket | null = null; // Initialize socket as null
@@ -55,7 +56,19 @@
},
volume: {
label: "Volume",
- step: ["100K", "50K", "20K", "10K", "5K", "2K", "1K", "100", "0"],
+ step: ["100M", "50M", "20M", "10M", "1M", "500K", "200K", "100K", "50K"],
+ defaultCondition: "over",
+ defaultValue: "any",
+ },
+ sizeVolRatio: {
+ label: "Size / Volume",
+ step: ["20%", "15%", "10%", "5%", "3%", "1%"],
+ defaultCondition: "over",
+ defaultValue: "any",
+ },
+ sizeAvgVolRatio: {
+ label: "Size / Avg Volume",
+ step: ["20%", "15%", "10%", "5%", "3%", "1%"],
defaultCondition: "over",
defaultValue: "any",
},
@@ -77,27 +90,19 @@
defaultCondition: "over",
defaultValue: "any",
},
- option_activity_type: {
- label: "Option Type",
- step: ["Sweep", "Trade"],
- defaultValue: "any",
- },
assetType: {
label: "Asset Type",
step: ["Stock", "ETF"],
defaultValue: "any",
},
+ sector: {
+ label: "Sector",
+ step: sectorList,
+ defaultValue: "any",
+ },
};
- const categoricalRules = [
- "moneyness",
- "flowType",
- "put_call",
- "sentiment",
- "execution_estimate",
- "option_activity_type",
- "assetType",
- ];
+ const categoricalRules = ["assetType", "sector"];
// Generate allRows from allRules
$: allRows = Object?.entries(allRules)
@@ -121,13 +126,6 @@
}
});
- // Update ruleCondition and valueMappings based on existing rules
- ruleOfList.forEach((rule) => {
- ruleCondition[rule.name] =
- rule.condition || allRules[rule.name].defaultCondition;
- valueMappings[rule.name] = rule.value || allRules[rule.name].defaultValue;
- });
-
async function handleDeleteRule(state) {
for (let i = 0; i < ruleOfList.length; i++) {
if (ruleOfList[i].name === state) {
@@ -147,7 +145,7 @@
ruleOfList?.some((rule) => rule.name === row.rule),
);
shouldLoadWorker.set(true);
- //await saveCookieRuleOfList();
+ saveRules();
}
async function handleResetAll() {
@@ -165,7 +163,7 @@
ruleOfList.some((rule) => rule.name === row.rule),
);
displayedData = rawData;
- //await saveCookieRuleOfList();
+ saveRules();
}
function changeRule(state: string) {
@@ -380,7 +378,7 @@
// Trigger worker load and save cookie
shouldLoadWorker.set(true);
- //await saveCookieRuleOfList();
+ saveRules();
}
async function stepSizeValue(value, condition) {
@@ -534,18 +532,13 @@
}
*/
- async function saveCookieRuleOfList() {
- const postData = {
- ruleOfList: ruleOfList,
- };
-
- const response = await fetch("/api/options-flow-filter-cookie", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify(postData),
- }); // make a POST request to the server with the FormData object
+ function saveRules() {
+ try {
+ // Save the version along with the rules
+ localStorage?.setItem(pagePathName, JSON?.stringify(ruleOfList));
+ } catch (e) {
+ console.log("Failed saving indicator rules: ", e);
+ }
}
/*
@@ -555,10 +548,36 @@
*/
onMount(async () => {
+ try {
+ const savedRules = localStorage?.getItem(pagePathName);
+
+ if (savedRules) {
+ const parsedRules = JSON.parse(savedRules);
+ // Compare and update ruleOfList based on allRows
+ ruleOfList = parsedRules.map((rule) => {
+ const matchingRow = allRows.find((row) => row.name === rule.name);
+ if (matchingRow && matchingRow.type !== rule.type) {
+ return { ...rule, type: matchingRow.type };
+ }
+ return rule;
+ });
+ }
+ } catch (e) {
+ ruleOfList = [];
+ console.warn(e);
+ }
+
+ // Update ruleCondition and valueMappings based on existing rules
+ ruleOfList?.forEach((rule) => {
+ ruleCondition[rule.name] =
+ rule.condition || allRules[rule.name].defaultCondition;
+ valueMappings[rule.name] = rule.value || allRules[rule.name].defaultValue;
+ });
+
if (filterQuery?.length > 0) {
shouldLoadWorker.set(true);
}
- if (ruleOfList?.length !== 0) {
+ if (ruleOfList?.length > 0) {
shouldLoadWorker.set(true);
console.log("initial filter");
}
diff --git a/src/routes/dark-pool-flow/workers/filterWorker.ts b/src/routes/dark-pool-flow/workers/filterWorker.ts
index 543527ea..2f60e3e5 100644
--- a/src/routes/dark-pool-flow/workers/filterWorker.ts
+++ b/src/routes/dark-pool-flow/workers/filterWorker.ts
@@ -1,14 +1,13 @@
+import { sectorList } from "$lib/utils";
+
interface FilterContext {
flowTypeCache?: Map;
}
const categoricalFields = [
- 'put_call',
- 'sentiment',
- 'execution_estimate',
- 'option_activity_type',
- 'underlying_type'
+ 'assetType',
+ 'sector',
];
function convertUnitToValue(
@@ -22,26 +21,14 @@ function convertUnitToValue(
);
}
const lowerInput = input?.toLowerCase();
- const nonNumericValues = new Set([
- "any",
- "puts",
- "calls",
- "bullish",
- "neutral",
- "bearish",
- "at bid",
- "at ask",
- "at midpoint",
- "above ask",
- "below bid",
- "sweep",
- "trade",
- "stock",
- "etf",
- "itm",
- "otm",
- "repeated flow"
- ]);
+const nonNumericValues = new Set([
+ "any",
+ "stock",
+ "etf",
+ ...sectorList.map(sector => sector.toLowerCase()),
+]);
+
+
if (nonNumericValues.has(lowerInput)) return input;
if (input.endsWith("%")) {
const numericValue = parseFloat(input.slice(0, -1));
@@ -64,80 +51,18 @@ function convertUnitToValue(
return numericValue;
}
-function isAny(value: string | string[]): boolean {
- if (typeof value === "string") return value.toLowerCase() === "any";
- if (Array.isArray(value))
- return value.length === 1 && value[0].toLowerCase() === "any";
- return false;
-}
-
-
function createRuleCheck(rule, ruleName, ruleValue) {
- const now = new Date(new Date().toLocaleString("en-US", { timeZone: "America/New_York" }));
+if (rule.value === 'any') return () => true;
-
-if (ruleName === 'flowtype') {
- return (item: any, context: FilterContext = {}) => {
- // Check for 'any' rule or other non-repeated flow conditions
- if (ruleValue === 'any' || ruleValue?.includes("any")) {
- return true;
- }
-
- // Handle Repeated Flow logic
- if (ruleValue?.includes('Repeated Flow')) {
- // Initialize flowTypeCache if it doesn't exist
- context.flowTypeCache = context.flowTypeCache || new Map();
-
- // Create a unique key for repeated flow based on item characteristics
- const key = `${item.ticker}-${item.put_call}-${item.strike_price}-${item.date_expiration}`;
-
- // Increment the count for the key in the flowTypeCache
- const currentCount = (context.flowTypeCache.get(key) || 0) + 1;
- context.flowTypeCache.set(key, currentCount);
-
- // Return true if this flow appears more than N times (3 in this case)
- return currentCount > 3;
- }
-
- // Fallback for other flow type conditions (i.e., non-repeated flow)
- return true;
- };
-}
-
-
- if (ruleName === 'moneyness') {
- return (item) => {
- if (ruleValue === 'any' || ruleValue?.includes("any")) return true;
-
- const currentPrice = parseFloat(item?.underlying_price);
- const strikePrice = parseFloat(item?.strike_price);
- const optionType = item?.put_call;
- if (isNaN(currentPrice) || isNaN(strikePrice)) return false;
-
- // Determine moneyness
- let moneyness = '';
- if (optionType === 'Calls') {
- moneyness = currentPrice > strikePrice ? 'ITM' : 'OTM';
- } else if (optionType === 'Puts') {
- moneyness = currentPrice < strikePrice ? 'ITM' : 'OTM';
- }
- // Check if the item matches the ruleValue ('itm' or 'otm')
- if (!ruleValue?.includes(moneyness)) return false;
- return true;
- };
- }
-
-if (ruleName === 'volumeoiratio') {
+if (['sizeVolRatio','sizeAvgVolRatio']?.includes(ruleName)) {
return (item) => {
- const volume = parseFloat(item.volume);
- const openInterest = parseFloat(item.open_interest);
- if (isNaN(volume) || isNaN(openInterest) || openInterest === 0) {
+ if (isNaN(ruleValue) || ruleValue === 0) {
return false;
}
- const ratio = Math.ceil((volume / openInterest) * 100);
+ const ratio = rule?.value;
// Handle 'between' condition for volume to open interest ratio
if (rule.condition === 'between' && Array.isArray(ruleValue)) {
@@ -161,125 +86,15 @@ if (ruleName === 'volumeoiratio') {
}
-if (ruleName === 'sizeoiratio') {
- return (item) => {
- const size = parseFloat(item?.size);
- const openInterest = parseFloat(item?.open_interest);
- if (isNaN(size) || isNaN(openInterest) || openInterest === 0) {
- return false;
- }
- const ratio = Math?.ceil((size / openInterest) * 100);
-
- // Handle 'between' condition for size to open interest ratio
- if (rule.condition === 'between' && Array.isArray(ruleValue)) {
- const [minRatio, maxRatio] = ruleValue?.map(convertUnitToValue); // Convert ruleValue to numeric values
-
- if (minRatio === null && maxRatio === null) return true;
- if (minRatio === null) return ratio <= maxRatio;
- if (maxRatio === null) return ratio >= minRatio;
-
- return ratio >= minRatio && ratio <= maxRatio;
- }
-
- // Existing conditions for 'over' and 'under'
- if (rule.condition === 'over' && ratio <= ruleValue) return false;
- if (rule.condition === 'under' && ratio >= ruleValue) return false;
- if (rule.condition === 'exactly' && ratio !== ruleValue) return false;
-
-
- return true;
- };
-}
-
-
- if (ruleName === 'date_expiration') {
- // If ruleValue is empty, undefined, "any", or an array containing only "any", return a function that always returns true
- if (ruleValue === "" || ruleValue === undefined || isAny(ruleValue)) {
- return () => true;
- }
-
-
- return (item) => {
- const expirationDate = new Date(item[rule.name]);
- if (isNaN(expirationDate)) return false; // Handle invalid dates
-
- const daysDiff = Math?.ceil((expirationDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
-
-
- if (rule.condition === 'between' && Array.isArray(ruleValue)) {
- const [minDays, maxDays] = ruleValue.map(val =>
- val === '' || val === null || val === undefined ? null : parseFloat(val.toString())
- );
-
- if (minDays === null && maxDays === null) return true;
- if (minDays === null) return daysDiff <= maxDays;
- if (maxDays === null) return daysDiff >= minDays;
-
- return daysDiff >= minDays && daysDiff <= maxDays;
- }
-
- if (rule.condition === 'over' && typeof ruleValue === 'number') {
- return daysDiff >= ruleValue;
- }
-
- if (rule.condition === 'under' && typeof ruleValue === 'number') {
- return daysDiff <= ruleValue;
- }
-
- if (rule.condition === 'exactly' && typeof ruleValue === 'number') {
- return daysDiff === ruleValue;
- }
-
-
- return false;
- };
-}
// Handle string-based conditions (sentiment, option_type, etc.)
- if (categoricalFields?.includes(ruleName)) {
+ if (categoricalFields.includes(rule.name)) {
return (item) => {
- // If ruleValue is empty, undefined, or "any", return true for all items
- if (ruleValue === "" || ruleValue === undefined || isAny(ruleValue)) {
- return true;
- }
-
const itemValue = item[rule.name];
-
- // Handle array of values for categorical fields
- if (Array?.isArray(ruleValue)) {
- // Remove any empty or undefined values from ruleValue
- const validRuleValues = ruleValue?.filter(val => val !== "" && val !== undefined);
-
- // If no valid values remain, return true for all items
- if (validRuleValues.length === 0) {
- return true;
- }
-
- // If itemValue is an array, check if any of the values match
- if (Array?.isArray(itemValue)) {
- return validRuleValues?.some(val =>
- itemValue.some(iv => iv.toLowerCase() === val.toLowerCase())
- );
- }
-
- // If itemValue is a string, check if it's in the validRuleValues array
- return validRuleValues?.some(val =>
- itemValue?.toLowerCase() === val.toLowerCase()
- );
+ if (Array.isArray(ruleValue)) {
+ return ruleValue.includes(itemValue);
}
-
- // Handle single string value
- if (typeof ruleValue === 'string') {
- // If itemValue is an array, check if any value matches
- if (Array?.isArray(itemValue)) {
- return itemValue?.some(iv => iv.toLowerCase() === ruleValue.toLowerCase());
- }
-
- // If both are strings, do a direct comparison
- return itemValue?.toLowerCase() === ruleValue.toLowerCase();
- }
-
- return false;
+ return itemValue === ruleValue;
};
}