diff --git a/src/routes/api/historical-price/+server.ts b/src/routes/api/historical-price/+server.ts index 9d1409b0..1f23b232 100644 --- a/src/routes/api/historical-price/+server.ts +++ b/src/routes/api/historical-price/+server.ts @@ -15,6 +15,5 @@ export const POST: RequestHandler = async ({ request, locals }) => { }); const output = await response.json(); - return new Response(JSON.stringify(output)); }; diff --git a/src/routes/watchlist/options/+page.server.ts b/src/routes/watchlist/options/+page.server.ts index 5cbff952..84c3f54c 100644 --- a/src/routes/watchlist/options/+page.server.ts +++ b/src/routes/watchlist/options/+page.server.ts @@ -1,3 +1,14 @@ + function daysLeft(targetDate) { + const targetTime = new Date(targetDate).getTime(); + const currentTime = new Date().getTime(); + const difference = targetTime - currentTime; + + const millisecondsPerDay = 1000 * 60 * 60 * 24; + const daysLeft = Math?.ceil(difference / millisecondsPerDay); + + return daysLeft; + } + export const load = async ({ locals }) => { const getOptionsWatchlist = async () => { const { apiKey, apiURL, pb, user } = locals; @@ -21,6 +32,13 @@ export const load = async ({ locals }) => { body: JSON.stringify(postData), }); const output = await response.json(); + + output?.forEach((item) => { + item.dte = daysLeft(item?.date_expiration); + item.size = Math.floor(item?.cost_basis / (item?.price * 100)); + }); + + return { id: watchList?.id, optionsList: output }; } else { return { id: "", optionsList: [] }; diff --git a/src/routes/watchlist/options/+page.svelte b/src/routes/watchlist/options/+page.svelte index ef0ee0c4..4bc61484 100644 --- a/src/routes/watchlist/options/+page.svelte +++ b/src/routes/watchlist/options/+page.svelte @@ -7,6 +7,7 @@ import { screenWidth, setCache, getCache } from "$lib/store"; import { onMount } from "svelte"; import { toast } from "svelte-sonner"; + import highcharts from "$lib/highcharts.ts"; import { mode } from "mode-watcher"; export let data; @@ -16,7 +17,7 @@ let deleteOptionsId = []; let isLoaded = false; - let configContract = null; + let config = null; let optionHistoryList = []; let selectGraphType = "Vol/OI"; @@ -24,6 +25,7 @@ let rawDataHistory = []; let strikePrice; let optionType; + let optionSymbol; let dateExpiration; let ticker; @@ -38,7 +40,7 @@ return daysLeft; } - let optionsWatchlist = data?.getOptionsWatchlist?.optionsList; + let rawData = data?.getOptionsWatchlist?.optionsList; function reformatDate(dateString) { return ( @@ -137,17 +139,6 @@ } } - onMount(async () => { - if (optionsWatchlist?.length !== 0) { - optionsWatchlist?.forEach((item) => { - item.dte = daysLeft(item?.date_expiration); - item.size = Math.floor(item?.cost_basis / (item?.price * 100)); - }); - } - originalData = [...optionsWatchlist]; - isLoaded = true; - }); - function getScroll() { const scrollThreshold = container.scrollHeight * 0.8; // 80% of the container height @@ -166,17 +157,19 @@ } } - const getContractHistory = async (contractId) => { + const getContractHistory = async () => { let output; - const cachedData = getCache(contractId, "getContractHistory"); + const cachedData = getCache(optionSymbol, "getContractHistory"); if (cachedData) { output = cachedData; } else { const postData = { ticker: ticker, - contract: contractId, + contract: optionSymbol, }; + console.log(postData); + // make the POST request to the endpoint const response = await fetch("/api/options-contract-history", { method: "POST", @@ -188,7 +181,35 @@ output = await response.json(); - setCache(contractId, output, "getContractHistory"); + setCache(optionSymbol, output, "getContractHistory"); + } + + return output; + }; + + const getHistoricalPrice = async () => { + let output; + const cachedData = getCache(ticker, "getHistoricalPrice"); + if (cachedData) { + output = cachedData; + } else { + const postData = { + timePeriod: "six-months", + ticker: ticker, + }; + + // make the POST request to the endpoint + const response = await fetch("/api/historical-price", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(postData), + }); + + output = await response.json(); + + setCache(ticker, output, "getHistoricalPrice"); } return output; @@ -201,13 +222,18 @@ strikePrice = item?.strike_price; optionType = item?.option_type; + ticker = item?.ticker; dateExpiration = item?.date_expiration; - const output = await getContractHistory(item?.option_symbol); + optionSymbol = item?.option_symbol; + const output = await getContractHistory(); + const historicalPrice = await getHistoricalPrice(); + + console.log(historicalPrice); rawDataHistory = output?.history; if (rawDataHistory?.length > 0) { rawDataHistory.forEach((entry) => { - const matchingData = data?.getHistoricalPrice?.find( + const matchingData = historicalPrice?.find( (d) => d?.time === entry?.date, ); if (matchingData) { @@ -215,19 +241,19 @@ } }); - configContract = plotContractHistory(); + config = plotData(); rawDataHistory = rawDataHistory?.sort( (a, b) => new Date(b?.date) - new Date(a?.date), ); optionHistoryList = rawDataHistory?.slice(0, 20); } else { - configContract = null; + config = null; } isLoaded = true; } - function plotContractHistory() { + function plotData() { // Ensure rawDataHistory exists and sort it by date const sortedData = rawDataHistory?.sort((a, b) => new Date(a?.date) - new Date(b?.date)) || @@ -503,10 +529,9 @@ sortOrders[key].order = orderCycle[(currentOrderIndex + 1) % orderCycle.length]; const sortOrder = sortOrders[key].order; - - // Reset to original data when 'none' and stop further sorting + let originalData = data?.getOptionsWatchlist?.optionsList; if (sortOrder === "none") { - optionsWatchlist = [...originalData]?.slice(0, 150); + rawData = [...originalData]?.slice(0, 150); return; } @@ -548,8 +573,21 @@ }; // Sort using the generic comparison function - optionsWatchlist = [...originalData].sort(compareValues)?.slice(0, 150); + rawData = [...originalData].sort(compareValues)?.slice(0, 150); }; + + $: { + if (selectGraphType) { + isLoaded = false; + if (rawDataHistory?.length > 0) { + config = plotData(); + } else { + config = null; + } + + isLoaded = true; + } + }
- {#if isLoaded} - {#if optionsWatchlist?.length !== 0} -
-

- {optionsWatchlist?.length} Options -

- - - Add Contracts - - {#if editMode} - - {/if} - + {#if rawData?.length !== 0} +
+

+ {rawData?.length} Options +

+ + + Add Contracts + + {#if editMode} -
+ {/if} - -
(editMode = !editMode)} + class="border shadow-sm text-sm border-gray-300 dark:border-gray-600 mr-2 sm:ml-3 sm:mr-0 cursor-pointer inline-flex items-center justify-center space-x-1 whitespace-nowrap rounded py-2 pl-3 pr-4 font-semibold sm:hover:bg-gray-100 dark:sm:hover:bg-default/60 ease-out sm:hover:text-gray-800 dark:sm:hover:text-gray-200" > - - - - - - {#each optionsWatchlist as item, index} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {/each} - -
- {formatTime(item?.time)} - handleFilter(item?.id)} - class="{index % 2 - ? 'bg-white dark:bg-default' - : 'bg-[#F6F7F8] dark:bg-odd'} font-normal text-sm sm:text-[1rem] text-center" - > -
- - {#if !editMode} - - {:else} - {item?.ticker} - {/if} -
-
- - - {reformatDate(item?.date_expiration)} - - {item?.dte < 0 ? "expired" : item?.dte + "d"} - - {item?.sentiment} - - {item?.size?.toLocaleString("en-US")} - - {item?.execution_estimate} - - {item?.price} - - {item?.underlying_price} - - {abbreviateNumber(item?.cost_basis)} - - {item?.option_activity_type} - - {new Intl.NumberFormat("en", { - minimumFractionDigits: 0, - maximumFractionDigits: 0, - }).format(item?.volume)} - - {new Intl.NumberFormat("en", { - minimumFractionDigits: 0, - maximumFractionDigits: 0, - }).format(item?.open_interest)} -
-
- - {:else} -
- - Empty Options List - - - - Add your unusual options contracts and start tracking them - now! - - {#if !data?.user} - - Get Started - - - - + {#if !editMode} + Edit {:else} - Cancel + {/if} + +
+ + +
+ + + + + + {#each rawData as item, index} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/each} + +
+ {formatTime(item?.time)} + handleFilter(item?.id)} + class="{index % 2 + ? 'bg-white dark:bg-default' + : 'bg-[#F6F7F8] dark:bg-odd'} font-normal text-sm sm:text-[1rem] text-center" + > +
+ + {#if !editMode} + + {:else} + {item?.ticker} + {/if} +
+
+ + + {reformatDate(item?.date_expiration)} + + {item?.dte < 0 ? "expired" : item?.dte + "d"} + + {item?.sentiment} + + {item?.size?.toLocaleString("en-US")} + + {item?.execution_estimate} + + {item?.price} + + {item?.underlying_price} + + {abbreviateNumber(item?.cost_basis)} + + {item?.option_activity_type} + + {new Intl.NumberFormat("en", { + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).format(item?.volume)} + + {new Intl.NumberFormat("en", { + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).format(item?.open_interest)} +
+
+ + {:else} +
- {/if} - {:else} -
-
- -
+ Follow the Whales + + + + {/if}
{/if}
@@ -948,7 +970,7 @@