diff --git a/src/routes/api/options-calculator/+server.ts b/src/routes/api/options-calculator/+server.ts new file mode 100644 index 00000000..16fd5365 --- /dev/null +++ b/src/routes/api/options-calculator/+server.ts @@ -0,0 +1,38 @@ +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ request, locals }) => { + const data = await request.json(); + const { apiURL, apiKey } = locals; + + const postData = { ticker: data?.ticker }; + + // First API call: contract lookup summary + const contractResponse = await fetch(apiURL + "/contract-lookup-summary", { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-API-KEY": apiKey, + }, + body: JSON.stringify(postData), + }); + + const contractOutput = await contractResponse.json(); + + // Second API call: stock quote + const stockResponse = await fetch(apiURL + "/stock-quote", { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-API-KEY": apiKey, + }, + body: JSON.stringify(postData), + }); + + const stockOutput = await stockResponse.json(); + + // Combine both outputs into a single object + const output = { getData: contractOutput, getStockQuote: stockOutput }; + + + return new Response(JSON.stringify(output)); +}; diff --git a/src/routes/options-calculator/+page.server.ts b/src/routes/options-calculator/+page.server.ts deleted file mode 100644 index 74f33fdf..00000000 --- a/src/routes/options-calculator/+page.server.ts +++ /dev/null @@ -1,49 +0,0 @@ -export const load = async ({ locals }) => { - const { apiKey, apiURL } = locals; - - const getData = async () => { - const postData = { - ticker: 'TSLA', - }; - - const response = await fetch(apiURL + "/contract-lookup-summary", { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-API-KEY": apiKey, - }, - body: JSON.stringify(postData), - }); - - const output = await response.json(); - - - return output; - }; - - - const getStockQuote = async () => { - const postData = { ticker: 'TSLA' }; - const response = await fetch(apiURL + "/stock-quote", { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-API-KEY": apiKey, - }, - body: JSON.stringify(postData), - }); - - const output = await response.json(); - return output; - }; - - - - // Make sure to return a promise - return { - getData: await getData(), - getStockQuote: await getStockQuote(), - }; -}; - - diff --git a/src/routes/options-calculator/+page.svelte b/src/routes/options-calculator/+page.svelte index cbc3e7f2..ae863b4d 100644 --- a/src/routes/options-calculator/+page.svelte +++ b/src/routes/options-calculator/+page.svelte @@ -4,7 +4,8 @@ import SEO from "$lib/components/SEO.svelte"; import { onMount, onDestroy } from "svelte"; import { abbreviateNumber, buildOptionSymbol } from "$lib/utils"; - import { setCache, getCache } from "$lib/store"; + import { setCache, getCache, screenWidth } from "$lib/store"; + import { Combobox } from "bits-ui"; import { mode } from "mode-watcher"; import highcharts from "$lib/highcharts.ts"; @@ -21,23 +22,25 @@ let selectedQuantity = 1; let debounceTimeout; - let currentStockPrice = data?.getStockQuote?.price; + let currentStockPrice; - let optionData = data?.getData[selectedOptionType]; - let dateList = Object?.keys(optionData); - let selectedDate = Object?.keys(optionData)[0]; - let strikeList = optionData[selectedDate] || []; - let selectedStrike = strikeList.reduce((closest, strike) => { - return Math.abs(strike - currentStockPrice) < - Math.abs(closest - currentStockPrice) - ? strike - : closest; - }, strikeList[0]); + let optionData = {}; + let dateList = []; + let selectedDate; + let strikeList = []; + let selectedStrike; let optionSymbol; let breakEvenPrice; let premium; let limits = {}; + let rawData = {}; + + let searchBarData = []; + let timeoutId; + + let inputValue = selectedTicker; + let touchedInput = false; let strategies = [ { @@ -173,24 +176,48 @@ if (scenarioKey === "Buy Call") { limits = { maxProfit: "Unlimited", - maxLoss: `-$${premium?.toLocaleString("en-US")}`, + maxLoss: `-$${premium?.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })}`, }; } else if (scenarioKey === "Sell Call") { limits = { - maxProfit: `+$${premium?.toLocaleString("en-US")}`, + maxProfit: `$${premium?.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })}`, maxLoss: "Unlimited", }; } else if (scenarioKey === "Buy Put") { limits = { // Maximum profit when underlying goes to 0 - maxProfit: `+$${(selectedStrike * 100 - premium)?.toLocaleString("en-US")}`, - maxLoss: `-$${premium?.toLocaleString("en-US")}`, + maxProfit: `$${(selectedStrike * 100 - premium)?.toLocaleString( + "en-US", + { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }, + )}`, + maxLoss: `-$${premium?.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })}`, }; } else if (scenarioKey === "Sell Put") { limits = { - maxProfit: `+$${premium?.toLocaleString("en-US")}`, + maxProfit: `$${premium?.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })}`, // Maximum loss when underlying goes to 0 - maxLoss: `-$${(selectedStrike * 100 - premium)?.toLocaleString("en-US")}`, + maxLoss: `-$${(selectedStrike * 100 - premium)?.toLocaleString( + "en-US", + { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }, + )}`, }; } else { console.error("Limits not defined for scenario:", scenarioKey); @@ -245,22 +272,23 @@ // Underlying Price line { value: currentStockPrice, - color: "black", + color: $mode === "light" ? "black" : "white", dashStyle: "Dash", width: 1.2, label: { text: `Underlying Price $${currentStockPrice}`, + style: { color: $mode === "light" ? "black" : "white" }, }, zIndex: 5, }, - // Break-Even line { value: breakEvenPrice, color: "#10B981", dashStyle: "Dash", - width: 1.2, + width: $screenWidth < 640 ? 0 : 1.2, label: { - text: `Breakeven $${breakEvenPrice.toFixed(2)}`, + text: `Breakeven $${breakEvenPrice.toFixed(2)}`, + style: { color: $mode === "light" ? "black" : "white" }, }, zIndex: 5, }, @@ -282,11 +310,16 @@ }, tooltip: { shared: true, - backgroundColor: $mode === "light" ? "#f9fafb" : "#1f2937", - borderColor: "#6b7280", + 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: $mode === "light" ? "black" : "white", + color: "#fff", + fontSize: "16px", + padding: "10px", }, + borderRadius: 2, formatter: function () { const underlyingPrice = this.x; const profitLoss = this.y; @@ -297,17 +330,18 @@ const profitLossPctChange = (profitLoss / premium) * 100; return ` -
| - | |||
|---|---|---|---|
|
- {selectedTicker}
+
+
+ {#if inputValue?.length !== 0 && inputValue !== selectedTicker}
+
+ {item?.symbol}
+ {item?.name}
+
+ |
@@ -595,7 +740,7 @@ min="1" bind:value={selectedQuantity} on:input={handleQuantityInput} - class="border border-gray-300 rounded px-2 py-1 w-20 focus:outline-none focus:ring-1 focus:ring-blue-500" + class="border border-gray-300 dark:border-gray-500 rounded px-2 py-1 w-20 focus:outline-none focus:ring-1 focus:ring-blue-500" /> | @@ -694,7 +839,7 @@ | @@ -704,7 +849,7 @@ step="0.1" bind:value={selectedOptionPrice} on:input={handleOptionPriceInput} - class="border border-gray-300 rounded px-2 py-1 w-24 focus:outline-none focus:ring-1 focus:ring-blue-500" + class="border border-gray-300 dark:border-gray-500 rounded px-2 py-1 w-24 focus:outline-none focus:ring-1 focus:ring-blue-500" /> @@ -755,7 +900,9 @@ {/if} |