From 1ff20190fa32d9d0903dbecb49bca49a4186f116 Mon Sep 17 00:00:00 2001 From: MuslemRahimi Date: Wed, 26 Mar 2025 01:46:26 +0100 Subject: [PATCH] ui fix --- .../components/Options/ContractLookup.svelte | 387 +++++++----------- .../components/Options/GreekByStrike.svelte | 2 +- 2 files changed, 143 insertions(+), 246 deletions(-) diff --git a/src/lib/components/Options/ContractLookup.svelte b/src/lib/components/Options/ContractLookup.svelte index 242c968a..9c958d49 100644 --- a/src/lib/components/Options/ContractLookup.svelte +++ b/src/lib/components/Options/ContractLookup.svelte @@ -4,6 +4,7 @@ import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js"; import { Button } from "$lib/components/shadcn/button/index.js"; import Infobox from "$lib/components/Infobox.svelte"; + import { onMount } from "svelte"; import highcharts from "$lib/highcharts.ts"; import { mode } from "mode-watcher"; @@ -23,15 +24,15 @@ let strikeList = optionData[selectedDate] || []; let selectedStrike = strikeList?.at(0) || []; + let optionSymbol = buildOptionSymbol( + selectedDate, + selectedOptionType, + selectedStrike, + ); - let optionHistoryList = []; + let displayList = []; let selectGraphType = "Vol/OI"; - let container; let rawDataHistory = []; - let strikePrice; - let optionType; - let dateExpiration; - let otmPercentage; const formatDate = (dateString) => { const date = new Date(dateString); @@ -41,47 +42,24 @@ year: "numeric", }); }; - function computeOTM(strikePrice, optionType) { - // Get the current stock price - const currentPrice = data?.getStockQuote?.price; - let otmPercentage = 0; + function buildOptionSymbol(dateExpiration, optionType, strikePrice) { + // Format the expiration date as YYMMDD + const date = new Date(dateExpiration); + const year = date.getFullYear() % 100; // Last two digits of the year + const month = (date.getMonth() + 1).toString().padStart(2, "0"); // Months are 0-indexed + const day = date.getDate().toString().padStart(2, "0"); + const expirationStr = `${year}${month}${day}`; - 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"; - } + // Convert option type to a single uppercase letter (C for Call, P for Put) + const optionTypeChar = optionType.charAt(0).toUpperCase(); - return otmPercentage; // Return the percentage rounded to two decimal places - } + // Format strike price as 8 digits (multiply by 1000 and pad with leading zeros) + const strikePriceScaled = Math.round(strikePrice * 1000); + const strikeStr = strikePriceScaled.toString().padStart(8, "0"); - function getScroll() { - const scrollThreshold = container.scrollHeight * 0.8; // 80% of the container height - - // Check if the user has scrolled to the bottom based on the threshold - const isBottom = - container.scrollTop + container.clientHeight >= scrollThreshold; - - // Only load more data if at the bottom and there is still data to load - if (isBottom && optionHistoryList?.length !== rawDataHistory?.length) { - const nextIndex = optionHistoryList.length; // Ensure optionHistoryList is defined - const filteredNewResults = rawDataHistory.slice( - nextIndex, - nextIndex + 25, - ); // Ensure rawData is defined - optionHistoryList = [...optionHistoryList, ...filteredNewResults]; - } + // Combine all components into the final option symbol + return `${ticker}${expirationStr}${optionTypeChar}${strikeStr}`; } const currentTime = new Date( @@ -115,20 +93,7 @@ }); } - let rawDataVolume = data?.getData?.volume?.map((item) => ({ - ...item, - dte: daysLeft(item?.date_expiration), - otm: computeOTM(item?.strike_price, item?.option_type), - })); - - let rawDataOI = data?.getData?.openInterest?.map((item) => ({ - ...item, - dte: daysLeft(item?.date_expiration), - otm: computeOTM(item?.strike_price, item?.option_type), - })); - - function plotContractHistory() { - // Ensure rawDataHistory exists and sort it by date + function plotData() { const sortedData = rawDataHistory?.sort((a, b) => new Date(a?.date) - new Date(b?.date)) || []; @@ -173,20 +138,20 @@ new Date(item.date).getTime(), item.mark, ]), - color: "#FAD776", + color: "#FF0006", yAxis: 2, animation: false, marker: { enabled: false }, }, { - name: "Price", + name: "Stock Price", type: "spline", yAxis: 1, data: filteredData.map((item) => [ new Date(item.date).getTime(), item.price, ]), - color: "#fff", + color: $mode === "light" ? "#005AFF" : "white", lineWidth: 1, marker: { enabled: false }, animation: false, @@ -213,21 +178,21 @@ new Date(item.date).getTime(), item.mark, ]), - color: "#FAD776", + color: "#FF0006", yAxis: 2, lineWidth: 1, animation: false, marker: { enabled: false }, }, { - name: "Price", + name: "Stock Price", type: "spline", yAxis: 1, data: filteredData.map((item) => [ new Date(item.date).getTime(), item.price, ]), - color: "#fff", + color: $mode === "light" ? "#005AFF" : "white", lineWidth: 1, marker: { enabled: false }, animation: false, @@ -242,16 +207,31 @@ animation: false, height: 360, }, + legend: { + enabled: true, + align: "left", // Positions legend at the left edge + verticalAlign: "top", // Positions legend at the top + layout: "horizontal", // Align items horizontally (use 'vertical' if preferred) + itemStyle: { + color: $mode === "light" ? "black" : "white", + }, + symbolWidth: 16, // Controls the width of the legend symbol + symbolRadius: 8, // Creates circular symbols (adjust radius as needed) + squareSymbol: false, // Ensures symbols are circular, not square + }, credits: { enabled: false }, title: { - text: `

