upgrade daisyui && update calculator

This commit is contained in:
MuslemRahimi 2025-04-08 14:00:45 +02:00
parent 97d3b168bb
commit 441902dca7
3 changed files with 97 additions and 77 deletions

8
package-lock.json generated
View File

@ -34,7 +34,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk-sv": "^0.0.18", "cmdk-sv": "^0.0.18",
"compression": "^1.7.4", "compression": "^1.7.4",
"daisyui": "^5.0.12", "daisyui": "^5.0.16",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"date-fns-tz": "^3.1.3", "date-fns-tz": "^3.1.3",
"date-picker-svelte": "^2.12.0", "date-picker-svelte": "^2.12.0",
@ -4043,9 +4043,9 @@
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/daisyui": { "node_modules/daisyui": {
"version": "5.0.12", "version": "5.0.16",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.0.12.tgz", "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.0.16.tgz",
"integrity": "sha512-01DU0eYBcHgPtuf5fxcrkGkIN6/Uyaqmkle5Yo3ZyW9YVAu036ALZbjv2KH5euvUbeQ4r9q3gAarGcf7Tywhng==", "integrity": "sha512-MQiZQ21+r91zrKv3fxDOF56dzkfrr2FyXOaR/1T4f9AIeI/ilkc0i+UqOIE/VqjIfkWyq+wBPmBcyIf6iPJpYQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {

View File

@ -34,7 +34,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk-sv": "^0.0.18", "cmdk-sv": "^0.0.18",
"compression": "^1.7.4", "compression": "^1.7.4",
"daisyui": "^5.0.12", "daisyui": "^5.0.16",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"date-fns-tz": "^3.1.3", "date-fns-tz": "^3.1.3",
"date-picker-svelte": "^2.12.0", "date-picker-svelte": "^2.12.0",

View File

