diff --git a/src/lib/components/FailToDeliver.svelte b/src/lib/components/FailToDeliver.svelte index 1ca81ed3..2322d434 100644 --- a/src/lib/components/FailToDeliver.svelte +++ b/src/lib/components/FailToDeliver.svelte @@ -10,6 +10,7 @@ monthNames, removeCompanyStrings, } from "$lib/utils"; + import { goto } from "$app/navigation"; import highcharts from "$lib/highcharts.ts"; export let data; diff --git a/src/lib/components/Options/UnusualActivity.svelte b/src/lib/components/Options/UnusualActivity.svelte index 479011e2..8cad5266 100644 --- a/src/lib/components/Options/UnusualActivity.svelte +++ b/src/lib/components/Options/UnusualActivity.svelte @@ -9,20 +9,15 @@ import TableHeader from "$lib/components/Table/TableHeader.svelte"; import UpgradeToPro from "$lib/components/UpgradeToPro.svelte"; import Infobox from "$lib/components/Infobox.svelte"; - import { onMount } from "svelte"; - import { init, use } from "echarts/core"; - import { BarChart, LineChart } from "echarts/charts"; - import { GridComponent, TooltipComponent } from "echarts/components"; - import { CanvasRenderer } from "echarts/renderers"; - import { Chart } from "svelte-echarts"; + import highcharts from "$lib/highcharts.ts"; - use([BarChart, LineChart, GridComponent, TooltipComponent, CanvasRenderer]); + import { onMount } from "svelte"; export let data; export let ticker = null; let isLoaded = false; - let optionsData = null; + let configContract = null; let optionHistoryList = []; let selectGraphType = "Vol/OI"; @@ -43,7 +38,7 @@ let displayList = rawData?.slice(0, 150) || []; - let options = plotData(); + let configUnusual = plotData(); function daysLeft(targetDate) { const targetTime = new Date(targetDate).getTime(); @@ -118,170 +113,198 @@ (a, b) => new Date(a?.date) - new Date(b?.date), ); - // Map to aggregate call size, put size, and premiums for each date + // Aggregate call size, put size, and premiums for each date const aggregatedData = {}; history?.forEach((item) => { const { date, optionType, size, premium } = item; - // Initialize the date in aggregatedData if it doesn't exist if (!aggregatedData[date]) { aggregatedData[date] = { callSize: 0, putSize: 0, totalPremium: 0 }; } - // Aggregate call size, put size, and premium if (optionType === "Calls") { aggregatedData[date].callSize += size; } else if (optionType === "Puts") { aggregatedData[date].putSize += size; } - // Add premium aggregatedData[date].totalPremium += premium; }); - // Extract dates, call data, put data, premiums, and price list + // Build data arrays from the aggregated data dates = Object.keys(aggregatedData); callData = dates.map((date) => aggregatedData[date].callSize); putData = dates.map((date) => aggregatedData[date].putSize); totalPremiums = dates.map((date) => aggregatedData[date].totalPremium); - // Match historical prices for the same dates + // Get the historical prices for matching dates priceList = dates.map((date) => { const matchingData = data?.getHistoricalPrice?.find( (d) => d?.time === date, ); - return matchingData?.close || null; // Use `null` if no match is found + return matchingData?.close || null; }); + // Highcharts configuration options const options = { - animation: false, - tooltip: { - trigger: "axis", - hideDelay: 100, - borderColor: "#969696", // Black border color - borderWidth: 1, // Border width of 1px - backgroundColor: "#313131", // Optional: Set background color for contrast - textStyle: { - color: "#fff", // Optional: Text color for better visibility + credits: { + enabled: false, + }, + chart: { + // Removed global type so each series can define its own type. + backgroundColor: "#09090B", + plotBackgroundColor: "#09090B", + height: 360, + animation: false, + }, + title: { + text: `

${ticker} Unusual Options Activity

