This commit is contained in:
MuslemRahimi 2025-01-05 23:27:10 +01:00
parent d6855b2be2
commit c5ba7a39fd
2 changed files with 122 additions and 88 deletions

View File

@ -41,6 +41,7 @@
let strikePrice; let strikePrice;
let optionType; let optionType;
let dateExpiration; let dateExpiration;
let otmPercentage;
function formatDate(dateStr) { function formatDate(dateStr) {
// Parse the input date string (YYYY-mm-dd) // Parse the input date string (YYYY-mm-dd)
@ -63,6 +64,36 @@
return formattedDate; return formattedDate;
} }
function computeOTM(strikePrice, optionType) {
// Get the current stock price
const currentPrice = data?.getStockQuote?.price;
// Ensure the current price and strike price are valid numbers
if (typeof currentPrice !== "number" || typeof strikePrice !== "number") {
throw new Error("Invalid current price or strike price");
}
let otmPercentage = 0;
if (optionType === "C") {
// Call option: OTM is positive if strike > currentPrice, negative (ITM) otherwise
otmPercentage = (
((strikePrice - currentPrice) / currentPrice) *
100
)?.toFixed(2);
} else if (optionType === "P") {
// Put option: OTM is positive if strike < currentPrice, negative (ITM) otherwise
otmPercentage = (
((currentPrice - strikePrice) / currentPrice) *
100
)?.toFixed(2);
} else {
otmPercentage = "n/a";
}
return otmPercentage; // Return the percentage rounded to two decimal places
}
function getScroll() { function getScroll() {
const scrollThreshold = container.scrollHeight * 0.8; // 80% of the container height const scrollThreshold = container.scrollHeight * 0.8; // 80% of the container height
@ -98,11 +129,13 @@
let rawDataVolume = data?.getData?.volume?.map((item) => ({ let rawDataVolume = data?.getData?.volume?.map((item) => ({
...item, ...item,
dte: daysLeft(item?.date_expiration), dte: daysLeft(item?.date_expiration),
otm: computeOTM(item?.strike_price, item?.option_type),
})); }));
let rawDataOI = data?.getData?.openInterest?.map((item) => ({ let rawDataOI = data?.getData?.openInterest?.map((item) => ({
...item, ...item,
dte: daysLeft(item?.date_expiration), dte: daysLeft(item?.date_expiration),
otm: computeOTM(item?.strike_price, item?.option_type),
})); }));
let volumeList = rawDataVolume; let volumeList = rawDataVolume;
@ -111,6 +144,7 @@
$: columns = [ $: columns = [
{ key: "strike_price", label: "Chain", align: "left" }, { key: "strike_price", label: "Chain", align: "left" },
{ key: "dte", label: "DTE", align: "right" }, { key: "dte", label: "DTE", align: "right" },
{ key: "otm", label: "% OTM", align: "right" },
{ key: "last_price", label: "Last", align: "right" }, { key: "last_price", label: "Last", align: "right" },
{ key: "high_price", label: "Low-High", align: "right" }, { key: "high_price", label: "Low-High", align: "right" },
{ key: "volume", label: "Volume", align: "right" }, { key: "volume", label: "Volume", align: "right" },
@ -123,6 +157,7 @@
$: sortOrders = { $: sortOrders = {
strike_price: { order: "none", type: "number" }, strike_price: { order: "none", type: "number" },
dte: { order: "none", type: "number" }, dte: { order: "none", type: "number" },
otm: { order: "none", type: "number" },
last_price: { order: "none", type: "number" }, last_price: { order: "none", type: "number" },
high_price: { order: "none", type: "number" }, high_price: { order: "none", type: "number" },
volume: { order: "none", type: "number" }, volume: { order: "none", type: "number" },
@ -142,9 +177,7 @@
// Cycle through 'none', 'asc', 'desc' for the clicked key // Cycle through 'none', 'asc', 'desc' for the clicked key
const orderCycle = ["none", "asc", "desc"]; const orderCycle = ["none", "asc", "desc"];
let originalData = rawDataVolume?.sort( let originalData = rawDataVolume;
(a, b) => b?.open_interest - a?.open_interest,
);
const currentOrderIndex = orderCycle.indexOf(sortOrders[key].order); const currentOrderIndex = orderCycle.indexOf(sortOrders[key].order);
sortOrders[key].order = sortOrders[key].order =
orderCycle[(currentOrderIndex + 1) % orderCycle.length]; orderCycle[(currentOrderIndex + 1) % orderCycle.length];
@ -201,9 +234,7 @@
// Cycle through 'none', 'asc', 'desc' for the clicked key // Cycle through 'none', 'asc', 'desc' for the clicked key
const orderCycle = ["none", "asc", "desc"]; const orderCycle = ["none", "asc", "desc"];
let originalData = rawDataOI?.sort( let originalData = rawDataOI;
(a, b) => b?.open_interest - a?.open_interest,
);
const currentOrderIndex = orderCycle.indexOf(sortOrders[key].order); const currentOrderIndex = orderCycle.indexOf(sortOrders[key].order);
sortOrders[key].order = sortOrders[key].order =
orderCycle[(currentOrderIndex + 1) % orderCycle.length]; orderCycle[(currentOrderIndex + 1) % orderCycle.length];
@ -264,7 +295,7 @@
let volumeList = data?.map((item) => item?.volume); let volumeList = data?.map((item) => item?.volume);
let oiList = data?.map((item) => item?.open_interest); let oiList = data?.map((item) => item?.open_interest);
let ivList = data?.map((item) => let ivList = data?.map((item) =>
Math.floor(item?.implied_volatility * 100), Math?.floor(item?.implied_volatility * 100),
); );
let series = []; let series = [];
if (selectGraphType === "Bid/Ask") { if (selectGraphType === "Bid/Ask") {
@ -359,7 +390,7 @@
{ {
name: "IV", name: "IV",
type: "line", type: "line",
data: volumeList, data: ivList,
itemStyle: { itemStyle: {
color: "#B24BF3", color: "#B24BF3",
}, },
@ -515,6 +546,7 @@
strikePrice = item?.strike_price; strikePrice = item?.strike_price;
optionType = item?.option_type; optionType = item?.option_type;
dateExpiration = item?.date_expiration; dateExpiration = item?.date_expiration;
otmPercentage = item?.otm;
rawDataHistory = await getContractHistory(item?.option_symbol); rawDataHistory = await getContractHistory(item?.option_symbol);
if (rawDataHistory?.length > 0) { if (rawDataHistory?.length > 0) {
@ -611,6 +643,11 @@
> >
{item?.dte} {item?.dte}
</td> </td>
<td
class="text-white text-sm sm:text-[1rem] text-end whitespace-nowrap"
>
{item?.otm}%
</td>
<td <td
class="text-white text-sm sm:text-[1rem] text-end whitespace-nowrap" class="text-white text-sm sm:text-[1rem] text-end whitespace-nowrap"
> >
@ -784,6 +821,11 @@
> >
{item?.dte} {item?.dte}
</td> </td>
<td
class="text-white text-sm sm:text-[1rem] text-end whitespace-nowrap"
>
{item?.otm}%
</td>
<td <td
class="text-white text-sm sm:text-[1rem] text-end whitespace-nowrap" class="text-white text-sm sm:text-[1rem] text-end whitespace-nowrap"
> >
@ -961,61 +1003,36 @@
></div> ></div>
<div class="hidden sm:flex flex-wrap text-white pb-2"> <div class="hidden sm:flex flex-wrap text-white pb-2">
<div <div class="mr-3 whitespace-nowrap">
class="mr-3 whitespace-nowrap"
data-state="closed"
data-sentry-element="unknown"
data-sentry-source-file="Tooltip.tsx"
>
{formatDate(optionHistoryList?.at(0)?.date)}: {formatDate(optionHistoryList?.at(0)?.date)}:
</div> </div>
<div <div class="mr-3 whitespace-nowrap">
class="mr-3 whitespace-nowrap"
data-state="closed"
data-sentry-element="unknown"
data-sentry-source-file="Tooltip.tsx"
>
<span class="text-[var(--light-text-color)] font-normal">Vol:</span> <span class="text-[var(--light-text-color)] font-normal">Vol:</span>
{optionHistoryList?.at(0)?.volume?.toLocaleString("en-US")} {optionHistoryList?.at(0)?.volume?.toLocaleString("en-US")}
</div> </div>
<div <div class="mr-3 whitespace-nowrap">
class="mr-3 whitespace-nowrap"
data-state="closed"
data-sentry-element="unknown"
data-sentry-source-file="Tooltip.tsx"
>
<span class="text-[var(--light-text-color)] font-normal">OI:</span> <span class="text-[var(--light-text-color)] font-normal">OI:</span>
{optionHistoryList?.at(0)?.open_interest?.toLocaleString("en-US")} {optionHistoryList?.at(0)?.open_interest?.toLocaleString("en-US")}
</div> </div>
<div <div class="mr-3 whitespace-nowrap">
class="mr-3 whitespace-nowrap"
data-state="closed"
data-sentry-element="unknown"
data-sentry-source-file="Tooltip.tsx"
>
<span class="text-[var(--light-text-color)] font-normal">Avg:</span> <span class="text-[var(--light-text-color)] font-normal">Avg:</span>
${optionHistoryList?.at(0)?.avg_price} ${optionHistoryList?.at(0)?.avg_price}
</div> </div>
<div <div class="mr-3 whitespace-nowrap">
class="mr-3 whitespace-nowrap"
data-state="closed"
data-sentry-element="unknown"
data-sentry-source-file="Tooltip.tsx"
>
<span class="text-[var(--light-text-color)] font-normal">Prem:</span> <span class="text-[var(--light-text-color)] font-normal">Prem:</span>
{abbreviateNumber(optionHistoryList?.at(0)?.total_premium, true)} {abbreviateNumber(optionHistoryList?.at(0)?.total_premium, true)}
</div> </div>
<div <div class="mr-3 whitespace-nowrap">
class="mr-3 whitespace-nowrap"
data-state="closed"
data-sentry-element="unknown"
data-sentry-source-file="Tooltip.tsx"
>
<span class="text-[var(--light-text-color)] font-normal">IV:</span> <span class="text-[var(--light-text-color)] font-normal">IV:</span>
{(optionHistoryList?.at(0)?.implied_volatility * 100)?.toLocaleString( {(optionHistoryList?.at(0)?.implied_volatility * 100)?.toLocaleString(
"en-US", "en-US",
)}% )}%
</div> </div>
<div class="mr-3 whitespace-nowrap">
<span class="text-[var(--light-text-color)] font-normal">OTM:</span>
{otmPercentage}%
</div>
</div> </div>
{#if $screenWidth > 640} {#if $screenWidth > 640}

View File

@ -41,6 +41,7 @@
let strikePrice; let strikePrice;
let optionType; let optionType;
let dateExpiration; let dateExpiration;
let otmPercentage;
function formatDate(dateStr) { function formatDate(dateStr) {
// Parse the input date string (YYYY-mm-dd) // Parse the input date string (YYYY-mm-dd)
@ -63,6 +64,36 @@
return formattedDate; return formattedDate;
} }
function computeOTM(strikePrice, optionType) {
// Get the current stock price
const currentPrice = data?.getStockQuote?.price;
// Ensure the current price and strike price are valid numbers
if (typeof currentPrice !== "number" || typeof strikePrice !== "number") {
throw new Error("Invalid current price or strike price");
}
let otmPercentage = 0;
if (optionType === "C") {
// Call option: OTM is positive if strike > currentPrice, negative (ITM) otherwise
otmPercentage = (
((strikePrice - currentPrice) / currentPrice) *
100
)?.toFixed(2);
} else if (optionType === "P") {
// Put option: OTM is positive if strike < currentPrice, negative (ITM) otherwise
otmPercentage = (
((currentPrice - strikePrice) / currentPrice) *
100
)?.toFixed(2);
} else {
otmPercentage = "n/a";
}
return otmPercentage; // Return the percentage rounded to two decimal places
}
function getScroll() { function getScroll() {
const scrollThreshold = container.scrollHeight * 0.8; // 80% of the container height const scrollThreshold = container.scrollHeight * 0.8; // 80% of the container height
@ -98,11 +129,13 @@
let rawDataVolume = data?.getData?.volume?.map((item) => ({ let rawDataVolume = data?.getData?.volume?.map((item) => ({
...item, ...item,
dte: daysLeft(item?.date_expiration), dte: daysLeft(item?.date_expiration),
otm: computeOTM(item?.strike_price, item?.option_type),
})); }));
let rawDataOI = data?.getData?.openInterest?.map((item) => ({ let rawDataOI = data?.getData?.openInterest?.map((item) => ({
...item, ...item,
dte: daysLeft(item?.date_expiration), dte: daysLeft(item?.date_expiration),
otm: computeOTM(item?.strike_price, item?.option_type),
})); }));
let volumeList = rawDataVolume; let volumeList = rawDataVolume;
@ -111,6 +144,7 @@
$: columns = [ $: columns = [
{ key: "strike_price", label: "Chain", align: "left" }, { key: "strike_price", label: "Chain", align: "left" },
{ key: "dte", label: "DTE", align: "right" }, { key: "dte", label: "DTE", align: "right" },
{ key: "otm", label: "% OTM", align: "right" },
{ key: "last_price", label: "Last", align: "right" }, { key: "last_price", label: "Last", align: "right" },
{ key: "high_price", label: "Low-High", align: "right" }, { key: "high_price", label: "Low-High", align: "right" },
{ key: "volume", label: "Volume", align: "right" }, { key: "volume", label: "Volume", align: "right" },
@ -123,6 +157,7 @@
$: sortOrders = { $: sortOrders = {
strike_price: { order: "none", type: "number" }, strike_price: { order: "none", type: "number" },
dte: { order: "none", type: "number" }, dte: { order: "none", type: "number" },
otm: { order: "none", type: "number" },
last_price: { order: "none", type: "number" }, last_price: { order: "none", type: "number" },
high_price: { order: "none", type: "number" }, high_price: { order: "none", type: "number" },
volume: { order: "none", type: "number" }, volume: { order: "none", type: "number" },
@ -142,9 +177,7 @@
// Cycle through 'none', 'asc', 'desc' for the clicked key // Cycle through 'none', 'asc', 'desc' for the clicked key
const orderCycle = ["none", "asc", "desc"]; const orderCycle = ["none", "asc", "desc"];
let originalData = rawDataVolume?.sort( let originalData = rawDataVolume;
(a, b) => b?.open_interest - a?.open_interest,
);
const currentOrderIndex = orderCycle.indexOf(sortOrders[key].order); const currentOrderIndex = orderCycle.indexOf(sortOrders[key].order);
sortOrders[key].order = sortOrders[key].order =
orderCycle[(currentOrderIndex + 1) % orderCycle.length]; orderCycle[(currentOrderIndex + 1) % orderCycle.length];
@ -201,9 +234,7 @@
// Cycle through 'none', 'asc', 'desc' for the clicked key // Cycle through 'none', 'asc', 'desc' for the clicked key
const orderCycle = ["none", "asc", "desc"]; const orderCycle = ["none", "asc", "desc"];
let originalData = rawDataOI?.sort( let originalData = rawDataOI;
(a, b) => b?.open_interest - a?.open_interest,
);
const currentOrderIndex = orderCycle.indexOf(sortOrders[key].order); const currentOrderIndex = orderCycle.indexOf(sortOrders[key].order);
sortOrders[key].order = sortOrders[key].order =
orderCycle[(currentOrderIndex + 1) % orderCycle.length]; orderCycle[(currentOrderIndex + 1) % orderCycle.length];
@ -264,7 +295,7 @@
let volumeList = data?.map((item) => item?.volume); let volumeList = data?.map((item) => item?.volume);
let oiList = data?.map((item) => item?.open_interest); let oiList = data?.map((item) => item?.open_interest);
let ivList = data?.map((item) => let ivList = data?.map((item) =>
Math.floor(item?.implied_volatility * 100), Math?.floor(item?.implied_volatility * 100),
); );
let series = []; let series = [];
if (selectGraphType === "Bid/Ask") { if (selectGraphType === "Bid/Ask") {
@ -359,7 +390,7 @@
{ {
name: "IV", name: "IV",
type: "line", type: "line",
data: volumeList, data: ivList,
itemStyle: { itemStyle: {
color: "#B24BF3", color: "#B24BF3",
}, },
@ -515,6 +546,7 @@
strikePrice = item?.strike_price; strikePrice = item?.strike_price;
optionType = item?.option_type; optionType = item?.option_type;
dateExpiration = item?.date_expiration; dateExpiration = item?.date_expiration;
otmPercentage = item?.otm;
rawDataHistory = await getContractHistory(item?.option_symbol); rawDataHistory = await getContractHistory(item?.option_symbol);
if (rawDataHistory?.length > 0) { if (rawDataHistory?.length > 0) {
@ -611,6 +643,11 @@
> >
{item?.dte} {item?.dte}
</td> </td>
<td
class="text-white text-sm sm:text-[1rem] text-end whitespace-nowrap"
>
{item?.otm}%
</td>
<td <td
class="text-white text-sm sm:text-[1rem] text-end whitespace-nowrap" class="text-white text-sm sm:text-[1rem] text-end whitespace-nowrap"
> >
@ -784,6 +821,11 @@
> >
{item?.dte} {item?.dte}
</td> </td>
<td
class="text-white text-sm sm:text-[1rem] text-end whitespace-nowrap"
>
{item?.otm}%
</td>
<td <td
class="text-white text-sm sm:text-[1rem] text-end whitespace-nowrap" class="text-white text-sm sm:text-[1rem] text-end whitespace-nowrap"
> >
@ -961,61 +1003,36 @@
></div> ></div>
<div class="hidden sm:flex flex-wrap text-white pb-2"> <div class="hidden sm:flex flex-wrap text-white pb-2">
<div <div class="mr-3 whitespace-nowrap">
class="mr-3 whitespace-nowrap"
data-state="closed"
data-sentry-element="unknown"
data-sentry-source-file="Tooltip.tsx"
>
{formatDate(optionHistoryList?.at(0)?.date)}: {formatDate(optionHistoryList?.at(0)?.date)}:
</div> </div>
<div <div class="mr-3 whitespace-nowrap">
class="mr-3 whitespace-nowrap"
data-state="closed"
data-sentry-element="unknown"
data-sentry-source-file="Tooltip.tsx"
>
<span class="text-[var(--light-text-color)] font-normal">Vol:</span> <span class="text-[var(--light-text-color)] font-normal">Vol:</span>
{optionHistoryList?.at(0)?.volume?.toLocaleString("en-US")} {optionHistoryList?.at(0)?.volume?.toLocaleString("en-US")}
</div> </div>
<div <div class="mr-3 whitespace-nowrap">
class="mr-3 whitespace-nowrap"
data-state="closed"
data-sentry-element="unknown"
data-sentry-source-file="Tooltip.tsx"
>
<span class="text-[var(--light-text-color)] font-normal">OI:</span> <span class="text-[var(--light-text-color)] font-normal">OI:</span>
{optionHistoryList?.at(0)?.open_interest?.toLocaleString("en-US")} {optionHistoryList?.at(0)?.open_interest?.toLocaleString("en-US")}
</div> </div>
<div <div class="mr-3 whitespace-nowrap">
class="mr-3 whitespace-nowrap"
data-state="closed"
data-sentry-element="unknown"
data-sentry-source-file="Tooltip.tsx"
>
<span class="text-[var(--light-text-color)] font-normal">Avg:</span> <span class="text-[var(--light-text-color)] font-normal">Avg:</span>
${optionHistoryList?.at(0)?.avg_price} ${optionHistoryList?.at(0)?.avg_price}
</div> </div>
<div <div class="mr-3 whitespace-nowrap">
class="mr-3 whitespace-nowrap"
data-state="closed"
data-sentry-element="unknown"
data-sentry-source-file="Tooltip.tsx"
>
<span class="text-[var(--light-text-color)] font-normal">Prem:</span> <span class="text-[var(--light-text-color)] font-normal">Prem:</span>
{abbreviateNumber(optionHistoryList?.at(0)?.total_premium, true)} {abbreviateNumber(optionHistoryList?.at(0)?.total_premium, true)}
</div> </div>
<div <div class="mr-3 whitespace-nowrap">
class="mr-3 whitespace-nowrap"
data-state="closed"
data-sentry-element="unknown"
data-sentry-source-file="Tooltip.tsx"
>
<span class="text-[var(--light-text-color)] font-normal">IV:</span> <span class="text-[var(--light-text-color)] font-normal">IV:</span>
{(optionHistoryList?.at(0)?.implied_volatility * 100)?.toLocaleString( {(optionHistoryList?.at(0)?.implied_volatility * 100)?.toLocaleString(
"en-US", "en-US",
)}% )}%
</div> </div>
<div class="mr-3 whitespace-nowrap">
<span class="text-[var(--light-text-color)] font-normal">OTM:</span>
{otmPercentage}%
</div>
</div> </div>
{#if $screenWidth > 640} {#if $screenWidth > 640}