update chart

This commit is contained in:
MuslemRahimi 2025-04-06 15:37:41 +02:00
parent 4859b0ff50
commit 866a437273
2 changed files with 114 additions and 48 deletions

View File

@ -24,7 +24,7 @@ export const load = async ({ locals }) => {
const getStockQuote = async () => { const getStockQuote = async () => {
const postData = { ticker: 'TSLA' }; const postData = { ticker: 'TSLA' };
const response = await fetch(apiURL + "/historical-price", { const response = await fetch(apiURL + "/stock-quote", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@ -2,7 +2,7 @@
import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js"; import * as DropdownMenu from "$lib/components/shadcn/dropdown-menu/index.js";
import { Button } from "$lib/components/shadcn/button/index.js"; import { Button } from "$lib/components/shadcn/button/index.js";
import SEO from "$lib/components/SEO.svelte"; import SEO from "$lib/components/SEO.svelte";
import { onMount } from "svelte"; import { onMount, onDestroy } from "svelte";
import { abbreviateNumber, buildOptionSymbol } from "$lib/utils"; import { abbreviateNumber, buildOptionSymbol } from "$lib/utils";
import { setCache, getCache } from "$lib/store"; import { setCache, getCache } from "$lib/store";
@ -18,14 +18,25 @@
let selectedTicker = "TSLA"; let selectedTicker = "TSLA";
let selectedAction = "Buy"; let selectedAction = "Buy";
let selectedOptionPrice; let selectedOptionPrice;
let selectedQuantity = 1;
let debounceTimeout;
let currentStockPrice = data?.getStockQuote?.price;
let optionData = data?.getData[selectedOptionType]; let optionData = data?.getData[selectedOptionType];
let dateList = Object?.keys(optionData); let dateList = Object?.keys(optionData);
let selectedDate = Object?.keys(optionData)[0]; let selectedDate = Object?.keys(optionData)[0];
let strikeList = optionData[selectedDate] || []; let strikeList = optionData[selectedDate] || [];
let deltaList = optionData[selectedDate] || []; let selectedStrike = strikeList.reduce((closest, strike) => {
let selectedStrike = strikeList?.at(0) || []; return Math.abs(strike - currentStockPrice) <
Math.abs(closest - currentStockPrice)
? strike
: closest;
}, strikeList[0]);
let optionSymbol; let optionSymbol;
let breakEvenPrice;
let premium;
let strategies = [ let strategies = [
{ name: "Long Call", sentiment: "Bullish" }, { name: "Long Call", sentiment: "Bullish" },
@ -51,14 +62,26 @@
*/ */
]; ];
const tradeInfo = { const payoffFunctions = {
type: "Long Call", "Buy Call": (s, strike, premium) =>
details: "BUY +1 TSLA Apr 11, 2025 280.0 call @$30.0", s < strike ? -premium : (s - strike) * 100 - premium,
currentPrice: "$239.34",
breakEvenPrice: "$310.00", "Sell Call": (s, strike, premium) =>
costOfTrade: "$3,000.00", s < strike ? premium : premium - (s - strike) * 100,
maxProfit: "Unlimited",
maxLoss: "-$3,000.00", "Buy Put": (s, strike, premium) =>
s > strike ? -premium : (strike - s) * 100 - premium,
"Sell Put": (s, strike, premium) =>
s > strike ? premium : premium - (strike - s) * 100,
};
// Define break-even calculators for each scenario (using per-share price)
const breakEvenCalculators = {
"Buy Call": (strike, optionPrice) => strike + optionPrice,
"Sell Call": (strike, optionPrice) => strike + optionPrice,
"Buy Put": (strike, optionPrice) => strike - optionPrice,
"Sell Put": (strike, optionPrice) => strike - optionPrice,
}; };
const formatDate = (dateString) => { const formatDate = (dateString) => {
@ -71,15 +94,24 @@
}; };
function plotData() { function plotData() {
const action = "Buy"; // total premium paid for 1 contract (premium is calculated per share times 100 shares)
const optionType = "Call"; premium = selectedOptionPrice * 100 * selectedQuantity;
const strikePrice = 230; // Create a key from the selected action and option type
const numOfQuantities = 1; const scenarioKey = `${selectedAction} ${selectedOptionType}`;
const currentStockPrice = 239;
const cost = 30.13; // Calculate break-even price per share using the mapping above.
const premium = cost * 100 * numOfQuantities; // total premium paid for 1 contract // Note: For display, we assume optionPrice is per share.
const breakEven = strikePrice + cost; breakEvenPrice;
if (breakEvenCalculators[scenarioKey]) {
breakEvenPrice = breakEvenCalculators[scenarioKey](
selectedStrike,
selectedOptionPrice,
);
} else {
console.error("Break-even scenario not implemented:", scenarioKey);
breakEvenPrice = selectedStrike; // default fallback
}
// 1) Build payoff data from 0 to 600 (in steps of 10) // 1) Build payoff data from 0 to 600 (in steps of 10)
const dataPoints = []; const dataPoints = [];
@ -87,14 +119,18 @@
const xMax = 600; const xMax = 600;
const step = 10; const step = 10;
if (payoffFunctions[scenarioKey]) {
for (let s = xMin; s <= xMax; s += step) { for (let s = xMin; s <= xMax; s += step) {
// Payoff for a long call: // For each price point, calculate payoff based on the scenario.
// If underlying price < strike, payoff = -premium const payoff = payoffFunctions[scenarioKey](s, selectedStrike, premium);
// Else payoff = (underlying - strike)*100 - premium
const payoff =
s < strikePrice ? -premium : (s - strikePrice) * 100 - premium;
dataPoints.push([s, payoff]); dataPoints.push([s, payoff]);
} }
} else {
console.error(
"Payoff function not implemented for scenario:",
scenarioKey,
);
}
const options = { const options = {
credits: { credits: {
@ -112,12 +148,11 @@
useHTML: true, useHTML: true,
}, },
xAxis: { xAxis: {
// numeric axis from 0 to 600
min: xMin, min: xMin,
max: xMax, max: xMax,
tickInterval: 50, tickInterval: 50,
title: { title: {
text: "TSLA Price at Expiration ($)", text: `${selectedTicker} Price at Expiration ($)`,
style: { color: $mode === "light" ? "#545454" : "white" }, style: { color: $mode === "light" ? "#545454" : "white" },
}, },
labels: { labels: {
@ -137,12 +172,12 @@
}, },
// Break-Even line // Break-Even line
{ {
value: breakEven, value: breakEvenPrice,
color: "#10B981", color: "#10B981",
dashStyle: "Dash", dashStyle: "Dash",
width: 1.2, width: 1.2,
label: { label: {
text: `<span class="text-black dark:text-white">Breakeven $${breakEven.toFixed(2)}</span>`, text: `<span class="text-black dark:text-white">Breakeven $${breakEvenPrice.toFixed(2)}</span>`,
}, },
zIndex: 5, zIndex: 5,
}, },
@ -268,7 +303,12 @@
strikeList = [...optionData[selectedDate]]; strikeList = [...optionData[selectedDate]];
if (!strikeList?.includes(selectedStrike)) { if (!strikeList?.includes(selectedStrike)) {
selectedStrike = strikeList?.at(0); // Set to first element if not found selectedStrike = strikeList.reduce((closest, strike) => {
return Math.abs(strike - currentStockPrice) <
Math.abs(closest - currentStockPrice)
? strike
: closest;
}, strikeList[0]);
} }
optionSymbol = buildOptionSymbol( optionSymbol = buildOptionSymbol(
@ -280,29 +320,48 @@
const output = await getContractHistory(optionSymbol); const output = await getContractHistory(optionSymbol);
selectedOptionPrice = output?.history?.at(-1)?.mark; selectedOptionPrice = output?.history?.at(-1)?.mark;
config = plotData();
isLoaded = true; isLoaded = true;
} }
function handleOptionType() { async function handleOptionType() {
if (selectedOptionType === "Call") { if (selectedOptionType === "Call") {
selectedOptionType = "Put"; selectedOptionType = "Put";
} else { } else {
selectedOptionType = "Call"; selectedOptionType = "Call";
} }
await loadData("optionType");
} }
function handleAction() { async function handleAction() {
if (selectedAction === "Buy") { if (selectedAction === "Buy") {
selectedAction = "Sell"; selectedAction = "Sell";
} else { } else {
selectedAction = "But"; selectedAction = "Buy";
} }
config = plotData();
}
function handleOptionPriceInput(event) {
selectedOptionPrice = +event.target.value;
// Clear any existing debounce timeout
if (debounceTimeout) clearTimeout(debounceTimeout);
// Set a new debounce timeout (1 second)
debounceTimeout = setTimeout(() => {
config = plotData();
}, 500);
} }
onMount(async () => { onMount(async () => {
await loadData("default"); await loadData("default");
}); });
config = plotData(); onDestroy(() => {
if (debounceTimeout) clearTimeout(debounceTimeout);
});
</script> </script>
<SEO <SEO
@ -425,8 +484,8 @@
<tbody class="bg-[#F8F9FA] divide-y divide-gray-200 text-sm"> <tbody class="bg-[#F8F9FA] divide-y divide-gray-200 text-sm">
<!-- Example Option Leg Row --> <!-- Example Option Leg Row -->
<tr> <tr>
<td class="px-4 py-3 whitespace-nowrap font-bold"> <td class="px-4 py-3 whitespace-nowrap font-semibold">
TSLA {selectedTicker}
</td> </td>
<td class="px-4 py-3 whitespace-nowrap"> <td class="px-4 py-3 whitespace-nowrap">
<label <label
@ -550,7 +609,8 @@
<input <input
type="number" type="number"
step="0.1" step="0.1"
value={selectedOptionPrice} 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 rounded px-2 py-1 w-24 focus:outline-none focus:ring-1 focus:ring-blue-500"
/> />
</td> </td>
@ -610,9 +670,13 @@
<div <div
class="border border-gray-300 dark:border-gray-800 rounded-lg p-4 mb-6 shadow-sm max-w-sm" class="border border-gray-300 dark:border-gray-800 rounded-lg p-4 mb-6 shadow-sm max-w-sm"
> >
<div>Long Call</div> <div>{selectedStrategy}</div>
<div class="text-green-800 font-semibold"> <div class="text-green-800 font-semibold">
BUY +1 TSLA Apr 11, 2025 280.0 call @$30.0 {selectedAction?.toUpperCase()} +{selectedQuantity}
{selectedTicker}
{formatDate(selectedDate)}
{selectedStrike}
{selectedOptionType} @${selectedOptionPrice}
</div> </div>
</div> </div>
@ -620,17 +684,19 @@
<h2 class="text-xl font-bold text-gray-800 mb-4">Stock</h2> <h2 class="text-xl font-bold text-gray-800 mb-4">Stock</h2>
<div class="grid grid-cols-2 sm:grid-cols-4 mb-6"> <div class="grid grid-cols-2 sm:grid-cols-4 mb-6">
<div> <div>
<div class="text-gray-600">TSLA Current Price</div> <div class="text-gray-600">
{selectedTicker} Current Price
</div>
<div class="flex items-baseline"> <div class="flex items-baseline">
<span class="text-lg font-semibold" <span class="text-lg font-semibold"
>{tradeInfo.currentPrice}</span >${currentStockPrice}</span
> >
</div> </div>
</div> </div>
<div> <div>
<div class="flex items-center text-gray-600"> <div class="flex items-center text-gray-600">
TSLA Breakeven Price {selectedTicker} Breakeven Price
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4 ml-1" class="h-4 w-4 ml-1"
@ -648,7 +714,7 @@
</div> </div>
<div class="flex items-baseline"> <div class="flex items-baseline">
<span class="text-lg font-semibold" <span class="text-lg font-semibold"
>{tradeInfo.breakEvenPrice}</span >${breakEvenPrice}</span
> >
</div> </div>
</div> </div>
@ -681,7 +747,7 @@
</div> </div>
<div class="flex items-baseline"> <div class="flex items-baseline">
<span class="text-lg font-semibold" <span class="text-lg font-semibold"
>{tradeInfo.costOfTrade}</span >${premium?.toLocaleString("en-US")}</span
> >
</div> </div>
</div> </div>
@ -705,7 +771,7 @@
</svg> </svg>
</div> </div>
<div class="text-lg font-semibold text-green-800"> <div class="text-lg font-semibold text-green-800">
{tradeInfo.maxProfit} Unlimited
</div> </div>
</div> </div>
@ -728,7 +794,7 @@
</svg> </svg>
</div> </div>
<div class="text-lg font-semibold text-red-600"> <div class="text-lg font-semibold text-red-600">
{tradeInfo.maxLoss} -${premium?.toLocaleString("en-US")}
</div> </div>
</div> </div>
</div> </div>