@ -14,27 +14,6 @@
import { mode } from "mode-watcher"; import { mode } from "mode-watcher";
import highcharts from "$lib/highcharts.ts"; import highcharts from "$lib/highcharts.ts";
// Types
type Strategy = {
name: string;
sentiment: string;
description: string;
};
type OptionLeg = {
action: string;
quantity: number;
date: string;
strike: number;
optionType: string;
optionPrice: number;
};
type SearchResult = {
symbol: string;
type: string;
};
export let data; export let data;
// State variables with proper types // State variables with proper types
@ -68,13 +47,13 @@
let rawData: Record<string, any> = {}; let rawData: Record<string, any> = {};
// Search variables // Search variables
let searchBarData: SearchResult[] = []; let searchBarData = [];
let timeoutId: ReturnType<typeof setTimeout>; let timeoutId: ReturnType<typeof setTimeout>;
let inputValue = ""; let inputValue = "";
let touchedInput = false; let touchedInput = false;
// Strategy definitions // Strategy definitions
const prebuiltStrategy: Strategy[] = [ const prebuiltStrategy = [
{ {
name: "Long Call", name: "Long Call",
sentiment: "Bullish", sentiment: "Bullish",
@ -99,6 +78,18 @@
description: description:
"In this strategy, an investor sells a put option, expecting that the price of the underlying asset will remain stable or increase, allowing the investor to keep the premium received from selling the option. Investors typically use a short put strategy when they have a neutral to bullish outlook on the stock and and views a potential assignment as an opportunity to buy the asset at a desirable price.", "In this strategy, an investor sells a put option, expecting that the price of the underlying asset will remain stable or increase, allowing the investor to keep the premium received from selling the option. Investors typically use a short put strategy when they have a neutral to bullish outlook on the stock and and views a potential assignment as an opportunity to buy the asset at a desirable price.",
}, },
{
name: "Cash Secured Put",
sentiment: "Bullish",
description:
"In this strategy, an investor sells a put option and simultaneously sets aside enough cash to buy the stock. The goal is to be assigned the stock at a desirable price and generate income from the option premium. Investors typically use a cash secured put strategy when they have a neutral to bullish outlook on the stock and view a potential assignment as an opportunity to buy the asset at a desirable price.",
},
{
name: "Bull Call Spread",
sentiment: "Bullish",
description:
"In this strategy, an investor simultaneously purchases call options at a specific strike price and sells the same number of calls at a higher strike price. Both call options have the same expiration date. This strategy is used when the investor is bullish and expects a moderate rise in the price of the underlying asset. The investor limits their upside profit potential but reduces the net premium spent compared to buying a single call option outright. The strategy is also known as a 'debit call spread' because the investor pays a net debit to establish the position.",
},
// Other strategies commented out in original code // Other strategies commented out in original code
]; ];
@ -107,7 +98,7 @@
// STRATEGY FUNCTIONS // STRATEGY FUNCTIONS
async function changeStrategy(strategy: Strategy) { async function changeStrategy(strategy) {
selectedStrategy = strategy?.name; selectedStrategy = strategy?.name;
description = strategy?.description; description = strategy?.description;
@ -129,11 +120,35 @@
selectedOptionType = "Put"; selectedOptionType = "Put";
selectedAction = "Sell"; selectedAction = "Sell";
break; break;
case "Cash Secured Put":
selectedOptionType = "Put";
selectedAction = "Sell";
break;
default: default:
console.warn("Unknown strategy:", strategy); console.warn("Unknown strategy:", strategy);
selectedOptionType = null; selectedOptionType = null;
selectedAction = null; selectedAction = null;
} }
if (["Bull Call Spread"]?.includes(selectedStrategy)) {
userStrategy = [
{
strike: selectedStrike,
optionType: "Call",
date: selectedDate,
optionPrice: selectedOptionPrice,
quantity: 1,
action: "Buy",
},
{
strike: selectedStrike,
optionType: "Call",
date: selectedDate,
optionPrice: selectedOptionPrice,
quantity: 1,
action: "Sell",
},
];
} else {
userStrategy = [ userStrategy = [
{ {
strike: selectedStrike, strike: selectedStrike,
@ -144,6 +159,8 @@
action: selectedAction, action: selectedAction,
}, },
]; ];
}
shouldUpdate = true; shouldUpdate = true;
} }
@ -500,7 +517,7 @@
} }
}; };
async function loadData(state: string) { async function loadData() {
if (!rawData?.getData) { if (!rawData?.getData) {
console.error("rawData is undefined or invalid in loadData"); console.error("rawData is undefined or invalid in loadData");
return; return;
@ -525,16 +542,15 @@
item.strikeList = strikeList; item.strikeList = strikeList;
// Find closest strike to current stock price // Find closest strike to current stock price
if (!strikeList.includes(selectedStrike) && strikeList.length > 0) { if (!strikeList?.includes(item?.strike) && strikeList?.length > 0) {
selectedStrike = strikeList.reduce((closest, strike) => { selectedStrike = strikeList.reduce((closest, strike) => {
return Math.abs(strike - currentStockPrice) < return Math.abs(strike - currentStockPrice) <
Math.abs(closest - currentStockPrice) Math.abs(closest - currentStockPrice)
? strike ? strike
: closest; : closest;
}, strikeList[0]); }, strikeList[0]);
}
item.strike = selectedStrike; item.strike = selectedStrike;
}
// Get option price // Get option price
optionSymbol = buildOptionSymbol( optionSymbol = buildOptionSymbol(
@ -547,6 +563,8 @@
const output = await getContractHistory(optionSymbol); const output = await getContractHistory(optionSymbol);
selectedOptionPrice = output?.history?.at(-1)?.mark || 0; selectedOptionPrice = output?.history?.at(-1)?.mark || 0;
item.optionPrice = selectedOptionPrice; item.optionPrice = selectedOptionPrice;
item.optionSymbol = optionSymbol;
item.optionType = selectedOptionType;
} }
} else { } else {
optionData = rawData?.getData[selectedOptionType] || {}; optionData = rawData?.getData[selectedOptionType] || {};
@ -581,7 +599,6 @@
selectedOptionPrice = output?.history?.at(-1)?.mark || 0; selectedOptionPrice = output?.history?.at(-1)?.mark || 0;
// Update user strategy if necessary // Update user strategy if necessary
if (state === "default") {
userStrategy = [ userStrategy = [
{ {
date: selectedDate, date: selectedDate,
@ -592,11 +609,10 @@
quantity: selectedQuantity, quantity: selectedQuantity,
strikeList: strikeList, strikeList: strikeList,
dateList: dateList, dateList: dateList,
optionSymbol: optionSymbol,
}, },
]; ];
} }
}
shouldUpdate = true; shouldUpdate = true;
} catch (error) { } catch (error) {
console.error("Error loading data:", error); console.error("Error loading data:", error);
@ -644,6 +660,7 @@
const newLeg = { ...lastLeg }; // Create a shallow copy const newLeg = { ...lastLeg }; // Create a shallow copy
userStrategy = [...userStrategy, newLeg]; userStrategy = [...userStrategy, newLeg];
} }
await loadData();
shouldUpdate = true; shouldUpdate = true;
} }
@ -675,6 +692,7 @@
// Update the selectedAction (for new legs) // Update the selectedAction (for new legs)
selectedOptionType = selectedOptionType === "Call" ? "Put" : "Call"; selectedOptionType = selectedOptionType === "Call" ? "Put" : "Call";
} }
await loadData();
shouldUpdate = true; shouldUpdate = true;
} }
@ -699,7 +717,7 @@
const updatedStrategy = [...userStrategy]; const updatedStrategy = [...userStrategy];
updatedStrategy[index].date = selectedDate; updatedStrategy[index].date = selectedDate;
userStrategy = updatedStrategy; userStrategy = updatedStrategy;
await loadData("default"); await loadData();
shouldUpdate = true; shouldUpdate = true;
} }
} }
@ -710,11 +728,12 @@
const updatedStrategy = [...userStrategy]; const updatedStrategy = [...userStrategy];
updatedStrategy[index].strike = selectedStrike; updatedStrategy[index].strike = selectedStrike;
userStrategy = updatedStrategy; userStrategy = updatedStrategy;
await loadData();
shouldUpdate = true; shouldUpdate = true;
} }
} }
function handleOptionPriceInput(event: Event, index) { async function handleOptionPriceInput(event: Event, index) {
const value = (event.target as HTMLInputElement).value; const value = (event.target as HTMLInputElement).value;
selectedOptionPrice = value === "" ? null : +value; selectedOptionPrice = value === "" ? null : +value;
@ -726,12 +745,12 @@
if (debounceTimeout) clearTimeout(debounceTimeout); if (debounceTimeout) clearTimeout(debounceTimeout);
// Set a new debounce timeout // Set a new debounce timeout
debounceTimeout = setTimeout(() => { debounceTimeout = setTimeout(async () => {
shouldUpdate = true; shouldUpdate = true;
}, 300); }, 300);
} }
function handleQuantityInput(event, index) { async function handleQuantityInput(event, index) {
if (index !== undefined && userStrategy[index]) { if (index !== undefined && userStrategy[index]) {
const value = (event.target as HTMLInputElement).value; const value = (event.target as HTMLInputElement).value;
@ -742,7 +761,7 @@
if (debounceTimeout) clearTimeout(debounceTimeout); if (debounceTimeout) clearTimeout(debounceTimeout);
// Set a new debounce timeout // Set a new debounce timeout
debounceTimeout = setTimeout(() => { debounceTimeout = setTimeout(async () => {
shouldUpdate = true; shouldUpdate = true;
}, 300); }, 300);
} }
@ -775,14 +794,14 @@
}, 50); // delay }, 50); // delay
} }
async function changeTicker(data: SearchResult) { async function changeTicker(data) {
if (!data?.symbol) return; if (!data?.symbol) return;
selectedTicker = data.symbol; selectedTicker = data.symbol;
assetType = data?.type?.toLowerCase() || "stocks"; assetType = data?.type?.toLowerCase() || "stocks";
await getStockData(); await getStockData();
await loadData("default"); await loadData();
inputValue = ""; inputValue = "";
shouldUpdate = true; shouldUpdate = true;
} }
@ -791,7 +810,7 @@
onMount(async () => { onMount(async () => {
await getStockData(); await getStockData();
await loadData("default"); await loadData();
shouldUpdate = true; shouldUpdate = true;
}); });
@ -881,6 +900,7 @@
</div> </div>
{/each} {/each}
</div> </div>
<div class="border-b border-gray-400 mt-5"></div> <div class="border-b border-gray-400 mt-5"></div>
<h2 class="mt-5 mb-1 text-xl sm:text-2xl font-bold"> <h2 class="mt-5 mb-1 text-xl sm:text-2xl font-bold">
{selectedStrategy} {selectedStrategy}
@ -945,7 +965,7 @@
> >
<!-- Table head --> <!-- Table head -->
<thead class="bg-gray-50 dark:bg-secondary"> <thead class="bg-gray-50 dark:bg-secondary">
<tr> <tr class="">
<th <th
scope="col" scope="col"
class="px-4 py-1.5 text-left text-sm font-semibold" class="px-4 py-1.5 text-left text-sm font-semibold"
@ -997,14 +1017,14 @@
<!-- Table body --> <!-- Table body -->
<tbody <tbody
class="bg-[#F8F9FA] dark:bg-secondary divide-y divide-gray-200 dark:divide-gray-800 text-sm" class="bg-[#F8F9FA] dark:bg-secondary divide-y divide-gray-200 dark:divide-gray-600 text-sm"
> >
{#each userStrategy as item, index} {#each userStrategy as item, index}
<tr> <tr class="">
<td class="px-4 whitespace-nowrap font-semibold"> <td class="px-4 whitespace-nowrap font-semibold">
{selectedTicker} {selectedTicker}
</td> </td>
<td class="px-4 whitespace-nowrap"> <td class="px-4 whitespace-nowrap py-2">
<label <label
on:click={() => handleAction(index)} on:click={() => handleAction(index)}
class="badge px-2 select-none rounded-md {item?.action === class="badge px-2 select-none rounded-md {item?.action ===
@ -1014,7 +1034,7 @@
>{item?.action}</label >{item?.action}</label
> >
</td> </td>
<td class="px-4 whitespace-nowrap"> <td class="px-4 whitespace-nowrap py-2">
<input <input
type="number" type="number"
value={userStrategy[index]?.quantity} value={userStrategy[index]?.quantity}
@ -1023,7 +1043,7 @@
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" 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"
/> />
</td> </td>
<td class="px-4 whitespace-nowrap"> <td class="px-4 whitespace-nowrap py-2">
<DropdownMenu.Root> <DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder> <DropdownMenu.Trigger asChild let:builder>
<Button <Button
@ -1070,7 +1090,7 @@
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Root> </DropdownMenu.Root>
</td> </td>
<td class="px-4 whitespace-nowrap"> <td class="px-4 whitespace-nowrap py-2">
<DropdownMenu.Root> <DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder> <DropdownMenu.Trigger asChild let:builder>
<Button <Button
@ -1116,14 +1136,14 @@
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Root> </DropdownMenu.Root>
</td> </td>
<td class="px-4 whitespace-nowrap"> <td class="px-4 whitespace-nowrap py-2">
<label <label
on:click={() => handleOptionType(index)} on:click={() => handleOptionType(index)}
class="select-none badge px-2 rounded-md bg-blue-100 text-blue-800 dark:bg-blue-300 dark:text-muted font-semibold cursor-pointer" class="select-none badge px-2 rounded-md bg-blue-100 text-blue-800 dark:bg-blue-300 dark:text-muted font-semibold cursor-pointer"
>{item?.optionType}</label >{item?.optionType}</label
> >
</td> </td>
<td class="px-4 whitespace-nowrap"> <td class="px-4 whitespace-nowrap py-2">
<input <input
type="number" type="number"
step="0.1" step="0.1"
@ -1133,13 +1153,13 @@
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" 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"
/> />
</td> </td>
<td class="px-4 whitespace-nowrap"> <td class="px-4 whitespace-nowrap py-2">
<div <div
class="flex flex-row items-center m-auto text-center justify-center" class="flex flex-row items-center m-auto text-center justify-center"
> >
<a <a
class="inline-block" class="inline-block"
href={`/${["stocks", "stock"]?.includes(assetType) ? "stocks" : assetType === "etf" ? "etf" : "index"}/${selectedTicker}/options/contract-lookup?query=${optionSymbol}`} href={`/${["stocks", "stock"]?.includes(assetType) ? "stocks" : assetType === "etf" ? "etf" : "index"}/${selectedTicker}/options/contract-lookup?query=${userStrategy[index]?.optionSymbol}`}
> >
<Link <Link
class="w-4 h-4 text-gray-800 dark:text-gray-100 mt-0.5" class="w-4 h-4 text-gray-800 dark:text-gray-100 mt-0.5"