update dark pool flow page
This commit is contained in:
parent
9bae61ff29
commit
fa939b926b
@ -4,58 +4,52 @@
|
||||
|
||||
import VirtualList from "svelte-tiny-virtual-list";
|
||||
import HoverStockChart from "$lib/components/HoverStockChart.svelte";
|
||||
import toast from "svelte-french-toast";
|
||||
|
||||
export let data;
|
||||
export let optionsWatchlist;
|
||||
export let displayedData = [];
|
||||
export let filteredData = [];
|
||||
export let rawData = [];
|
||||
|
||||
let animationClass = "";
|
||||
let animationId = "";
|
||||
function formatToNewYorkTime(isoString) {
|
||||
const date = new Date(isoString);
|
||||
|
||||
function formatTime(timeString) {
|
||||
// Split the time string into components
|
||||
const [hours, minutes, seconds] = timeString?.split(":").map(Number);
|
||||
// Get the date components in New York time zone
|
||||
const options = {
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
second: "numeric",
|
||||
timeZone: "America/New_York",
|
||||
hour12: false,
|
||||
};
|
||||
|
||||
// Determine AM or PM
|
||||
const period = hours >= 12 ? "PM" : "AM";
|
||||
// Format date for New York timezone
|
||||
const formatter = new Intl.DateTimeFormat("en-US", options);
|
||||
const parts = formatter.formatToParts(date);
|
||||
|
||||
// Convert hours from 24-hour to 12-hour format
|
||||
const formattedHours = hours % 12 || 12; // Converts 0 to 12 for midnight
|
||||
const year = parts.find((p) => p.type === "year").value;
|
||||
const day = parts.find((p) => p.type === "day").value;
|
||||
const hour = parts.find((p) => p.type === "hour").value.padStart(2, "0");
|
||||
const minute = parts
|
||||
.find((p) => p.type === "minute")
|
||||
.value.padStart(2, "0");
|
||||
const second = parts
|
||||
.find((p) => p.type === "second")
|
||||
.value.padStart(2, "0");
|
||||
|
||||
// Format the time string
|
||||
const formattedTimeString = `${formattedHours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")} ${period}`;
|
||||
|
||||
return formattedTimeString;
|
||||
}
|
||||
|
||||
function reformatDate(dateString) {
|
||||
return (
|
||||
dateString.substring(5, 7) +
|
||||
"/" +
|
||||
dateString.substring(8) +
|
||||
"/" +
|
||||
dateString.substring(2, 4)
|
||||
);
|
||||
return `${day}/${year} ${hour}:${minute}:${second}`;
|
||||
}
|
||||
|
||||
let sortOrders = {
|
||||
time: "none",
|
||||
date: "none",
|
||||
ticker: "none",
|
||||
expiry: "none",
|
||||
dte: "none",
|
||||
strike: "none",
|
||||
callPut: "none",
|
||||
sentiment: "none",
|
||||
spot: "none",
|
||||
price: "none",
|
||||
premium: "none",
|
||||
type: "none",
|
||||
exec: "none",
|
||||
vol: "none",
|
||||
oi: "none",
|
||||
assetType: "none",
|
||||
volume: "none",
|
||||
size: "none",
|
||||
};
|
||||
|
||||
// Generalized sorting function
|
||||
@ -83,11 +77,6 @@
|
||||
}
|
||||
|
||||
const compareFunctions = {
|
||||
time: (a, b) => {
|
||||
const timeA = new Date("1970-01-01T" + a.time).getTime();
|
||||
const timeB = new Date("1970-01-01T" + b.time).getTime();
|
||||
return sortOrder === "asc" ? timeA - timeB : timeB - timeA;
|
||||
},
|
||||
ticker: (a, b) => {
|
||||
const tickerA = a.ticker.toUpperCase();
|
||||
const tickerB = b.ticker.toUpperCase();
|
||||
@ -95,34 +84,19 @@
|
||||
? tickerA.localeCompare(tickerB)
|
||||
: tickerB.localeCompare(tickerA);
|
||||
},
|
||||
expiry: (a, b) => {
|
||||
const timeA = new Date(a.date_expiration);
|
||||
const timeB = new Date(b.date_expiration);
|
||||
date: (a, b) => {
|
||||
const timeA = new Date(a.date);
|
||||
const timeB = new Date(b.date);
|
||||
return sortOrder === "asc" ? timeA - timeB : timeB - timeA;
|
||||
},
|
||||
dte: (a, b) => {
|
||||
const timeA = new Date(a.date_expiration);
|
||||
const timeB = new Date(b.date_expiration);
|
||||
return sortOrder === "asc" ? timeA - timeB : timeB - timeA;
|
||||
},
|
||||
strike: (a, b) => {
|
||||
const strikeA = parseFloat(a.strike_price);
|
||||
const strikeB = parseFloat(b.strike_price);
|
||||
return sortOrder === "asc" ? strikeA - strikeB : strikeB - strikeA;
|
||||
},
|
||||
spot: (a, b) => {
|
||||
const spotA = parseFloat(a.underlying_price);
|
||||
const spotB = parseFloat(b.underlying_price);
|
||||
return sortOrder === "asc" ? spotA - spotB : spotB - spotA;
|
||||
},
|
||||
price: (a, b) => {
|
||||
const priceA = parseFloat(a.price);
|
||||
const priceB = parseFloat(b.price);
|
||||
return sortOrder === "asc" ? priceA - priceB : priceB - priceA;
|
||||
},
|
||||
premium: (a, b) => {
|
||||
const premiumA = parseFloat(a.cost_basis);
|
||||
const premiumB = parseFloat(b.cost_basis);
|
||||
const premiumA = parseFloat(a.premium);
|
||||
const premiumB = parseFloat(b.premium);
|
||||
return sortOrder === "asc" ? premiumA - premiumB : premiumB - premiumA;
|
||||
},
|
||||
size: (a, b) => {
|
||||
@ -130,44 +104,27 @@
|
||||
const volB = parseFloat(b?.size);
|
||||
return sortOrder === "asc" ? volA - volB : volB - volA;
|
||||
},
|
||||
vol: (a, b) => {
|
||||
volume: (a, b) => {
|
||||
const volA = parseFloat(a.volume);
|
||||
const volB = parseFloat(b.volume);
|
||||
return sortOrder === "asc" ? volA - volB : volB - volA;
|
||||
},
|
||||
oi: (a, b) => {
|
||||
const oiA = parseFloat(a.open_interest);
|
||||
const oiB = parseFloat(b.open_interest);
|
||||
return sortOrder === "asc" ? oiA - oiB : oiB - oiA;
|
||||
dailyVolume: (a, b) => {
|
||||
const volA = parseFloat(a.dailyVolumePercentage);
|
||||
const volB = parseFloat(b.dailyVolumePercentage);
|
||||
return sortOrder === "asc" ? volA - volB : volB - volA;
|
||||
},
|
||||
callPut: (a, b) => {
|
||||
const callPutA = a.put_call?.toUpperCase();
|
||||
const callPutB = b.put_call?.toUpperCase();
|
||||
return sortOrder === "asc"
|
||||
? callPutA.localeCompare(callPutB)
|
||||
: callPutB.localeCompare(callPutA);
|
||||
avgVolume: (a, b) => {
|
||||
const volA = parseFloat(a.avgVolumePercentage);
|
||||
const volB = parseFloat(b.avgVolumePercentage);
|
||||
return sortOrder === "asc" ? volA - volB : volB - volA;
|
||||
},
|
||||
sentiment: (a, b) => {
|
||||
const sentimentOrder = { BULLISH: 1, NEUTRAL: 2, BEARISH: 3 };
|
||||
const sentimentA = sentimentOrder[a?.sentiment?.toUpperCase()] || 4;
|
||||
const sentimentB = sentimentOrder[b?.sentiment?.toUpperCase()] || 4;
|
||||
return sortOrder === "asc"
|
||||
? sentimentA - sentimentB
|
||||
: sentimentB - sentimentA;
|
||||
},
|
||||
type: (a, b) => {
|
||||
assetType: (a, b) => {
|
||||
const typeOrder = { SWEEP: 1, TRADE: 2 };
|
||||
const typeA = typeOrder[a.option_activity_type?.toUpperCase()] || 3;
|
||||
const typeB = typeOrder[b.option_activity_type?.toUpperCase()] || 3;
|
||||
const typeA = typeOrder[a.assetType?.toUpperCase()] || 3;
|
||||
const typeB = typeOrder[b.assetType?.toUpperCase()] || 3;
|
||||
return sortOrder === "asc" ? typeA - typeB : typeB - typeA;
|
||||
},
|
||||
exec: (a, b) => {
|
||||
const tickerA = a?.execution_estimate?.toUpperCase();
|
||||
const tickerB = b?.execution_estimate?.toUpperCase();
|
||||
return sortOrder === "asc"
|
||||
? tickerA.localeCompare(tickerB)
|
||||
: tickerB.localeCompare(tickerA);
|
||||
},
|
||||
};
|
||||
|
||||
// Sort using the appropriate comparison function
|
||||
@ -183,15 +140,18 @@
|
||||
itemCount={displayedData.length}
|
||||
itemSize={40}
|
||||
>
|
||||
<div slot="header" class="tr th sticky z-40 top-0">
|
||||
<div
|
||||
slot="header"
|
||||
class="tr th m-auto sticky z-40 top-0 border-b border-gray-800 shadow-xl"
|
||||
>
|
||||
<!-- Table headers -->
|
||||
<div
|
||||
on:click={() => sortData("time")}
|
||||
class="td cursor-pointer select-none bg-[#1E222D] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
on:click={() => sortData("date")}
|
||||
class="td cursor-pointer select-none bg-[#121217] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
>
|
||||
Time
|
||||
Date
|
||||
<svg
|
||||
class="flex-shrink-0 w-4 h-4 inline-block {sortOrders['time'] ===
|
||||
class="flex-shrink-0 w-4 h-4 inline-block {sortOrders['date'] ===
|
||||
'asc'
|
||||
? 'rotate-180'
|
||||
: ''} "
|
||||
@ -207,7 +167,7 @@
|
||||
</div>
|
||||
<div
|
||||
on:click={() => sortData("ticker")}
|
||||
class="td cursor-pointer select-none bg-[#1E222D] font-bold text-slate-300 text-xs text-start uppercase"
|
||||
class="td cursor-pointer select-none bg-[#121217] font-bold text-slate-300 text-xs text-start uppercase"
|
||||
>
|
||||
Symbol
|
||||
<svg
|
||||
@ -230,7 +190,7 @@
|
||||
|
||||
<div
|
||||
on:click={() => sortData("price")}
|
||||
class="td cursor-pointer select-none bg-[#1E222D] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
class="td cursor-pointer select-none bg-[#121217] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
>
|
||||
Price
|
||||
<svg
|
||||
@ -252,7 +212,7 @@
|
||||
</div>
|
||||
<div
|
||||
on:click={() => sortData("premium")}
|
||||
class="td cursor-pointer select-none bg-[#1E222D] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
class="td cursor-pointer select-none bg-[#121217] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
>
|
||||
Premium
|
||||
<svg
|
||||
@ -275,7 +235,7 @@
|
||||
|
||||
<div
|
||||
on:click={() => sortData("size")}
|
||||
class="td cursor-pointer select-none bg-[#1E222D] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
class="td cursor-pointer select-none bg-[#121217] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
>
|
||||
Size
|
||||
<svg
|
||||
@ -296,15 +256,15 @@
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
on:click={() => sortData("vol")}
|
||||
class="td cursor-pointer select-none bg-[#1E222D] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
on:click={() => sortData("volume")}
|
||||
class="td cursor-pointer select-none bg-[#121217] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
>
|
||||
Volume
|
||||
<svg
|
||||
class="flex-shrink-0 w-4 h-4 inline-block {sortOrders['vol'] ===
|
||||
class="flex-shrink-0 w-4 h-4 inline-block {sortOrders['volume'] ===
|
||||
'asc'
|
||||
? 'rotate-180'
|
||||
: sortOrders['vol'] === 'desc'
|
||||
: sortOrders['volume'] === 'desc'
|
||||
? ''
|
||||
: 'hidden'} "
|
||||
viewBox="0 0 20 20"
|
||||
@ -319,15 +279,16 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
on:click={() => sortData("vol")}
|
||||
class="td cursor-pointer select-none bg-[#1E222D] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
on:click={() => sortData("dailyVolume")}
|
||||
class="td cursor-pointer select-none bg-[#121217] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
>
|
||||
% Daily Volume
|
||||
<svg
|
||||
class="flex-shrink-0 w-4 h-4 inline-block {sortOrders['vol'] ===
|
||||
'asc'
|
||||
class="flex-shrink-0 w-4 h-4 inline-block {sortOrders[
|
||||
'dailyVolume'
|
||||
] === 'asc'
|
||||
? 'rotate-180'
|
||||
: sortOrders['vol'] === 'desc'
|
||||
: sortOrders['dailyVolume'] === 'desc'
|
||||
? ''
|
||||
: 'hidden'} "
|
||||
viewBox="0 0 20 20"
|
||||
@ -342,15 +303,16 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
on:click={() => sortData("vol")}
|
||||
class="td cursor-pointer select-none bg-[#1E222D] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
on:click={() => sortData("avgVolume")}
|
||||
class="td cursor-pointer select-none bg-[#121217] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
>
|
||||
% 30D Volume
|
||||
<svg
|
||||
class="flex-shrink-0 w-4 h-4 inline-block {sortOrders['vol'] ===
|
||||
'asc'
|
||||
class="flex-shrink-0 w-4 h-4 inline-block {sortOrders[
|
||||
'avgVolume'
|
||||
] === 'asc'
|
||||
? 'rotate-180'
|
||||
: sortOrders['vol'] === 'desc'
|
||||
: sortOrders['avgVolume'] === 'desc'
|
||||
? ''
|
||||
: 'hidden'} "
|
||||
viewBox="0 0 20 20"
|
||||
@ -365,15 +327,15 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
on:click={() => sortData("vol")}
|
||||
class="td cursor-pointer select-none bg-[#1E222D] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
on:click={() => sortData("sector")}
|
||||
class="td cursor-pointer select-none bg-[#121217] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
>
|
||||
Sector
|
||||
<svg
|
||||
class="flex-shrink-0 w-4 h-4 inline-block {sortOrders['vol'] ===
|
||||
class="flex-shrink-0 w-4 h-4 inline-block {sortOrders['sector'] ===
|
||||
'asc'
|
||||
? 'rotate-180'
|
||||
: sortOrders['vol'] === 'desc'
|
||||
: sortOrders['sector'] === 'desc'
|
||||
? ''
|
||||
: 'hidden'} "
|
||||
viewBox="0 0 20 20"
|
||||
@ -389,7 +351,7 @@
|
||||
|
||||
<div
|
||||
on:click={() => sortData("vol")}
|
||||
class="td cursor-pointer select-none bg-[#1E222D] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
class="td cursor-pointer select-none bg-[#121217] text-slate-300 font-bold text-xs text-start uppercase"
|
||||
>
|
||||
Issue Type
|
||||
<svg
|
||||
@ -416,15 +378,15 @@
|
||||
let:index
|
||||
let:style
|
||||
{style}
|
||||
class="tr {index % 2 === 0 ? 'bg-secondary' : 'bg-[#09090B]'}"
|
||||
class="tr {index % 2 === 0 ? 'bg-[#19191F]' : 'bg-[#121217]'}"
|
||||
>
|
||||
<!-- Row data -->
|
||||
|
||||
<div
|
||||
style="justify-content: center;"
|
||||
class="td text-white text-sm sm:text-[1rem] text-start m-auto whitespace-nowrap"
|
||||
style="justify-content: center; "
|
||||
class=" td w-full text-white text-xs sm:text-sm whitespace-nowrap m-auto"
|
||||
>
|
||||
{reformatDate(displayedData[index]?.date_expiration)}
|
||||
{formatToNewYorkTime(displayedData[index]?.date)}
|
||||
</div>
|
||||
<div
|
||||
on:click|stopPropagation
|
||||
@ -433,7 +395,7 @@
|
||||
>
|
||||
<HoverStockChart
|
||||
symbol={displayedData[index]?.ticker}
|
||||
assetType={displayedData[index]?.underlying_type}
|
||||
assetType={displayedData[index]?.assetType}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -448,7 +410,7 @@
|
||||
style="justify-content: center;"
|
||||
class="td text-sm sm:text-[1rem] text-start text-white"
|
||||
>
|
||||
{@html abbreviateNumber(displayedData[index]?.cost_basis, true, true)}
|
||||
{@html abbreviateNumber(displayedData[index]?.premium, true, true)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
@ -465,44 +427,41 @@
|
||||
style="justify-content: center;"
|
||||
class="td text-sm sm:text-[1rem] text-white text-end"
|
||||
>
|
||||
{new Intl.NumberFormat("en", {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(displayedData[index]?.volume)}
|
||||
{@html abbreviateNumber(displayedData[index]?.volume, false, true)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
style="justify-content: center;"
|
||||
class="td text-sm sm:text-[1rem] text-white text-end"
|
||||
>
|
||||
{new Intl.NumberFormat("en", {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(displayedData[index]?.volume)}
|
||||
{displayedData[index]?.dailyVolumePercentage > 0.01
|
||||
? displayedData[index]?.dailyVolumePercentage?.toFixed(2) + "%"
|
||||
: "< 0.01%"}
|
||||
</div>
|
||||
|
||||
<div
|
||||
style="justify-content: center;"
|
||||
class="td text-sm sm:text-[1rem] text-white text-end"
|
||||
>
|
||||
{new Intl.NumberFormat("en", {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(displayedData[index]?.volume)}
|
||||
{displayedData[index]?.avgVolume > 0.01
|
||||
? displayedData[index]?.avgVolume?.toFixed(2) + "%"
|
||||
: "< 0.01%"}
|
||||
</div>
|
||||
|
||||
<div
|
||||
style="justify-content: center;"
|
||||
class="td text-sm sm:text-[1rem] text-white text-end truncate"
|
||||
style="justify-content: start;"
|
||||
class="td text-sm sm:text-[1rem] text-white text-start"
|
||||
>
|
||||
Healthcare
|
||||
{displayedData[index]?.sector?.length > 13
|
||||
? displayedData[index]?.sector?.slice(0, 13) + "..."
|
||||
: displayedData[index]?.sector}
|
||||
</div>
|
||||
|
||||
<div
|
||||
style="justify-content: center;"
|
||||
class="td text-sm sm:text-[1rem] text-white text-end"
|
||||
>
|
||||
Stock
|
||||
{displayedData[index]?.assetType}
|
||||
</div>
|
||||
</div>
|
||||
</VirtualList>
|
||||
@ -537,7 +496,6 @@
|
||||
.th {
|
||||
display: none;
|
||||
font-weight: 700;
|
||||
background-color: #09090b;
|
||||
}
|
||||
|
||||
.th > .td {
|
||||
@ -571,27 +529,4 @@
|
||||
min-width: 0px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.heartbeat {
|
||||
animation: heartbeat-animation 0.3s;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes heartbeat-animation {
|
||||
0% {
|
||||
transform: rotate(0deg) scale(0.95);
|
||||
}
|
||||
25% {
|
||||
transform: rotate(10deg) scale(1.05);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(0deg) scale(1.2);
|
||||
}
|
||||
75% {
|
||||
transform: rotate(-10deg) scale(1.05);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(0deg) scale(0.95);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -657,11 +657,11 @@ export function abbreviateNumber(number, addDollarSign = false, color = false) {
|
||||
|
||||
if (color) {
|
||||
if (suffix === "K") {
|
||||
suffix = '<span class=\"font-semibold text-[#A4F720]\">K</span>';
|
||||
suffix = '<span class=\"font-semibold text-[#8374DC]\">K</span>';
|
||||
} else if (suffix === "M") {
|
||||
suffix = '<span class=\"font-semibold text-[#A4F720]\">M</span>';
|
||||
suffix = '<span class=\"font-semibold text-[#FACD38]\">M</span>';
|
||||
} else if (suffix === "B") {
|
||||
suffix = '<span class=\"font-semibold text-[#A4F720]\">B</span>';
|
||||
suffix = '<span class=\"font-semibold text-[#FACD38]\">B</span>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
export const load = async ({ locals, cookies }) => {
|
||||
const { apiURL, apiKey, pb, user } = locals;
|
||||
export const load = async ({ locals }) => {
|
||||
const { apiURL, apiKey } = locals;
|
||||
|
||||
const getOptionsFlowFeed = async () => {
|
||||
const getFlowData = async () => {
|
||||
// make the POST request to the endpoint
|
||||
const response = await fetch(apiURL + "/options-flow-feed", {
|
||||
const response = await fetch(apiURL + "/dark-pool-flow-feed", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@ -15,42 +15,8 @@ export const load = async ({ locals, cookies }) => {
|
||||
return output;
|
||||
};
|
||||
|
||||
const getPredefinedCookieRuleOfList = async () => {
|
||||
// make the POST request to the endpoint
|
||||
const ruleOfList = cookies.get("options-flow-filter-cookie") ?? [];
|
||||
const output =
|
||||
ruleOfList?.length !== 0
|
||||
? JSON.parse(ruleOfList)
|
||||
: [
|
||||
{ name: "cost_basis", value: "any" },
|
||||
{ name: "date_expiration", value: "any" },
|
||||
];
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
const getOptionsWatchlist = async () => {
|
||||
let output;
|
||||
try {
|
||||
output = (
|
||||
await pb?.collection("optionsWatchlist").getFullList({
|
||||
filter: `user="${user?.id}"`,
|
||||
})
|
||||
)?.at(0);
|
||||
if (output === undefined) {
|
||||
output = { optionsId: [] };
|
||||
}
|
||||
} catch (e) {
|
||||
//console.log(e)
|
||||
output = { optionsId: [] };
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
// Make sure to return a promise
|
||||
return {
|
||||
getOptionsFlowFeed: await getOptionsFlowFeed(),
|
||||
getPredefinedCookieRuleOfList: await getPredefinedCookieRuleOfList(),
|
||||
getOptionsWatchlist: await getOptionsWatchlist(),
|
||||
getFlowData: await getFlowData(),
|
||||
};
|
||||
};
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js";
|
||||
import * as Popover from "$lib/components/shadcn/popover/index.js";
|
||||
import { Button } from "$lib/components/shadcn/button/index.js";
|
||||
import { Calendar } from "$lib/components/shadcn/calendar/index.js";
|
||||
import CalendarIcon from "lucide-svelte/icons/calendar";
|
||||
|
||||
import { page } from "$app/stores";
|
||||
@ -25,8 +24,6 @@
|
||||
export let data;
|
||||
let shouldLoadWorker = writable(false);
|
||||
|
||||
let optionsWatchlist = data?.getOptionsWatchlist;
|
||||
|
||||
let ruleOfList = data?.getPredefinedCookieRuleOfList || [];
|
||||
|
||||
let displayRules = [];
|
||||
@ -62,25 +59,7 @@
|
||||
defaultCondition: "over",
|
||||
defaultValue: "any",
|
||||
},
|
||||
open_interest: {
|
||||
label: "Open Interest",
|
||||
step: ["100K", "10K", "1K"],
|
||||
defaultCondition: "over",
|
||||
defaultValue: "any",
|
||||
},
|
||||
volumeOIRatio: {
|
||||
label: "Volume / Open Interest",
|
||||
step: ["100%", "80%", "60%", "50%", "30%", "15%", "10%", "5%"],
|
||||
defaultCondition: "over",
|
||||
defaultValue: "any",
|
||||
},
|
||||
sizeOIRatio: {
|
||||
label: "Size / Open Interest",
|
||||
step: ["100%", "80%", "60%", "50%", "30%", "15%", "10%", "5%"],
|
||||
defaultCondition: "over",
|
||||
defaultValue: "any",
|
||||
},
|
||||
cost_basis: {
|
||||
premium: {
|
||||
label: "Premium",
|
||||
step: [
|
||||
"10M",
|
||||
@ -98,43 +77,12 @@
|
||||
defaultCondition: "over",
|
||||
defaultValue: "any",
|
||||
},
|
||||
moneyness: {
|
||||
label: "Moneyness",
|
||||
step: ["ITM", "OTM"],
|
||||
defaultValue: "any",
|
||||
},
|
||||
flowType: {
|
||||
label: "Flow Type",
|
||||
step: ["Repeated Flow"],
|
||||
defaultValue: "any",
|
||||
},
|
||||
put_call: {
|
||||
label: "Contract Type",
|
||||
step: ["Calls", "Puts"],
|
||||
defaultValue: "any",
|
||||
},
|
||||
sentiment: {
|
||||
label: "Sentiment",
|
||||
step: ["Bullish", "Neutral", "Bearish"],
|
||||
defaultValue: "any",
|
||||
},
|
||||
execution_estimate: {
|
||||
label: "Execution",
|
||||
step: ["Above Ask", "Below Bid", "At Ask", "At Bid", "At Midpoint"],
|
||||
defaultValue: "any",
|
||||
},
|
||||
option_activity_type: {
|
||||
label: "Option Type",
|
||||
step: ["Sweep", "Trade"],
|
||||
defaultValue: "any",
|
||||
},
|
||||
date_expiration: {
|
||||
label: "Date Expiration",
|
||||
step: ["250", "180", "100", "80", "60", "50", "30", "20", "10", "5", "0"],
|
||||
defaultCondition: "over",
|
||||
defaultValue: "any",
|
||||
},
|
||||
underlying_type: {
|
||||
assetType: {
|
||||
label: "Asset Type",
|
||||
step: ["Stock", "ETF"],
|
||||
defaultValue: "any",
|
||||
@ -148,7 +96,7 @@
|
||||
"sentiment",
|
||||
"execution_estimate",
|
||||
"option_activity_type",
|
||||
"underlying_type",
|
||||
"assetType",
|
||||
];
|
||||
|
||||
// Generate allRows from allRules
|
||||
@ -199,7 +147,7 @@
|
||||
ruleOfList?.some((rule) => rule.name === row.rule),
|
||||
);
|
||||
shouldLoadWorker.set(true);
|
||||
await saveCookieRuleOfList();
|
||||
//await saveCookieRuleOfList();
|
||||
}
|
||||
|
||||
async function handleResetAll() {
|
||||
@ -217,7 +165,7 @@
|
||||
ruleOfList.some((rule) => rule.name === row.rule),
|
||||
);
|
||||
displayedData = rawData;
|
||||
await saveCookieRuleOfList();
|
||||
//await saveCookieRuleOfList();
|
||||
}
|
||||
|
||||
function changeRule(state: string) {
|
||||
@ -249,7 +197,7 @@
|
||||
case "underlying_type":
|
||||
newRule = {
|
||||
name: ruleName,
|
||||
value: Array.isArray(valueMappings[ruleName])
|
||||
value: Array?.isArray(valueMappings[ruleName])
|
||||
? valueMappings[ruleName]
|
||||
: [valueMappings[ruleName]],
|
||||
}; // Ensure value is an array
|
||||
@ -310,7 +258,6 @@
|
||||
filteredData = event.data?.filteredData ?? [];
|
||||
displayedData = filteredData;
|
||||
console.log("handle Message");
|
||||
calculateStats(displayedData);
|
||||
|
||||
//console.log(displayedData)
|
||||
};
|
||||
@ -435,7 +382,7 @@
|
||||
|
||||
// Trigger worker load and save cookie
|
||||
shouldLoadWorker.set(true);
|
||||
await saveCookieRuleOfList();
|
||||
//await saveCookieRuleOfList();
|
||||
}
|
||||
|
||||
async function stepSizeValue(value, condition) {
|
||||
@ -467,12 +414,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
const currentTime = new Date(
|
||||
new Date().toLocaleString("en-US", { timeZone: "America/New_York" }),
|
||||
)?.getTime();
|
||||
|
||||
const nyseDate = new Date(
|
||||
data?.getOptionsFlowFeed?.at(0)?.date ?? null,
|
||||
data?.getFlowData?.at(0)?.date ?? null,
|
||||
)?.toLocaleString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
@ -480,7 +423,7 @@
|
||||
timeZone: "Europe/Berlin",
|
||||
});
|
||||
|
||||
let rawData = data?.getOptionsFlowFeed?.filter((item) =>
|
||||
let rawData = data?.getFlowData?.filter((item) =>
|
||||
Object?.values(item)?.every(
|
||||
(value) =>
|
||||
value !== null &&
|
||||
@ -491,9 +434,6 @@
|
||||
)),
|
||||
),
|
||||
);
|
||||
rawData?.forEach((item) => {
|
||||
item.dte = daysLeft(item?.date_expiration);
|
||||
});
|
||||
|
||||
let displayedData = [];
|
||||
|
||||
@ -517,7 +457,7 @@
|
||||
mode = !mode;
|
||||
if (mode === true && selectedDate !== undefined) {
|
||||
selectedDate = undefined;
|
||||
rawData = data?.getOptionsFlowFeed;
|
||||
rawData = data?.getFlowData;
|
||||
displayedData = [...rawData];
|
||||
shouldLoadWorker.set(true);
|
||||
}
|
||||
@ -527,17 +467,12 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
async function websocketRealtimeData() {
|
||||
newData = [];
|
||||
try {
|
||||
socket = new WebSocket(data?.wsURL + "/options-flow-reader");
|
||||
/*
|
||||
socket.addEventListener("open", () => {
|
||||
const ids = rawData.map(item => item.id);
|
||||
sendMessage(JSON.stringify({ ids }));
|
||||
});
|
||||
*/
|
||||
|
||||
|
||||
socket.addEventListener("message", (event) => {
|
||||
const totalVolume = displayCallVolume + displayPutVolume;
|
||||
@ -567,11 +502,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if (previousCallVolume !== displayCallVolume && !muted && audio) {
|
||||
audio?.play();
|
||||
}
|
||||
*/
|
||||
|
||||
} catch (e) {
|
||||
console.error("Error processing WebSocket message:", e);
|
||||
}
|
||||
@ -603,16 +534,7 @@
|
||||
setTimeout(() => websocketRealtimeData(), 400);
|
||||
}
|
||||
}
|
||||
|
||||
function daysLeft(targetDate) {
|
||||
const targetTime = new Date(targetDate).getTime();
|
||||
const difference = targetTime - currentTime;
|
||||
|
||||
const millisecondsPerDay = 1000 * 60 * 60 * 24;
|
||||
const daysLeft = Math?.ceil(difference / millisecondsPerDay);
|
||||
|
||||
return daysLeft;
|
||||
}
|
||||
*/
|
||||
|
||||
async function saveCookieRuleOfList() {
|
||||
const postData = {
|
||||
@ -628,9 +550,11 @@
|
||||
}); // make a POST request to the server with the FormData object
|
||||
}
|
||||
|
||||
/*
|
||||
$: if ($isOpen) {
|
||||
websocketRealtimeData();
|
||||
}
|
||||
*/
|
||||
|
||||
onMount(async () => {
|
||||
if (filterQuery?.length > 0) {
|
||||
@ -647,7 +571,6 @@
|
||||
|
||||
audio = new Audio(notifySound);
|
||||
displayedData = rawData;
|
||||
calculateStats(rawData);
|
||||
|
||||
if (!syncWorker) {
|
||||
const SyncWorker = await import("./workers/filterWorker?worker");
|
||||
@ -678,65 +601,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
function calculateStats(data) {
|
||||
const {
|
||||
callVolumeSum,
|
||||
putVolumeSum,
|
||||
bullishCount,
|
||||
bearishCount,
|
||||
neutralCount,
|
||||
} = data?.reduce(
|
||||
(acc, item) => {
|
||||
const volume = parseInt(item?.volume);
|
||||
|
||||
if (item?.put_call === "Calls") {
|
||||
acc.callVolumeSum += volume;
|
||||
} else if (item?.put_call === "Puts") {
|
||||
acc.putVolumeSum += volume;
|
||||
}
|
||||
|
||||
if (item?.sentiment === "Bullish") {
|
||||
acc.bullishCount += 1;
|
||||
} else if (item?.sentiment === "Bearish") {
|
||||
acc.bearishCount += 1;
|
||||
} else if (item?.sentiment === "Neutral") {
|
||||
acc.neutralCount += 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
callVolumeSum: 0,
|
||||
putVolumeSum: 0,
|
||||
bullishCount: 0,
|
||||
bearishCount: 0,
|
||||
neutralCount: 0,
|
||||
},
|
||||
);
|
||||
|
||||
if (bullishCount > bearishCount) {
|
||||
flowSentiment = "Bullish";
|
||||
} else if (bullishCount < bearishCount) {
|
||||
flowSentiment = "Bearish";
|
||||
} else if (neutralCount > bearishCount && neutralCount > bullishCount) {
|
||||
flowSentiment = "Neutral";
|
||||
} else {
|
||||
flowSentiment = "-";
|
||||
}
|
||||
|
||||
putCallRatio = callVolumeSum !== 0 ? putVolumeSum / callVolumeSum : 0;
|
||||
|
||||
callPercentage =
|
||||
callVolumeSum + putVolumeSum !== 0
|
||||
? Math.floor((callVolumeSum / (callVolumeSum + putVolumeSum)) * 100)
|
||||
: 0;
|
||||
putPercentage =
|
||||
callVolumeSum + putVolumeSum !== 0 ? 100 - callPercentage : 0;
|
||||
|
||||
displayCallVolume = callVolumeSum;
|
||||
displayPutVolume = putVolumeSum;
|
||||
}
|
||||
|
||||
/*
|
||||
const getHistoricalFlow = async () => {
|
||||
// Create a delay using setTimeout wrapped in a Promise
|
||||
if (data?.user?.tier === "Pro") {
|
||||
@ -747,7 +612,6 @@
|
||||
ruleOfList.some((rule) => rule.name === row.rule),
|
||||
);
|
||||
displayedData = [];
|
||||
calculateStats(displayedData);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
|
||||
@ -769,12 +633,7 @@
|
||||
});
|
||||
|
||||
rawData = await response?.json();
|
||||
if (rawData?.length !== 0) {
|
||||
rawData?.forEach((item) => {
|
||||
item.dte = daysLeft(item?.date_expiration);
|
||||
});
|
||||
shouldLoadWorker.set(true);
|
||||
}
|
||||
shouldLoadWorker.set(true);
|
||||
} catch (error) {
|
||||
console.error("Error fetching historical flow:", error);
|
||||
rawData = [];
|
||||
@ -787,6 +646,7 @@
|
||||
});
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
function handleInput(event) {
|
||||
filterQuery = event.target.value;
|
||||
@ -863,7 +723,7 @@
|
||||
|
||||
<body class="overflow-y-auto">
|
||||
<section
|
||||
class="w-full max-w-screen sm:max-w-7xl xl:max-w-screen-2xl flex justify-center items-center bg-[#09090B] pb-20"
|
||||
class="w-full max-w-screen sm:max-w-7xl xl:max-w-screen-2xl flex justify-center items-center bg-[#09090B] pb-20 mt-5 sm:mt-0 px-3 sm:px-0"
|
||||
>
|
||||
<div class="w-full m-auto min-h-screen">
|
||||
<!--
|
||||
@ -884,8 +744,7 @@
|
||||
: nyseDate} (NYSE Time)
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="rounded-md border border-gray-700 bg-[#1E222D] p-2">
|
||||
<div class="rounded-md border border-gray-700 bg-[#121217] p-2">
|
||||
<div
|
||||
class="flex flex-col sm:flex-row items-center pt-3 sm:pt-1 pb-3 sm:border-b sm:border-gray-600"
|
||||
>
|
||||
@ -936,11 +795,14 @@
|
||||
class="inline-flex items-center cursor-pointer focus-none focus:outline-none"
|
||||
>
|
||||
<input
|
||||
on:click={toggleMode}
|
||||
on:click={() =>
|
||||
toast("Feature is coming soon 🔥", {
|
||||
style:
|
||||
"border-radius: 200px; background: #272B37; color: #fff; border: 1px solid #4B5563; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);",
|
||||
})}
|
||||
type="checkbox"
|
||||
checked={mode}
|
||||
value={mode}
|
||||
disabled={!$isOpen}
|
||||
class="sr-only peer"
|
||||
/>
|
||||
|
||||
@ -963,14 +825,14 @@
|
||||
<div class="sm:ml-auto w-full sm:w-fit pt-5">
|
||||
<div class="relative flex flex-col sm:flex-row items-center">
|
||||
<div
|
||||
class="relative w-full sm:w-fit pl-3 sm:mr-5 mb-4 sm:mb-0 flex-auto text-center bg-[#2A2E39] rounded-md border border-gray-600"
|
||||
class="relative w-full sm:w-fit pl-3 sm:mr-5 mb-4 sm:mb-0 flex-auto text-center bg-[#19191F] rounded-md border border-gray-600"
|
||||
>
|
||||
<label class="flex flex-row items-center">
|
||||
<input
|
||||
id="modal-search"
|
||||
type="search"
|
||||
class="text-white sm:ml-2 text-[1rem] placeholder-gray-300 border-transparent focus:border-transparent focus:ring-0 flex items-center justify-center w-full px-0 py-1.5 bg-inherit"
|
||||
placeholder="Search AAPL, SPY,..."
|
||||
placeholder="Stock or ETF symbol..."
|
||||
bind:value={filterQuery}
|
||||
on:input={debouncedHandleInput}
|
||||
autocomplete="off"
|
||||
@ -997,6 +859,11 @@
|
||||
<Popover.Root>
|
||||
<Popover.Trigger asChild let:builder>
|
||||
<Button
|
||||
on:click={() =>
|
||||
toast("Feature is coming soon 🔥", {
|
||||
style:
|
||||
"border-radius: 200px; background: #272B37; color: #fff; border: 1px solid #4B5563; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);",
|
||||
})}
|
||||
class={cn(
|
||||
"w-full sm:w-[160px] truncate sm:mr-3 py-3 bg-[#000] sm:hover:bg-[#000] sm:hover:text-white text-white justify-center sm:justify-start text-center sm:text-left font-normal border-none rounded-md",
|
||||
!selectedDate && "text-gray-300",
|
||||
@ -1004,19 +871,9 @@
|
||||
builders={[builder]}
|
||||
>
|
||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||
{selectedDate
|
||||
? df.format(selectedDate?.toDate())
|
||||
: "Pick a date"}
|
||||
Pick a Date
|
||||
</Button>
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="w-auto p-0 border-gray-500">
|
||||
<Calendar
|
||||
class="bg-[#09090B] text-white"
|
||||
bind:value={selectedDate}
|
||||
initialFocus
|
||||
onValueChange={getHistoricalFlow}
|
||||
/>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
</div>
|
||||
</div>
|
||||
@ -1442,212 +1299,11 @@
|
||||
</div>
|
||||
|
||||
{#if isLoaded}
|
||||
<div class="w-full mt-5 m-auto flex justify-center items-center">
|
||||
<div class="w-full grid grid-cols-1 lg:grid-cols-4 gap-y-3 gap-x-3">
|
||||
<!--Start Flow Sentiment-->
|
||||
<div
|
||||
class="flex flex-row items-center flex-wrap w-full px-5 bg-[#1E222D] shadow-lg rounded-md h-20"
|
||||
>
|
||||
<div class="flex flex-col items-start">
|
||||
<span class="font-semibold text-gray-200 text-sm sm:text-[1rem]"
|
||||
>Flow Sentiment</span
|
||||
>
|
||||
<span
|
||||
class="text-start text-[1rem] font-semibold {flowSentiment ===
|
||||
'Bullish'
|
||||
? 'text-[#00FC50]'
|
||||
: flowSentiment === 'Bearish'
|
||||
? 'text-[#FF2F1F]'
|
||||
: flowSentiment === 'Neutral'
|
||||
? 'text-[#fff]'
|
||||
: 'text-white'}">{flowSentiment}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!--End Flow Sentiment-->
|
||||
<!--Start Put/Call-->
|
||||
<div
|
||||
class="flex flex-row items-center flex-wrap w-full px-5 bg-[#1E222D] shadow-lg rounded-md h-20"
|
||||
>
|
||||
<div class="flex flex-col items-start">
|
||||
<span class="font-semibold text-gray-200 text-sm sm:text-[1rem]"
|
||||
>Put/Call</span
|
||||
>
|
||||
<span class="text-start text-[1rem] font-semibold text-white">
|
||||
{putCallRatio?.toFixed(3)}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Circular Progress -->
|
||||
<div class="relative size-14 ml-auto">
|
||||
<svg
|
||||
class="size-full w-14 h-14"
|
||||
viewBox="0 0 36 36"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<!-- Background Circle -->
|
||||
<circle
|
||||
cx="18"
|
||||
cy="18"
|
||||
r="16"
|
||||
fill="none"
|
||||
class="stroke-current text-[#3E3E3E]"
|
||||
stroke-width="3"
|
||||
></circle>
|
||||
<!-- Progress Circle inside a group with rotation -->
|
||||
<g class="origin-center -rotate-90 transform">
|
||||
<circle
|
||||
cx="18"
|
||||
cy="18"
|
||||
r="16"
|
||||
fill="none"
|
||||
class="stroke-current text-blue-500"
|
||||
stroke-width="3"
|
||||
stroke-dasharray="100"
|
||||
stroke-dashoffset={putCallRatio >= 1
|
||||
? 0
|
||||
: 100 - (putCallRatio * 100)?.toFixed(2)}
|
||||
></circle>
|
||||
</g>
|
||||
</svg>
|
||||
<!-- Percentage Text -->
|
||||
<div
|
||||
class="absolute top-1/2 start-1/2 transform -translate-y-1/2 -translate-x-1/2"
|
||||
>
|
||||
<span class="text-center text-white text-sm"
|
||||
>{putCallRatio?.toFixed(2)}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Circular Progress -->
|
||||
</div>
|
||||
<!--End Put/Call-->
|
||||
<!--Start Call Flow-->
|
||||
<div
|
||||
class="flex flex-row items-center flex-wrap w-full px-5 bg-[#1E222D] shadow-lg rounded-md h-20"
|
||||
>
|
||||
<div class="flex flex-col items-start">
|
||||
<span class="font-semibold text-gray-200 text-sm sm:text-[1rem]"
|
||||
>Call Flow</span
|
||||
>
|
||||
<span class="text-start text-[1rem] font-semibold text-white">
|
||||
{new Intl.NumberFormat("en", {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(displayCallVolume)}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Circular Progress -->
|
||||
<div class="relative size-14 ml-auto">
|
||||
<svg
|
||||
class="size-full w-14 h-14"
|
||||
viewBox="0 0 36 36"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<!-- Background Circle -->
|
||||
<circle
|
||||
cx="18"
|
||||
cy="18"
|
||||
r="16"
|
||||
fill="none"
|
||||
class="stroke-current text-[#3E3E3E]"
|
||||
stroke-width="3"
|
||||
></circle>
|
||||
<!-- Progress Circle inside a group with rotation -->
|
||||
<g class="origin-center -rotate-90 transform">
|
||||
<circle
|
||||
cx="18"
|
||||
cy="18"
|
||||
r="16"
|
||||
fill="none"
|
||||
class="stroke-current text-[#00FC50]"
|
||||
stroke-width="3"
|
||||
stroke-dasharray="100"
|
||||
stroke-dashoffset={100 - callPercentage?.toFixed(2)}
|
||||
></circle>
|
||||
</g>
|
||||
</svg>
|
||||
<!-- Percentage Text -->
|
||||
<div
|
||||
class="absolute top-1/2 start-1/2 transform -translate-y-1/2 -translate-x-1/2"
|
||||
>
|
||||
<span class="text-center text-white text-sm"
|
||||
>{callPercentage}%</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Circular Progress -->
|
||||
</div>
|
||||
<!--End Call Flow-->
|
||||
<!--Start Put Flow-->
|
||||
<div
|
||||
class="flex flex-row items-center flex-wrap w-full px-5 bg-[#1E222D] shadow-lg rounded-md h-20"
|
||||
>
|
||||
<div class="flex flex-col items-start">
|
||||
<span class="font-semibold text-gray-200 text-sm sm:text-[1rem]"
|
||||
>Put Flow</span
|
||||
>
|
||||
<span class="text-start text-[1rem] font-semibold text-white">
|
||||
{new Intl.NumberFormat("en", {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(displayPutVolume)}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Circular Progress -->
|
||||
<div class="relative size-14 ml-auto">
|
||||
<svg
|
||||
class="size-full w-14 h-14"
|
||||
viewBox="0 0 36 36"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<!-- Background Circle -->
|
||||
<circle
|
||||
cx="18"
|
||||
cy="18"
|
||||
r="16"
|
||||
fill="none"
|
||||
class="stroke-current text-[#3E3E3E]"
|
||||
stroke-width="3"
|
||||
></circle>
|
||||
<!-- Progress Circle inside a group with rotation -->
|
||||
<g class="origin-center -rotate-90 transform">
|
||||
<circle
|
||||
cx="18"
|
||||
cy="18"
|
||||
r="16"
|
||||
fill="none"
|
||||
class="stroke-current text-[#EE5365]"
|
||||
stroke-width="3"
|
||||
stroke-dasharray="100"
|
||||
stroke-dashoffset={100 - putPercentage?.toFixed(2)}
|
||||
></circle>
|
||||
</g>
|
||||
</svg>
|
||||
<!-- Percentage Text -->
|
||||
<div
|
||||
class="absolute top-1/2 start-1/2 transform -translate-y-1/2 -translate-x-1/2"
|
||||
>
|
||||
<span class="text-center text-white text-sm"
|
||||
>{putPercentage}%</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Circular Progress -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Page wrapper -->
|
||||
<div class="flex w-full m-auto h-full overflow-hidden">
|
||||
{#if displayedData?.length !== 0}
|
||||
<div class="mt-8 w-full overflow-x-auto h-[850px] overflow-hidden">
|
||||
<DarkPoolTable
|
||||
{data}
|
||||
{optionsWatchlist}
|
||||
{displayedData}
|
||||
{filteredData}
|
||||
{rawData}
|
||||
/>
|
||||
<div class="mt-3 w-full overflow-x-auto h-[850px] overflow-hidden">
|
||||
<DarkPoolTable {data} {displayedData} {filteredData} {rawData} />
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user