`, + style: { + color: "white", + // Using inline CSS for margin-top and margin-bottom }, - formatter: function (params) { - // Get the timestamp from the first parameter - const timestamp = params[0].axisValue; - - // Initialize result with timestamp - let result = timestamp + "
"; - - // Add each series data - params?.forEach((param) => { - const marker = - ''; - result += - marker + - param.seriesName + - ": " + - abbreviateNumberWithColor(param.value, false, true) + - "
"; - }); - - return result; + useHTML: true, // Enable HTML to apply custom class styling + }, + xAxis: { + type: "datetime", + endOnTick: false, + categories: dates, + crosshair: { + color: "#fff", // Set the color of the crosshair line + width: 1, // Adjust the line width as needed + dashStyle: "Solid", }, - axisPointer: { - lineStyle: { + labels: { + style: { color: "#fff", }, - }, - }, - silent: true, - grid: { - left: $screenWidth < 640 ? "5%" : "2%", - right: $screenWidth < 640 ? "5%" : "2%", - bottom: "10%", - containLabel: true, - }, - xAxis: [ - { - type: "category", - data: dates, - axisLabel: { - color: "#fff", - - formatter: function (value) { - // Assuming dates are in the format 'yyyy-mm-dd' - const dateParts = value.split("-"); - const monthIndex = parseInt(dateParts[1]) - 1; // Months are zero-indexed in JavaScript Date objects - const year = parseInt(dateParts[0]); - const day = parseInt(dateParts[2]); - return `${day} ${monthNames[monthIndex]} ${year}`; - }, + distance: 20, // Increases space between label and axis + formatter: function () { + const date = new Date(this.value); + return date.toLocaleDateString("en-US", { + month: "short", + year: "numeric", + }); }, }, - ], + tickPositioner: function () { + // Create custom tick positions with wider spacing + const positions = []; + const info = this.getExtremes(); + const tickCount = 5; // Reduce number of ticks displayed + const interval = Math.floor((info.max - info.min) / tickCount); + + for (let i = 0; i <= tickCount; i++) { + positions.push(info.min + i * interval); + } + return positions; + }, + }, yAxis: [ { - type: "value", - splitLine: { - show: false, // Disable x-axis grid lines - }, - axisLabel: { - show: false, // Hide y-axis labels + gridLineWidth: 1, + gridLineColor: "#111827", + labels: { + style: { color: "white" }, }, + title: { text: null }, + opposite: true, }, { - type: "value", - splitLine: { - show: false, // Disable x-axis grid lines + title: { + text: null, }, - position: "right", - axisLabel: { - show: false, // Hide y-axis labels + gridLineWidth: 0, + labels: { + enabled: false, }, }, ], + tooltip: { + shared: true, + useHTML: true, + backgroundColor: "rgba(0, 0, 0, 0.8)", // Semi-transparent black + borderColor: "rgba(255, 255, 255, 0.2)", // Slightly visible white border + borderWidth: 1, + style: { + color: "#fff", + fontSize: "16px", + padding: "10px", + }, + borderRadius: 4, + formatter: function () { + // Format the x value to display time in hh:mm format + let tooltipContent = `${new Date( + this?.x, + ).toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + })}
`; + + // Loop through each point in the shared tooltip + this.points.forEach((point) => { + tooltipContent += `${point.series.name}: + ${abbreviateNumber( + point.y, + )}
`; + }); + + return tooltipContent; + }, + }, series: [ { name: "Call", - type: "bar", - stack: "Put-Call Ratio", - emphasis: { - focus: "series", - }, + type: "column", data: callData, - itemStyle: { - color: "#00FC50", + color: "#00FC50", + borderColor: "#00FC50", // Match border color + marker: { + enabled: false, }, + animation: false, }, { name: "Put", - type: "bar", - stack: "Put-Call Ratio", - emphasis: { - focus: "series", - }, + type: "column", data: putData, - itemStyle: { - color: "#EE5365", //'#7A1C16' + color: "#EE5365", + borderColor: "#EE5365", // Match border color + marker: { + enabled: false, }, + animation: false, }, { - name: "Price", // Name for the line chart - type: "line", // Type of the chart (line) - yAxisIndex: 1, // Use the second y-axis on the right - data: priceList, // iv60Data (assumed to be passed as priceList) - itemStyle: { - color: "#fff", // Choose a color for the line (gold in this case) + name: "Price", + type: "area", + yAxis: 1, + data: priceList, + color: "#fff", + lineWidth: 1, + marker: { + enabled: false, }, - lineStyle: { - width: 2, // Set the width of the line + animation: false, + fillColor: { + linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, + stops: [ + [0, "rgba(255, 255, 255, 0.1)"], + [1, "rgba(255, 255, 255, 0.001)"], + ], }, - smooth: true, // Optional: make the line smooth - showSymbol: false, + // If you prefer a smooth (curved) line, you can use the "spline" type: + // type: "spline" }, ], + + legend: { + enabled: false, + }, }; + return options; } @@ -302,174 +325,227 @@ }); function plotContractHistory() { - let data = rawDataHistory?.sort( - (a, b) => new Date(a?.date) - new Date(b?.date), - ); - let dates = data?.map((item) => item?.date); - let avgPrice = data?.map((item) => item?.mark); - let priceList = data?.map((item) => item?.price); + // Ensure rawDataHistory exists and sort it by date + const sortedData = + rawDataHistory?.sort((a, b) => new Date(a?.date) - new Date(b?.date)) || + []; - let volumeList = data?.map((item) => item?.volume); - let oiList = data?.map((item) => item?.open_interest); - let ivList = data?.map((item) => - Math?.floor(item?.implied_volatility * 100), - ); - - const createLineSeries = (name, data, color, yAxisIndex = 1) => ({ - name, - type: "line", - yAxisIndex, - data, - itemStyle: { color }, - lineStyle: { width: 2 }, - smooth: true, - showSymbol: false, - }); - - const createBarSeries = (name, data, color, stack = null) => ({ - name, - type: "bar", - stack, - data, - itemStyle: { color }, - emphasis: { focus: "series" }, - }); + // Filter out data points that have an undefined price so they don't appear in any series + const filteredData = sortedData.filter((item) => item?.price !== undefined); + // Build series based on the selected graph type, using filteredData let series = []; - if (selectGraphType === "Vol/OI") { + if (selectGraphType == "Vol/OI") { series = [ - createBarSeries("Volume", volumeList, "#FD7E14"), - createBarSeries("OI", oiList, "#33B890"), - createLineSeries("Avg Fill", avgPrice, "#FAD776"), - createLineSeries("Stock Price", priceList, "#fff", 2), + { + name: "Volume", + type: "column", + data: filteredData.map((item) => [ + new Date(item.date).getTime(), + item.volume, + ]), + color: "#FD7E14", + borderColor: "#FD7E14", + borderRadius: "2px", + yAxis: 0, + animation: false, + }, + { + name: "OI", + type: "column", + data: filteredData.map((item) => [ + new Date(item.date).getTime(), + item.open_interest, + ]), + color: "#33B890", + borderColor: "#33B890", + borderRadius: "2px", + yAxis: 0, + animation: false, + }, + { + name: "Avg Fill", + type: "spline", // smooth line + data: filteredData.map((item) => [ + new Date(item.date).getTime(), + item.mark, + ]), + color: "#FAD776", + yAxis: 2, + animation: false, + marker: { enabled: false }, + }, + { + name: "Price", + type: "spline", + yAxis: 1, + data: filteredData.map((item) => [ + new Date(item.date).getTime(), + item.price, + ]), + color: "#fff", + lineWidth: 1, + marker: { enabled: false }, + animation: false, + }, ]; } else { series = [ - createLineSeries("IV", ivList, "#B24BF3", 0), - createLineSeries("Avg Fill", avgPrice, "#FAD776"), - createLineSeries("Stock Price", priceList, "#fff", 2), + { + name: "IV", + type: "spline", + data: filteredData.map((item) => [ + new Date(item.date).getTime(), + Math.floor(item.implied_volatility * 100), + ]), + color: "#B24BF3", + yAxis: 0, + animation: false, + marker: { enabled: false }, + }, + { + name: "Avg Fill", + type: "spline", + data: filteredData.map((item) => [ + new Date(item.date).getTime(), + item.mark, + ]), + color: "#FAD776", + yAxis: 2, + lineWidth: 1, + animation: false, + marker: { enabled: false }, + }, + { + name: "Price", + type: "spline", + yAxis: 1, + data: filteredData.map((item) => [ + new Date(item.date).getTime(), + item.price, + ]), + color: "#fff", + lineWidth: 1, + marker: { enabled: false }, + animation: false, + }, ]; } + // Highcharts configuration object const options = { - animation: false, - tooltip: { - trigger: "axis", - hideDelay: 100, - borderColor: "#969696", // Black border color - borderWidth: 1, // Border width of 1px - backgroundColor: "#313131", // Optional: Set background color for contrast - textStyle: { - color: "#fff", // Optional: Text color for better visibility - }, - formatter: function (params) { - // Get the timestamp from the first parameter - const timestamp = params[0].axisValue; - - // Find the matching data point in rawDataHistory based on the date - const rawDataPoint = rawDataHistory.find( - (item) => item.date === timestamp, - ); - - // Initialize result with timestamp - let result = timestamp + "
"; - - // Sort params to ensure Vol appears last - params.sort((a, b) => { - if (a.seriesName === "Vol") return 1; - if (b.seriesName === "Vol") return -1; - return 0; - }); - - // Loop through each series data - params?.forEach((param) => { - const marker = - ''; - - // Check if the series is for IV and add a '%' sign - const value = - param.seriesName === "IV" - ? `${param.value}%` - : (param.value?.toLocaleString("en-US") ?? "n/a"); - - result += marker + param.seriesName + ": " + value + "
"; - }); - - if (rawDataPoint?.dte !== undefined) { - result += `Days to Expiration : ${rawDataPoint.dte}
`; - } - - return result; - }, - - axisPointer: { - lineStyle: { - color: "#fff", - }, - }, + chart: { + backgroundColor: "#09090B", + animation: false, + height: 360, }, - - silent: true, - grid: { - left: $screenWidth < 640 ? "5%" : "2%", - right: $screenWidth < 640 ? "5%" : "2%", - bottom: "20%", - containLabel: true, + credits: { enabled: false }, + title: { + text: `

