update fail to deliver page

This commit is contained in:
MuslemRahimi 2024-12-31 12:45:08 +01:00
parent 7d86c54431
commit d3080f1e6e
19 changed files with 721 additions and 220 deletions

View File

@ -1,13 +1,9 @@
<script lang="ts">
import {
failToDeliverComponent,
displayCompanyName,
stockTicker,
assetType,
etfTicker,
screenWidth,
getCache,
setCache,
} from "$lib/store";
import InfoModal from "$lib/components/InfoModal.svelte";
import { Chart } from "svelte-echarts";
@ -19,11 +15,11 @@
import { CanvasRenderer } from "echarts/renderers";
export let data;
export let rawData = [];
use([LineChart, GridComponent, TooltipComponent, CanvasRenderer]);
let isLoaded = false;
let rawData = [];
let optionsData;
let avgFailToDeliver;
let lowestPrice;
@ -66,6 +62,41 @@
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;
// Initialize result with timestamp
let result = timestamp + "<br/>";
// Add each series data
params.forEach((param) => {
const marker =
'<span style="display:inline-block;margin-right:4px;' +
"border-radius:10px;width:10px;height:10px;background-color:" +
param.color +
'"></span>';
result +=
marker +
param.seriesName +
": " +
abbreviateNumber(param.value, false, true) +
"<br/>";
});
return result;
},
axisPointer: {
lineStyle: {
color: "#fff",
},
},
},
animation: false,
grid: {
@ -137,45 +168,19 @@
return option;
}
const getFailToDeliver = async (ticker) => {
const cachedData = getCache(ticker, "getFailToDeliver");
if (cachedData) {
rawData = cachedData;
} else {
const postData = { ticker: ticker, path: "fail-to-deliver" };
const response = await fetch("/api/ticker-data", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(postData),
});
rawData = await response.json();
setCache(ticker, rawData, "getFailToDeliver");
}
failToDeliverComponent.set(rawData?.length !== 0);
};
$: {
const ticker = $assetType === "stock" ? $stockTicker : $etfTicker;
if (ticker && typeof window !== "undefined") {
isLoaded = false;
Promise.all([getFailToDeliver(ticker)])
.then(() => {
if (rawData?.length !== 0) {
weightedFTD = (
(rawData?.slice(-1)?.at(0)?.failToDeliver /
data?.getStockQuote?.avgVolume) *
100
)?.toFixed(2);
optionsData = getPlotOptions();
}
})
.catch((error) => {
console.error("An error occurred:", error);
});
console.log(rawData);
if (rawData?.length > 0) {
weightedFTD = (
(rawData?.slice(-1)?.at(0)?.failToDeliver /
data?.getStockQuote?.avgVolume) *
100
)?.toFixed(2);
optionsData = getPlotOptions();
}
isLoaded = true;
}
}
@ -183,18 +188,10 @@
<section class="overflow-hidden text-white h-full pb-8">
<main class="overflow-hidden">
<div class="flex flex-row items-center">
<label
for="failToDeliverInfo"
class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-3xl font-bold"
>
Fail to Deliver
</label>
<InfoModal
title={"Fail to Deliver"}
content={"Failure to deliver in the stock market occurs when a seller does not deliver securities to the buyer within the settlement period. Naked shorts contribute to this by selling shares not owned or borrowed, potentially distorting market dynamics and regulations."}
id={"failToDeliverInfo"}
/>
<div class="flex flex-row items-center w-full mt-5">
<h1 class="text-2xl text-white font-bold">
Historical {$stockTicker} Chart
</h1>
</div>
{#if isLoaded}
@ -261,7 +258,7 @@
</h2>
<div class="flex justify-start items-center w-full m-auto">
<table class="w-full" data-test="statistics-table">
<table class="w-full bg-table border border-gray-800">
<tbody>
<tr class="border-y border-gray-800 odd:bg-odd">
<td class="px-[5px] py-1.5 xs:px-2.5 xs:py-2">

View File

@ -11,7 +11,7 @@
<div
class="relative flex justify-center items-start overflow-hidden w-full"
>
<main class="w-full lg:w-3/4">
<main class="w-full lg:w-3/4 mt-2 sm:mt-0">
<slot />
</main>

View File

@ -1,12 +1,17 @@
import { error, fail, redirect } from "@sveltejs/kit";
import { validateData } from "$lib/utils";
import { loginUserSchema, registerUserSchema } from "$lib/schemas";
export const load = async ({ locals, params }) => {
const { apiKey, apiURL } = locals;
const getOptionsNetFlow = async () => {
const getDailyStats = async () => {
const postData = {
ticker: params.tickerID,
};
const response = await fetch(apiURL + "/options-net-flow-ticker", {
const response = await fetch(apiURL + "/options-stats-ticker", {
method: "POST",
headers: {
"Content-Type": "application/json",
@ -101,10 +106,162 @@ export const load = async ({ locals, params }) => {
// Make sure to return a promise
return {
getOptionsNetFlow: await getOptionsNetFlow(),
getDailyStats: await getDailyStats(),
getOptionsPlotData: await getOptionsPlotData(),
getOptionsHistoricalData: await getOptionsHistoricalData(),
getOptionsChainData: await getOptionsChainData(),
getOptionsGexData: await getOptionsGexData(),
};
};
export const actions = {
login: async ({ url, request, locals }) => {
const path = url?.href?.replace("/oauth2","")
const { formData, errors } = await validateData(
await request.formData(),
loginUserSchema,
);
if (errors) {
return fail(400, {
data: formData,
errors: errors.fieldErrors,
});
}
try {
await locals.pb
.collection("users")
.authWithPassword(formData.email, formData.password);
/*
if (!locals.pb?.authStore?.model?.verified) {
locals.pb.authStore.clear();
return {
notVerified: true,
};
}
*/
} catch (err) {
console.log("Error: ", err);
error(err.status, err.message);
}
redirect(302, path);
},
register: async ({ url, locals, request }) => {
const path = url?.href?.replace("/oauth2","")
const { formData, errors } = await validateData(
await request.formData(),
registerUserSchema,
);
if (errors) {
return fail(400, {
data: formData,
errors: errors.fieldErrors,
});
}
try {
let newUser = await locals.pb.collection("users").create(formData);
/*
await locals.pb?.collection('users').update(
newUser?.id, {
'freeTrial' : true,
'tier': 'Pro', //Give new users a free trial for the Pro Subscription
});
*/
await locals.pb.collection("users")?.requestVerification(formData.email);
} catch (err) {
console.log("Error: ", err);
error(err.status, err.message);
}
try {
await locals.pb
.collection("users")
.authWithPassword(formData.email, formData.password);
} catch (err) {
console.log("Error: ", err);
error(err.status, err.message);
}
redirect(303, path);
},
oauth2: async ({ url, locals, request, cookies }) => {
const path = url?.href?.replace("/oauth2","")
const authMethods = (await locals?.pb
?.collection("users")
?.listAuthMethods())?.oauth2;
const data = await request?.formData();
const providerSelected = data?.get("provider");
if (!authMethods) {
return {
authProviderRedirect: "",
authProviderState: "",
};
}
const redirectURL = `${url.origin}/oauth`;
const targetItem = authMethods?.providers?.findIndex(
(item) => item?.name === providerSelected,
);
//console.log("==================")
//console.log(authMethods.authProviders)
//console.log('target item is: ', targetItem)
const provider = authMethods.providers[targetItem];
const authProviderRedirect = `${provider.authUrl}${redirectURL}`;
const state = provider.state;
const verifier = provider.codeVerifier;
cookies.set("state", state, {
httpOnly: true,
sameSite: "lax",
secure: true,
path: "/",
maxAge: 60 * 60,
});
cookies.set("verifier", verifier, {
httpOnly: true,
sameSite: "lax",
secure: true,
path: "/",
maxAge: 60 * 60,
});
cookies.set("provider", providerSelected, {
httpOnly: true,
sameSite: "lax",
secure: true,
path: "/",
maxAge: 60 * 60,
});
cookies.set("path", path, {
httpOnly: true,
sameSite: "lax",
secure: true,
path: "/",
maxAge: 60,
});
redirect(302, authProviderRedirect);
},
};

View File

@ -7,6 +7,7 @@
setCache,
getCache,
} from "$lib/store";
import DailyStats from "$lib/components/Options/DailyStats.svelte";
import { Chart } from "svelte-echarts";
import { abbreviateNumber } from "$lib/utils";
import InfoModal from "$lib/components/InfoModal.svelte";
@ -16,14 +17,14 @@
import { BarChart, LineChart } from "echarts/charts";
import { GridComponent, TooltipComponent } from "echarts/components";
import { CanvasRenderer } from "echarts/renderers";
import Infobox from "$lib/components/Infobox.svelte";
use([BarChart, LineChart, GridComponent, TooltipComponent, CanvasRenderer]);
export let data;
let isLoaded = false;
let activeEX = 0;
let activeIdx = 0;
let dailyStats = data?.getDailyStats;
const getDailyTransactions = async (transactionId) => {
let output;
const cachedData = getCache(transactionId, "getDailyTransactions");
@ -521,7 +522,7 @@
$: {
if (
(displayTimePeriod || displayData) &&
optionsPlotData?.length !== 0 &&
rawPlotData?.length !== 0 &&
typeof window !== "undefined"
) {
// Filter the raw plot data based on the selected time period
@ -570,47 +571,23 @@
<!-- Add more Twitter meta tags as needed -->
</svelte:head>
<section class="w-full bg-default overflow-hidden text-white h-full">
<section class="w-full bg-default overflow-hidden text-white min-h-screen">
<div class="w-full flex h-full overflow-hidden">
<div
class="w-full relative flex justify-center items-center overflow-hidden"
>
<div class="sm:p-7 w-full m-auto mt-2 sm:mt-0">
<div class="w-full mb-6">
<div
class="w-full m-auto sm:pb-6 {data?.getOptionsNetFlow?.length === 0
? 'hidden'
: ''}"
>
{#await import("$lib/components/OptionsNetFlow.svelte") then { default: Comp }}
<svelte:component this={Comp} rawData={data?.getOptionsNetFlow} />
{/await}
{#if Object?.keys(dailyStats)?.length === 0 && rawPlotData?.length === 0}
<Infobox text="No Options data available" />
{/if}
{#if Object?.keys(dailyStats)?.length > 0}
<div class="w-full mb-10">
<DailyStats rawData={dailyStats} />
</div>
{/if}
<div
class="w-fit text-white p-3 sm:p-5 mb-5 rounded-md sm:flex sm:flex-row sm:items-center border border-gray-600 text-sm sm:text-[1rem]"
>
<svg
class="w-6 h-6 flex-shrink-0 inline-block sm:mr-2"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 256"
><path
fill="#fff"
d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24m-4 48a12 12 0 1 1-12 12a12 12 0 0 1 12-12m12 112a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 0 16"
/></svg
>
{#if optionsPlotData?.length !== 0}
1 Year of options activity involving {$displayCompanyName} by major
institutional traders and hedge funds.
{:else}
There's no data available, indicating that major traders may not
be actively betting on {$displayCompanyName}.
{/if}
</div>
</div>
{#if optionsPlotData?.length !== 0}
{#if rawPlotData?.length > 0}
<div
class="mb-4 grid grid-cols-2 grid-rows-2 divide-gray-600 rounded-md border border-gray-600 md:grid-cols-4 md:grid-rows-1 md:divide-x"
>
@ -779,11 +756,11 @@
</div>
{/if}
<h3 class="text-2xl text-gray-200 font-bold mb-4 text-start">
{activeIdx === 0 ? "Historical Option Data" : "Option Chain Data"}
</h3>
{#if optionList?.length !== 0}
<h3 class="text-2xl text-gray-200 font-bold mb-4 text-start">
{activeIdx === 0 ? "Historical Option Data" : "Option Chain Data"}
</h3>
<div
class="bg-secondary w-fit relative flex flex-wrap items-center justify-center rounded-md p-1 mt-6 mb-6"
>
@ -839,10 +816,10 @@
<div class="flex justify-start items-center m-auto overflow-x-auto">
{#if activeIdx === 0}
<table
class="w-full table table-sm table-compact rounded-none sm:rounded-md border-bg-default m-auto mt-4 overflow-x-auto"
class="w-full table table-sm table-compact bg-table border border-gray-800 rounded-none sm:rounded-md m-auto mt-4 overflow-x-auto"
>
<thead>
<tr class="border-b border-[#27272A]">
<thead class="bg-default">
<tr class="">
<td class="text-white font-semibold text-sm text-start"
>Date</td
>
@ -878,14 +855,16 @@
on:click={() => handleViewData(item?.date)}
on:mouseover={() =>
getDailyTransactions($etfTicker + "+" + item?.date)}
class="cursor-pointer sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] odd:bg-odd border-b-[#09090B] {index +
class="cursor-pointer sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] odd:bg-odd border-b border-gray-800 {index +
1 ===
optionList?.slice(0, 3)?.length &&
data?.user?.tier !== 'Pro'
? 'opacity-[0.1]'
: ''}"
>
<td class="text-white text-sm text-start">
<td
class="text-white text-sm sm:text-[1rem] text-start"
>
{formatDate(item?.date)}
</td>
@ -1012,18 +991,23 @@
</td>
<td class="text-sm sm:text-[1rem] text-white text-end">
{abbreviateNumber(item?.total_volume)}
{@html abbreviateNumber(
item?.total_volume,
false,
true,
)}
</td>
<td class="text-sm sm:text-[1rem] text-end text-white">
{abbreviateNumber(item?.total_oi)}
{@html abbreviateNumber(item?.total_oi, false, true)}
</td>
<td class="text-sm sm:text-[1rem] text-end text-white">
{abbreviateNumber(
{@html abbreviateNumber(
item?.total_bull_prem +
item?.total_bear_prem +
item?.total_neutral_prem,
false,
true,
)}
</td>
@ -1033,33 +1017,29 @@
</table>
{:else}
<table
class="table table-pin-cols table-sm table-compact rounded-none sm:rounded-md w-full border-bg-default m-auto mt-4 overflow-x-auto"
class="table table-pin-cols table-sm bg-table border border-gray-800 table-compact rounded-none sm:rounded-md w-full m-auto mt-4 overflow-x-auto"
>
<thead>
<thead class="bg-default">
<tr class="">
<td class="text-slate-200 font-semibold text-sm text-end"
<td class="text-white font-semibold text-sm text-end"
>Call Prem</td
>
<td class="text-slate-200 font-semibold text-sm text-end"
<td class="text-white font-semibold text-sm text-end"
>Call OI</td
>
<td class="text-slate-200 font-semibold text-sm text-end"
<td class="text-white font-semibold text-sm text-end"
>Call Volume</td
>
<td
class="text-slate-200 font-semibold text-sm text-center"
<td class="text-white font-semibold text-sm text-center"
>Strike Price</td
>
<td
class="text-slate-200 font-semibold text-sm text-start"
<td class="text-white font-semibold text-sm text-start"
>Put Volume</td
>
<td
class="text-slate-200 font-semibold text-sm text-start"
<td class="text-white font-semibold text-sm text-start"
>Put OI</td
>
<td
class="text-slate-200 font-semibold text-sm text-start"
<td class="text-white font-semibold text-sm text-start"
>Put Prem</td
>
</tr>
@ -1067,22 +1047,35 @@
<tbody>
{#each data?.user?.tier === "Pro" ? optionChainList : optionChainList?.slice(0, 3) as item, index}
<tr
class="odd:bg-odd border-b-[#09090B] {index + 1 ===
class="odd:bg-odd border-b border-gray-800 {index +
1 ===
optionChainList?.slice(0, 3)?.length &&
data?.user?.tier !== 'Pro'
? 'opacity-[0.1]'
: ''}"
>
<td class="text-white text-sm sm:text-[1rem] text-end">
{abbreviateNumber(item?.total_premium_call, true)}
{@html abbreviateNumber(
item?.total_premium_call,
false,
true,
)}
</td>
<td class="text-sm sm:text-[1rem] text-end text-white">
{abbreviateNumber(item?.total_open_interest_call)}
{@html abbreviateNumber(
item?.total_open_interest_call,
false,
true,
)}
</td>
<td class="text-sm sm:text-[1rem] text-end text-white">
{abbreviateNumber(item?.total_volume_call)}
{@html abbreviateNumber(
item?.total_volume_call,
false,
true,
)}
</td>
<td
@ -1101,19 +1094,31 @@
<td
class="text-sm sm:text-[1rem] text-start text-white"
>
{abbreviateNumber(item?.total_volume_put)}
{@html abbreviateNumber(
item?.total_volume_put,
false,
true,
)}
</td>
<td
class="text-sm sm:text-[1rem] text-start text-white"
>
{abbreviateNumber(item?.total_open_interest_put)}
{@html abbreviateNumber(
item?.total_open_interest_put,
false,
true,
)}
</td>
<td
class="text-white text-sm sm:text-[1rem] text-start"
>
{abbreviateNumber(item?.total_premium_put, true)}
{@html abbreviateNumber(
item?.total_premium_put,
false,
true,
)}
</td>
</tr>
{/each}
@ -1123,23 +1128,6 @@
</div>
<UpgradeToPro {data} />
{:else}
<div class="flex justify-center items-center m-auto mt-16 mb-6">
<div
class="text-gray-100 text-sm sm:text-[1rem] rounded-md h-auto border border-gray-600 p-4"
>
<svg
class="w-5 h-5 inline-block sm:mr-2 flex-shrink-0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 256"
><path
fill="#fff"
d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24m-4 48a12 12 0 1 1-12 12a12 12 0 0 1 12-12m12 112a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 0 16"
/></svg
>
No Options activity found
</div>
</div>
{/if}
{/if}
</div>
@ -1203,54 +1191,38 @@
<div class="flex justify-start items-center m-auto">
{#if isLoaded}
<table
class="table table-pin-cols table-sm table-compact rounded-none sm:rounded-md w-full border-bg-default m-auto mt-4 overflow-x-auto"
class="table table-pin-cols table-sm bg-table border border-gray-800 table-compact rounded-none sm:rounded-md w-full m-auto mt-4 overflow-x-auto"
>
<thead>
<thead class="bg-default">
<tr class="">
<td class="text-slate-200 font-semibold text-sm text-start"
>Time</td
<td class="text-white font-semibold text-sm text-start">Time</td
>
<td class="text-slate-200 font-semibold text-sm text-start"
>Date</td
<td class="text-white font-semibold text-sm text-start">Date</td
>
<td class="text-slate-200 font-semibold text-sm text-end"
>Expiry</td
<td class="text-white font-semibold text-sm text-end">Expiry</td
>
<td class="text-slate-200 font-semibold text-sm text-end"
>Strike</td
<td class="text-white font-semibold text-sm text-end">Strike</td
>
<td class="text-slate-200 font-semibold text-sm text-end"
>C/P</td
>
<td class="text-slate-200 font-semibold text-sm text-start"
<td class="text-white font-semibold text-sm text-end">C/P</td>
<td class="text-white font-semibold text-sm text-start"
>Sent.</td
>
<td class="text-slate-200 font-semibold text-sm text-start"
<td class="text-white font-semibold text-sm text-start"
>Exec.</td
>
<td class="text-slate-200 font-semibold text-sm text-end"
>Spot</td
>
<td class="text-slate-200 font-semibold text-sm text-end"
>Price</td
>
<td class="text-slate-200 font-semibold text-sm text-end"
>Prem.</td
>
<td class="text-slate-200 font-semibold text-sm text-start"
>Type</td
>
<td class="text-slate-200 font-semibold text-sm text-end"
>Vol.</td
>
<td class="text-slate-200 font-semibold text-sm text-end">OI</td
<td class="text-white font-semibold text-sm text-end">Spot</td>
<td class="text-white font-semibold text-sm text-end">Price</td>
<td class="text-white font-semibold text-sm text-end">Prem.</td>
<td class="text-white font-semibold text-sm text-start">Type</td
>
<td class="text-white font-semibold text-sm text-end">Vol.</td>
<td class="text-white font-semibold text-sm text-end">OI</td>
</tr>
</thead>
<tbody>
{#each optionHistoryList as item}
<!-- row -->
<tr class="odd:bg-odd border-b-[#09090B]">
<tr class="odd:bg-odd border-b border-gray-800">
<td class="text-white text-sm text-start whitespace-nowrap">
{formatTime(item?.time)}
</td>
@ -1298,13 +1270,8 @@
{item?.price}
</td>
<td
class="text-sm sm:text-[1rem] text-end font-medium {item?.put_call ===
'Puts'
? 'text-[#CB281C]'
: 'text-[#0FB307]'} "
>
{abbreviateNumber(item?.cost_basis)}
<td class="text-sm sm:text-[1rem] text-end text-white">
{@html abbreviateNumber(item?.cost_basis, false, true)}
</td>
<td

View File

@ -111,7 +111,7 @@
>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total ETFs</div>
<div class="text-sm font-semibold text-white">Total ETFs</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -121,7 +121,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Assets</div>
<div class="text-sm font-semibold text-white">Total Assets</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -131,7 +131,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Avg. Cost</div>
<div class="text-sm font-semibold text-white">Avg. Cost</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>

View File

@ -38,7 +38,7 @@
>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Stocks</div>
<div class="text-sm font-semibold text-white">Total Stocks</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -48,7 +48,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Market Cap</div>
<div class="text-sm font-semibold text-white">Total Market Cap</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -58,7 +58,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Revenue</div>
<div class="text-sm font-semibold text-white">Total Revenue</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>

View File

@ -33,7 +33,7 @@
>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Stocks</div>
<div class="text-sm font-semibold text-white">Total Stocks</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -43,7 +43,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Market Cap</div>
<div class="text-sm font-semibold text-white">Total Market Cap</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -53,7 +53,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Revenue</div>
<div class="text-sm font-semibold text-white">Total Revenue</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>

View File

@ -23,7 +23,7 @@
>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Stocks</div>
<div class="text-sm font-semibold text-white">Total Stocks</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -33,7 +33,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Market Cap</div>
<div class="text-sm font-semibold text-white">Total Market Cap</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -43,7 +43,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Revenue</div>
<div class="text-sm font-semibold text-white">Total Revenue</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>

View File

@ -34,7 +34,7 @@
>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Stocks</div>
<div class="text-sm font-semibold text-white">Total Stocks</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -44,7 +44,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Market Cap</div>
<div class="text-sm font-semibold text-white">Total Market Cap</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -54,7 +54,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Revenue</div>
<div class="text-sm font-semibold text-white">Total Revenue</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>

View File

@ -30,7 +30,7 @@
>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Stocks</div>
<div class="text-sm font-semibold text-white">Total Stocks</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -40,7 +40,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Market Cap</div>
<div class="text-sm font-semibold text-white">Total Market Cap</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -50,7 +50,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Revenue</div>
<div class="text-sm font-semibold text-white">Total Revenue</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>

View File

@ -24,7 +24,7 @@
>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Stocks</div>
<div class="text-sm font-semibold text-white">Total Stocks</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -34,7 +34,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Market Cap</div>
<div class="text-sm font-semibold text-white">Total Market Cap</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -44,7 +44,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Revenue</div>
<div class="text-sm font-semibold text-white">Total Revenue</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>

View File

@ -120,7 +120,7 @@
>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Stocks</div>
<div class="text-sm font-semibold text-white">Total Stocks</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -130,7 +130,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Market Cap</div>
<div class="text-sm font-semibold text-white">Total Market Cap</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -140,7 +140,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Revenue</div>
<div class="text-sm font-semibold text-white">Total Revenue</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>

View File

@ -28,7 +28,7 @@
>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Stocks</div>
<div class="text-sm font-semibold text-white">Total Stocks</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -38,7 +38,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Market Cap</div>
<div class="text-sm font-semibold text-white">Total Market Cap</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -48,7 +48,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Total Revenue</div>
<div class="text-sm font-semibold text-white">Total Revenue</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>

View File

@ -8,6 +8,7 @@
const subSectionMap = {
"market-cap": "/statistics/market-cap",
employees: "/statistics/employees",
"fail-to-deliver": "/statistics/fail-to-deliver",
};
if (state !== "overview" && subSectionMap[state]) {
@ -25,6 +26,7 @@
const sectionMap = {
"market-cap": "market-cap",
employees: "employees",
"fail-to-deliver": "fail-to-deliver",
};
const foundSection = parts?.find((part) =>
@ -75,6 +77,16 @@
>
Employees
</a>
<a
href={`/stocks/${$stockTicker}/statistics/fail-to-deliver`}
on:click={() => changeSubSection("fail-to-deliver")}
class="p-2 px-5 cursor-pointer {displaySubSection ===
'fail-to-deliver'
? 'text-white bg-primary sm:hover:bg-opacity-[0.95]'
: 'text-gray-400 sm:hover:text-white sm:hover:bg-primary sm:hover:bg-opacity-[0.95]'}"
>
Fail to Deliver
</a>
</ul>
</nav>
</div>

View File

@ -0,0 +1,28 @@
export const load = async ({ locals, params }) => {
const getSimilarStocks = async () => {
const { apiKey, apiURL } = locals;
const postData = {
ticker: params.tickerID,
};
// make the POST request to the endpoint
const response = await fetch(apiURL + "/similar-stocks", {
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 {
getSimilarStocks: await getSimilarStocks(),
};
};

View File

@ -0,0 +1,96 @@
<script lang="ts">
import { abbreviateNumber } from "$lib/utils";
import ArrowLogo from "lucide-svelte/icons/move-up-right";
export let data;
const similarStocks = data?.getSimilarStocks?.sort(
(a, b) => b?.relativeFTD - a?.relativeFTD,
);
</script>
<section class="w-full overflow-hidden">
<div class="w-full overflow-hidden m-auto">
<div class="sm:p-0 flex justify-center w-full m-auto overflow-hidden">
<div
class="relative flex justify-center items-start overflow-hidden w-full"
>
<main class="w-full lg:w-3/4">
<slot />
</main>
<aside class="hidden lg:block relative fixed w-1/4 ml-4">
{#if data?.user?.tier !== "Pro" || data?.user?.freeTrial}
<div
class="w-full text-white border border-gray-600 rounded-md h-fit pb-4 mt-4 cursor-pointer bg-primary sm:hover:bg-secondary transition ease-out duration-100"
>
<a
href="/pricing"
class="w-auto lg:w-full p-1 flex flex-col m-auto px-2 sm:px-0"
>
<div class="w-full flex justify-between items-center p-3 mt-3">
<h2 class="text-start text-xl font-semibold text-white ml-3">
Pro Subscription
</h2>
<ArrowLogo class="w-8 h-8 mr-3 flex-shrink-0" />
</div>
<span class="text-white p-3 ml-3 mr-3">
Upgrade now for unlimited access to all data and tools.
</span>
</a>
</div>
{/if}
{#if similarStocks?.length > 0}
<div
class="w-full p-2 text-white border border-gray-600 bg-primary rounded-md h-fit pb-4 mt-4 cursor-pointer"
>
<h3 class="p-2 pt-4 text-2xl font-semibold">Related Stocks</h3>
<table class="table table-sm table-compact w-full text-white">
<thead class="text-white"
><tr
><th
class="whitespace-nowrap border-b font-semibold text-[1rem] text-left"
>Company</th
>
<th
class="whitespace-nowrap border-b font-semibold text-[1rem] text-right"
>FTD / Avg Volume</th
></tr
></thead
>
<tbody>
{#each similarStocks?.slice(0, 8) as item, index}
{#if item?.relativeFTD > 0}
<tr
class="border-gray-600 text-[1rem] {index !==
similarStocks?.slice(0, 8).length - 1
? 'border-b'
: ''}"
><td class="text-left text-[1rem]"
><a
href={`/stocks/${item?.symbol}`}
class="sm:hover:text-white text-blue-400"
>{item?.symbol}</a
></td
>
<td class="text-right cursor-normal text-[1rem]"
>{abbreviateNumber(item?.relativeFTD)}%</td
>
</tr>
{/if}
{/each}
</tbody>
</table>
<a
href="/list/most-ftd-shares"
class="flex justify-center items-center rounded cursor-pointer w-full py-2 mt-3 text-[1rem] text-center font-semibold text-black m-auto sm:hover:bg-gray-300 bg-[#fff] transition duration-100"
>
Fail-to-Deliver Ranks
</a>
</div>
{/if}
</aside>
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,29 @@
export const load = async ({ locals, params }) => {
const getData = async () => {
const { apiKey, apiURL } = locals;
const postData = {
ticker: params.tickerID,
};
// make the POST request to the endpoint
const response = await fetch(apiURL + "/fail-to-deliver", {
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(),
};
};

View File

@ -0,0 +1,195 @@
<script lang="ts">
import {
numberOfUnreadNotification,
displayCompanyName,
stockTicker,
} from "$lib/store";
import { abbreviateNumber } from "$lib/utils";
import { onMount } from "svelte";
import FailToDeliver from "$lib/components/FailToDeliver.svelte";
export let data;
let isLoaded = false;
let rawData = data?.getData || [];
let changePercentageYearAgo = 0;
function computeYearOverYearChange(rawData) {
if (rawData.length < 2) {
return null; // Not enough rawData to compute change
}
// Step 1: Get the last entry in the list
const lastEntry = rawData[rawData.length - 1];
const lastDate = new Date(lastEntry.date);
const lastValue = rawData?.slice(-1)?.at(0).failToDeliver;
// Step 2: Find the entry closest to one year before the last date
let closestEntry = null;
for (let i = rawData.length - 2; i >= 0; i--) {
const entryDate = new Date(rawData[i].date);
const oneYearAgo = new Date(lastDate);
oneYearAgo.setFullYear(lastDate.getFullYear() - 1);
// Check if the entry is close to one year ago
if (entryDate <= oneYearAgo) {
closestEntry = rawData[i];
break;
}
}
if (!closestEntry) {
return null; // No suitable entry found for comparison
}
const prevValue = closestEntry?.failToDeliver;
// Step 3: Calculate the percentage change
const change = ((lastValue - prevValue) / prevValue) * 100;
return change;
}
onMount(async () => {
changePercentageYearAgo = computeYearOverYearChange(rawData);
isLoaded = true;
});
</script>
<svelte:head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>
{$numberOfUnreadNotification > 0 ? `(${$numberOfUnreadNotification})` : ""}
{$displayCompanyName} ({$stockTicker}) Fail-to-Deliver Shares · Stocknear
</title>
<meta
name="description"
content={`Historical Fail-to-Deliver shares of ${$stockTicker}.`}
/>
<meta
property="og:title"
content={`${$displayCompanyName} (${$stockTicker}) Fail-to-Deliver Shares · Stocknear`}
/>
<meta
property="og:description"
content={`Historical Fail-to-Deliver shares of ${$stockTicker}.`}
/>
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content={`${$displayCompanyName} (${$stockTicker}) Fail-to-Deliver Shares · Stocknear`}
/>
<meta
name="twitter:description"
content={`Historical Fail-to-Deliver shares of ${$stockTicker}.`}
/>
</svelte:head>
<section
class="bg-default w-full overflow-hidden min-h-screen text-white h-full"
>
<div class="w-full flex justify-center w-full sm-auto h-full overflow-hidden">
<div
class="w-full relative flex justify-center items-center overflow-hidden"
>
{#if isLoaded}
<main class="w-full">
<div class="sm:p-7 m-auto mt-2 sm:mt-0">
<div class="mb-3">
<h1 class="text-xl sm:text-2xl text-white font-bold">
Fail-to-Deliver (FTD)
</h1>
</div>
{#if rawData?.length !== 0}
<div class="grid grid-cols-1 gap-2">
<div
class="mb-4 mt-5 bg-primary flex flex-col divide-y divide-gray-600 rounded-md border border-gray-600 sm:grid sm:grid-cols-3 sm:divide-x sm:divide-y-0"
>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-semibold text-white">
FTD Shares
</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
{abbreviateNumber(
rawData?.slice(-1)?.at(0)?.failToDeliver,
false,
)}
</div>
</div>
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-semibold text-white">
FTD / Avg Volume
</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
{data?.getStockQuote?.avgVolume > 0
? (
(rawData?.slice(-1)?.at(0)?.failToDeliver /
data?.getStockQuote?.avgVolume) *
100
)?.toFixed(2) + "%"
: "n/a"}
</div>
</div>
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-semibold text-white">
1-Year Change
</div>
<div
class="mt-1 break-words font-semibold leading-8 tiny:text-lg xs:text-xl sm:text-2xl {changePercentageYearAgo >=
0 && changePercentageYearAgo !== null
? "before:content-['+'] text-[#00FC50]"
: changePercentageYearAgo < 0 &&
changePercentageYearAgo !== null
? 'text-[#FF2F1F]'
: 'text-white'}"
>
{changePercentageYearAgo !== null
? abbreviateNumber(
changePercentageYearAgo?.toFixed(2),
) + "%"
: "n/a"}
</div>
</div>
</div>
</div>
<FailToDeliver {data} {rawData} />
</div>
{:else}
<h2
class="mt-16 flex justify-center items-center text-2xl font-medium text-white mb-5 m-auto"
>
No data available
</h2>
{/if}
</div>
</main>
{:else}
<div class="w-full flex justify-center items-center h-80">
<div class="relative">
<label
class="bg-odd rounded-md h-14 w-14 flex justify-center items-center absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
>
<span class="loading loading-spinner loading-md text-gray-400"
></span>
</label>
</div>
</div>
{/if}
</div>
</div>
</section>

View File

@ -318,15 +318,33 @@
color: "#fff", // Optional: Text color for better visibility
},
formatter: function (params) {
const date = params[0].name; // Get the date from the x-axis value
const dateParts = date.split("-");
const year = dateParts[0];
const monthIndex = parseInt(dateParts[1]) - 1;
const day = dateParts[2];
const formattedDate = `${monthNames[monthIndex]} ${day}, ${year}`;
// Get the timestamp from the first parameter
const timestamp = params[0].axisValue;
// Return the tooltip content
return `${formattedDate}<br/> ${abbreviateNumber(params[0].value)}`;
// Initialize result with timestamp
let result = timestamp + "<br/>";
// Add each series data
params.forEach((param) => {
const marker =
'<span style="display:inline-block;margin-right:4px;' +
"border-radius:10px;width:10px;height:10px;background-color:" +
param.color +
'"></span>';
result +=
marker +
param.seriesName +
": " +
abbreviateNumber(param.value, false, true) +
"<br/>";
});
return result;
},
axisPointer: {
lineStyle: {
color: "#fff",
},
},
},
};
@ -439,7 +457,7 @@
>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">
<div class="text-sm font-semibold text-white">
Market Cap
</div>
<div
@ -455,7 +473,9 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">Category</div>
<div class="text-sm font-semibold text-white">
Category
</div>
<div
class="mt-1 break-words font-semibold leading-8 text-white tiny:text-lg xs:text-xl sm:text-2xl"
>
@ -474,7 +494,7 @@
</div>
<div class="px-4 py-3 sm:px-2 sm:py-5 md:px-3 lg:p-6">
<div class="flex items-center justify-between sm:block">
<div class="text-sm font-normal text-white">
<div class="text-sm font-semibold text-white">
1-Year Change
</div>
<div