Contract History

`, + text: `

${ticker} + ${formatDate(selectedDate)} + ${selectedStrike} + ${selectedOptionType}

`, useHTML: true, - style: { color: "white" }, + style: { color: $mode === "light" ? "black" : "white" }, }, // Disable markers globally on hover for all series plotOptions: { series: { - color: "white", + color: $mode === "light" ? "black" : "white", animation: false, // Disable series animation states: { hover: { @@ -264,12 +244,12 @@ type: "datetime", endOnTick: false, crosshair: { - color: "#fff", + color: $mode === "light" ? "black" : "white", width: 1, dashStyle: "Solid", }, labels: { - style: { color: "#fff" }, + style: { color: $mode === "light" ? "black" : "white" }, distance: 20, formatter: function () { return new Date(this.value).toLocaleDateString("en-US", { @@ -293,8 +273,8 @@ yAxis: [ { gridLineWidth: 1, - gridLineColor: "#111827", - labels: { style: { color: "white" } }, + gridLineColor: $mode === "light" ? "#d1d5dc" : "#111827", + labels: { style: { color: $mode === "light" ? "black" : "white" } }, title: { text: null }, opposite: true, }, @@ -327,7 +307,7 @@ }, borderRadius: 4, formatter: function () { - let tooltipContent = `${new Date( + let tooltipContent = `${new Date( this.x, ).toLocaleDateString("en-US", { year: "numeric", @@ -337,14 +317,13 @@ this.points.forEach((point) => { tooltipContent += `${point.series.name}: - ${abbreviateNumber( + ${abbreviateNumber( point.y, )}
`; }); return tooltipContent; }, }, - legend: { enabled: false }, series: series, }; @@ -379,54 +358,54 @@ return output; }; - async function handleViewData(item) { - isLoaded = false; - selectGraphType = "Vol/OI"; - optionDetailsDesktopModal?.showModal(); + async function handleScroll() { + const scrollThreshold = document.body.offsetHeight * 0.8; // 80% of the website height + const isBottom = window.innerHeight + window.scrollY >= scrollThreshold; - strikePrice = item?.strike_price; - optionType = item?.option_type; - dateExpiration = item?.date_expiration; - otmPercentage = item?.otm; - - const output = await getContractHistory(item?.option_symbol); - rawDataHistory = output?.history; - - if (rawDataHistory?.length > 0) { - rawDataHistory.forEach((entry) => { - const matchingData = data?.getHistoricalPrice?.find( - (d) => d?.time === entry?.date, - ); - if (matchingData) { - entry.price = matchingData?.close; - } - }); - - rawDataHistory = calculateDTE(rawDataHistory, dateExpiration); - config = plotContractHistory(); - rawDataHistory = rawDataHistory?.sort( - (a, b) => new Date(b?.date) - new Date(a?.date), + if (isBottom && displayList?.length !== rawDataHistory?.length) { + const nextIndex = displayList?.length; + const filteredNewResults = rawDataHistory?.slice( + nextIndex, + nextIndex + 50, ); - optionHistoryList = rawDataHistory?.slice(0, 20); - } else { - config = null; + displayList = [...displayList, ...filteredNewResults]; } - - isLoaded = true; } - $: { + onMount(async () => { if (selectGraphType) { isLoaded = false; + + const output = await getContractHistory(optionSymbol); + rawDataHistory = output?.history; if (rawDataHistory?.length > 0) { - config = plotContractHistory(); + rawDataHistory.forEach((entry) => { + const matchingData = data?.getHistoricalPrice?.find( + (d) => d?.time === entry?.date, + ); + if (matchingData) { + entry.price = matchingData?.close; + } + }); + + rawDataHistory = calculateDTE(rawDataHistory, selectedDate); + config = plotData(); + rawDataHistory = rawDataHistory?.sort( + (a, b) => new Date(b?.date) - new Date(a?.date), + ); + displayList = rawDataHistory?.slice(0, 20); } else { config = null; } isLoaded = true; } - } + + window.addEventListener("scroll", handleScroll); + return () => { + window.removeEventListener("scroll", handleScroll); + }; + });
@@ -454,9 +433,9 @@ >
-
+
Date Expiration
-
+
Strike Price
-
+
Option Type
-
-
-
- -