Contract History

`, + useHTML: true, + style: { color: "white" }, }, - xAxis: [ - { - type: "category", - data: dates, - axisLabel: { - color: "#fff", - - formatter: function (value) { - // Assuming dates are in the format 'yyyy-mm-dd' - const dateParts = value.split("-"); - const monthIndex = parseInt(dateParts[1]) - 1; // Months are zero-indexed in JavaScript Date objects - const year = parseInt(dateParts[0]); - const day = parseInt(dateParts[2]); - return `${day} ${monthNames[monthIndex]} ${year}`; + // Disable markers globally on hover for all series + plotOptions: { + series: { + marker: { + enabled: false, + states: { + hover: { + enabled: false, + }, }, }, }, - ], + }, + xAxis: { + type: "datetime", + endOnTick: false, + crosshair: { + color: "#fff", + width: 1, + dashStyle: "Solid", + }, + labels: { + style: { color: "#fff" }, + distance: 20, + formatter: function () { + return new Date(this.value).toLocaleDateString("en-US", { + month: "short", + year: "numeric", + }); + }, + }, + tickPositioner: function () { + const positions = []; + const info = this.getExtremes(); + const tickCount = 5; // Reduce number of ticks displayed + const interval = Math.floor((info.max - info.min) / tickCount); + + for (let i = 0; i <= tickCount; i++) { + positions.push(info.min + i * interval); + } + return positions; + }, + }, yAxis: [ { - type: "value", - splitLine: { - show: false, // Disable x-axis grid lines - }, - axisLabel: { - show: false, // Hide y-axis labels - }, + gridLineWidth: 1, + gridLineColor: "#111827", + labels: { style: { color: "white" } }, + title: { text: null }, + opposite: true, }, { - type: "value", - splitLine: { - show: false, // Disable x-axis grid lines - }, - position: "right", - axisLabel: { - show: false, // Hide y-axis labels - }, + title: { text: null }, + gridLineWidth: 0, + labels: { enabled: false }, }, { - type: "value", - splitLine: { - show: false, // Disable x-axis grid lines - }, - position: "top", - axisLabel: { - show: false, // Hide y-axis labels - }, + title: { text: null }, + gridLineWidth: 0, + labels: { enabled: false }, + }, + { + title: { text: null }, + gridLineWidth: 0, + labels: { enabled: false }, }, ], + tooltip: { + shared: true, + useHTML: true, + backgroundColor: "rgba(0, 0, 0, 0.8)", + borderColor: "rgba(255, 255, 255, 0.2)", + borderWidth: 1, + style: { + color: "#fff", + fontSize: "16px", + padding: "10px", + }, + borderRadius: 4, + formatter: function () { + let tooltipContent = `${new Date( + this.x, + ).toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + })}
`; + + this.points.forEach((point) => { + tooltipContent += `${point.series.name}: + ${abbreviateNumber( + point.y, + )}
`; + }); + return tooltipContent; + }, + }, + legend: { enabled: false }, series: series, }; + return options; } @@ -524,13 +600,13 @@ }); rawDataHistory = calculateDTE(rawDataHistory, dateExpiration); - optionsData = plotContractHistory(); + configContract = plotContractHistory(); rawDataHistory = rawDataHistory?.sort( (a, b) => new Date(b?.date) - new Date(a?.date), ); optionHistoryList = rawDataHistory?.slice(0, 20); } else { - optionsData = null; + configContract = null; } isLoaded = true; @@ -627,9 +703,9 @@ if (typeof window !== "undefined" && selectGraphType) { isLoaded = false; if (rawDataHistory?.length > 0) { - optionsData = plotContractHistory(); + configContract = plotContractHistory(); } else { - optionsData = null; + configContract = null; } isLoaded = true; @@ -654,9 +730,11 @@ text="Unusual Options trades with a premium of at least 1 million dollar from big whales." /> -
- -
+
+
@@ -693,7 +771,7 @@ on:click={() => handleViewData(item)} on:mouseover={() => getContractHistory(item?.option_symbol)} - class=" cursor-pointer text-[#04D9FF] sm:hover:text-white sm:hover:underline sm:hover:underline-offset-4" + class="cursor-pointer text-[#04D9FF] sm:hover:text-white sm:hover:underline sm:hover:underline-offset-4" > {item?.strike} @@ -701,7 +779,7 @@ - {#if $screenWidth > 640} -
-
- {#each ["Vol/OI", "IV"] as item} - - {/each} -
-
- {#if isLoaded} - - {:else} -
-
- -
-
- {/if} -
+
+
+ {#each ["Vol/OI", "IV"] as item} + + {/each}
- {/if} +
+
close - - diff --git a/src/routes/stocks/[tickerID]/metrics/[slug]/+page.svelte b/src/routes/stocks/[tickerID]/metrics/[slug]/+page.svelte index de1455d4..f91ca1a4 100644 --- a/src/routes/stocks/[tickerID]/metrics/[slug]/+page.svelte +++ b/src/routes/stocks/[tickerID]/metrics/[slug]/+page.svelte @@ -100,24 +100,36 @@ opposite: true, }, tooltip: { + shared: true, useHTML: true, - backgroundColor: "#fff", + backgroundColor: "rgba(0, 0, 0, 0.8)", // Semi-transparent black + borderColor: "rgba(255, 255, 255, 0.2)", // Slightly visible white border + borderWidth: 1, style: { - color: "black", + color: "#fff", fontSize: "16px", padding: "10px", }, - borderRadius: 2, - borderWidth: 1, - borderColor: "#fff", + borderRadius: 4, formatter: function () { - return `${new Date( + // Format the x value to display time in hh:mm format + let tooltipContent = `${new Date( this?.x, ).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", - })}
${abbreviateNumber(this.y)}`; + })}

`; + + // Loop through each point in the shared tooltip + this.points.forEach((point) => { + tooltipContent += `${point.series.name}: + ${abbreviateNumber( + point.y, + )}
`; + }); + + return tooltipContent; }, }, plotOptions: {