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: `