update dark pool feed flow
This commit is contained in:
parent
aa697ccf45
commit
fd286269a1
@ -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 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
on:click={() => 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
|
||||
<svg
|
||||
class="flex-shrink-0 w-4 h-4 inline-block {sortOrders[
|
||||
'dailyVolume'
|
||||
'sizeVolRatio'
|
||||
] === 'asc'
|
||||
? 'rotate-180'
|
||||
: sortOrders['dailyVolume'] === 'desc'
|
||||
: sortOrders['sizeVolRatio'] === 'desc'
|
||||
? ''
|
||||
: 'hidden'} "
|
||||
viewBox="0 0 20 20"
|
||||
@ -328,16 +328,16 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
on:click={() => sortData("avgVolume")}
|
||||
on:click={() => sortData("sizeAvgVolRatio")}
|
||||
class="td cursor-pointer select-none bg-[#121217] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
>
|
||||
% Size / Avg Vol
|
||||
<svg
|
||||
class="flex-shrink-0 w-4 h-4 inline-block {sortOrders[
|
||||
'avgVolume'
|
||||
'sizeAvgVolRatio'
|
||||
] === 'asc'
|
||||
? 'rotate-180'
|
||||
: sortOrders['avgVolume'] === 'desc'
|
||||
: sortOrders['sizeAvgVolRatio'] === 'desc'
|
||||
? ''
|
||||
: 'hidden'} "
|
||||
viewBox="0 0 20 20"
|
||||
@ -464,8 +464,8 @@
|
||||
style="justify-content: center;"
|
||||
class="td text-sm sm:text-[1rem] text-white text-end"
|
||||
>
|
||||
{displayedData[index]?.dailyVolumePercentage > 0.01
|
||||
? displayedData[index]?.dailyVolumePercentage?.toFixed(2) + "%"
|
||||
{displayedData[index]?.sizeVolRatio > 0.01
|
||||
? displayedData[index]?.sizeVolRatio?.toFixed(2) + "%"
|
||||
: "< 0.01%"}
|
||||
</div>
|
||||
|
||||
@ -473,8 +473,8 @@
|
||||
style="justify-content: center;"
|
||||
class="td text-sm sm:text-[1rem] text-white text-end"
|
||||
>
|
||||
{displayedData[index]?.avgVolume > 0.01
|
||||
? displayedData[index]?.avgVolume?.toFixed(2) + "%"
|
||||
{displayedData[index]?.sizeAvgVolRatio > 0.01
|
||||
? displayedData[index]?.sizeAvgVolRatio?.toFixed(2) + "%"
|
||||
: "< 0.01%"}
|
||||
</div>
|
||||
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import { sectorList } from "$lib/utils";
|
||||
|
||||
|
||||
interface FilterContext {
|
||||
flowTypeCache?: Map<string, number>;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user