clean code
This commit is contained in:
parent
46e5d91b7f
commit
e97b356d27
@ -1,147 +0,0 @@
|
||||
<script lang 'ts'></script>
|
||||
|
||||
<section class="overflow-hidden text-white h-full pb-8">
|
||||
<main class="overflow-hidden">
|
||||
<div class="flex flex-row items-center">
|
||||
<label
|
||||
for="borrowedShareInfo"
|
||||
class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-3xl font-bold"
|
||||
>
|
||||
Borrowed Share
|
||||
</label>
|
||||
<InfoModal
|
||||
title={"Borrowed Share Statistics"}
|
||||
content={"At Interactive Brokers, borrowed shares refer to shares lent by other investors for short selling. Borrowers pay a fee to lenders, aiming to profit from declining stock prices. Lenders earn interest on their lent shares, enhancing returns while still owning the stock."}
|
||||
id={"borrowedShareInfo"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if isLoaded}
|
||||
{#if rawData?.length !== 0}
|
||||
<div class="w-full flex flex-col items-start">
|
||||
<div class="text-white text-[1rem] mt-2 mb-2 w-full">
|
||||
Over the past six months, Interactive Brokers had {abbreviateNumber(
|
||||
totalAvailableShares,
|
||||
)} shares available for borrowing, with an average fee of {avgFee}%.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pb-2 rounded-md bg-default">
|
||||
<div class="app w-full h-[300px] mt-5">
|
||||
<Chart {init} options={optionsData} class="chart" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-row items-center justify-between mx-auto mt-5 w-full sm:w-11/12"
|
||||
>
|
||||
<div
|
||||
class="mt-3.5 sm:mt-0 flex flex-col sm:flex-row items-center ml-3 sm:ml-0 w-1/2 justify-center"
|
||||
>
|
||||
<div
|
||||
class="h-full transform -translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<div
|
||||
class="w-3 h-3 bg-[#fff] border-4 box-content border-[#27272A] rounded-full transform sm:-translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<span
|
||||
class="mt-2 sm:mt-0 text-white text-center sm:text-start text-xs sm:text-md inline-block"
|
||||
>
|
||||
Available Shares
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col sm:flex-row items-center ml-3 sm:ml-0 w-1/2 justify-center"
|
||||
>
|
||||
<div
|
||||
class="h-full transform -translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<div
|
||||
class="w-3 h-3 bg-[#22C55E] border-4 box-content border-[#27272A] rounded-full transform sm:-translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<span
|
||||
class="mt-2 sm:mt-0 text-white text-xs sm:text-md sm:font-medium inline-block"
|
||||
>
|
||||
Fee
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2
|
||||
class="mt-10 mr-1 flex flex-row items-center text-white text-xl sm:text-2xl font-bold mb-3"
|
||||
>
|
||||
Latest Information
|
||||
</h2>
|
||||
|
||||
<div class="flex justify-start items-center w-full m-auto">
|
||||
<table class="w-full" data-test="statistics-table">
|
||||
<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">
|
||||
<span>Date</span>
|
||||
</td>
|
||||
<td
|
||||
class="px-[5px] py-1.5 text-right whitespace-nowrap font-medium xs:px-2.5 xs:py-2"
|
||||
>
|
||||
{formatDateRange(rawData?.slice(-1)?.at(0)?.date)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-y border-gray-800 odd:bg-odd">
|
||||
<td class="px-[5px] py-1.5 xs:px-2.5 xs:py-2">
|
||||
<span>Fee Range</span>
|
||||
</td>
|
||||
<td
|
||||
class="px-[5px] py-1.5 text-right font-medium xs:px-2.5 xs:py-2"
|
||||
>
|
||||
{lowestFee + "%" + "-" + highestFee + "%"}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-y border-gray-800 odd:bg-odd">
|
||||
<td class="px-[5px] py-1.5 xs:px-2.5 xs:py-2">
|
||||
<span>Total Available Shares</span>
|
||||
</td>
|
||||
<td
|
||||
class="px-[5px] py-1.5 text-right font-medium xs:px-2.5 xs:py-2"
|
||||
>
|
||||
{abbreviateNumber(monthlyAvailableShares)}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex justify-center items-center h-80">
|
||||
<div class="relative">
|
||||
<label
|
||||
class="bg-secondary 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}
|
||||
</main>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.app {
|
||||
height: 300px;
|
||||
max-width: 100%; /* Ensure chart width doesn't exceed the container */
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.app {
|
||||
height: 210px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -1,159 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { etfTicker, screenWidth } from "$lib/store";
|
||||
import { formatString } from "$lib/utils";
|
||||
import InfoModal from "$lib/components/InfoModal.svelte";
|
||||
import { countryList } from "$lib/country-list.ts";
|
||||
|
||||
export let geographicList;
|
||||
|
||||
$: {
|
||||
if (
|
||||
$etfTicker &&
|
||||
typeof window !== "undefined" &&
|
||||
typeof geographicList !== "undefined" &&
|
||||
geographicList?.length !== 0
|
||||
) {
|
||||
showFullStats = false;
|
||||
|
||||
// Create an index for quick lookups
|
||||
const countryIndex = countryList?.reduce((acc, country) => {
|
||||
acc[country.long] = country.short;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Update the originalList with the "code" property
|
||||
geographicList = geographicList?.map((item) => ({
|
||||
...item,
|
||||
code: countryIndex[item.country]?.toLowerCase() || "xx",
|
||||
}));
|
||||
|
||||
geographicList = [...geographicList];
|
||||
}
|
||||
}
|
||||
let showFullStats = false;
|
||||
let charNumber = 40;
|
||||
|
||||
$: {
|
||||
if ($screenWidth < 640) {
|
||||
charNumber = 25;
|
||||
} else {
|
||||
charNumber = 40;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="bg-default overflow-hidden text-white h-full sm:mb-0">
|
||||
<div class="flex justify-center m-auto h-full overflow-hidden">
|
||||
<div
|
||||
class="relative flex justify-center items-center overflow-hidden w-full"
|
||||
>
|
||||
<div class="w-full">
|
||||
<div class="flex flex-row items-center">
|
||||
<label
|
||||
for="countryWeightingInfo"
|
||||
class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-3xl font-bold"
|
||||
>
|
||||
Country Weighting Breakdown
|
||||
</label>
|
||||
<InfoModal
|
||||
title={"Country Weighting Breakdown"}
|
||||
content={"Explore the degree to which your ETF's performance is influenced by individual countries."}
|
||||
id={"countryWeightingInfo"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if geographicList?.length !== 0}
|
||||
<div class="text-white text-md mt-3">
|
||||
The ETF is build on top of {geographicList?.length} regions:
|
||||
</div>
|
||||
|
||||
<div class="w-full rounded-full flex justify-center items-center">
|
||||
<div class="flex flex-col items-center w-full">
|
||||
<!--Start Progress-->
|
||||
{#each showFullStats ? geographicList : geographicList?.slice(0, 3) as item, index}
|
||||
<div
|
||||
class="shadow-lg bg-primary w-full rounded-md p-4 sm:p-3 mb-5 flex flex-row items-center {index ===
|
||||
0
|
||||
? 'mt-4'
|
||||
: ''} {index === 2 &&
|
||||
!showFullStats &&
|
||||
geographicList?.length > 2
|
||||
? 'opacity-[0.3]'
|
||||
: ''}"
|
||||
>
|
||||
<div
|
||||
class="flex-shrink-0 mr-3 rounded-full w-10 h-10 relative bg-default"
|
||||
>
|
||||
<img
|
||||
class="flex-shrink-0 rounded-full w-7 h-7 absolute inset-1/2 transform -translate-x-1/2 -translate-y-1/2"
|
||||
src={`https://hatscripts.github.io/circle-flags/flags/${item?.code}.svg`}
|
||||
alt="Country Logo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col -mt-3 sm:-mt-5 w-full">
|
||||
<div class="flex flex-row items-center w-full">
|
||||
<span
|
||||
class="text-white text-sm sm:text-md font-medium text-start mb-2 mr-auto mt-2"
|
||||
>
|
||||
{item?.country?.length > charNumber
|
||||
? formatString(item?.country)?.slice(0, charNumber) +
|
||||
"..."
|
||||
: formatString(item?.country)}
|
||||
</span>
|
||||
<span
|
||||
class="text-white text-sm sm:text-md font-medium ml-auto"
|
||||
>
|
||||
{item?.weightPercentage <= 0.01
|
||||
? "< 0.01%"
|
||||
: item?.weightPercentage?.toFixed(2) + "%"}
|
||||
</span>
|
||||
</div>
|
||||
<progress
|
||||
class="progress [&::-webkit-progress-value]:bg-[#00FC50] [&::-moz-progress-bar]:bg-[#00FC50]"
|
||||
value={item?.weightPercentage}
|
||||
max="100"
|
||||
></progress>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<!--End Progress-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if geographicList?.length > 2}
|
||||
<label
|
||||
on:click={() => (showFullStats = !showFullStats)}
|
||||
class="cursor-pointer m-auto flex justify-center items-center mt-5"
|
||||
>
|
||||
<svg
|
||||
class="w-10 h-10 transform {showFullStats ? 'rotate-180' : ''} "
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
fill="#2A323C"
|
||||
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2zm0 13.5L7.5 11l1.42-1.41L12 12.67l3.08-3.08L16.5 11L12 15.5z"
|
||||
/></svg
|
||||
>
|
||||
</label>
|
||||
{/if}
|
||||
{:else}
|
||||
<h2
|
||||
class="mt-10 justify-center items-center text-3xl font-bold text-slate-700 mb-5 m-auto"
|
||||
>
|
||||
No data available
|
||||
<svg
|
||||
class="w-10 sm:w-12 inline-block"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
fill="#334155"
|
||||
d="M18.68 12.32a4.49 4.49 0 0 0-6.36.01a4.49 4.49 0 0 0 0 6.36a4.508 4.508 0 0 0 5.57.63L21 22.39L22.39 21l-3.09-3.11c1.13-1.77.87-4.09-.62-5.57m-1.41 4.95c-.98.98-2.56.97-3.54 0c-.97-.98-.97-2.56.01-3.54c.97-.97 2.55-.97 3.53 0c.97.98.97 2.56 0 3.54M10.9 20.1a6.527 6.527 0 0 1-1.48-2.32C6.27 17.25 4 15.76 4 14v3c0 2.21 3.58 4 8 4c-.4-.26-.77-.56-1.1-.9M4 9v3c0 1.68 2.07 3.12 5 3.7v-.2c0-.93.2-1.85.58-2.69C6.34 12.3 4 10.79 4 9m8-6C7.58 3 4 4.79 4 7c0 2 3 3.68 6.85 4h.05c1.2-1.26 2.86-2 4.6-2c.91 0 1.81.19 2.64.56A3.215 3.215 0 0 0 20 7c0-2.21-3.58-4-8-4Z"
|
||||
/></svg
|
||||
>
|
||||
</h2>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -1,288 +0,0 @@
|
||||
<script lang="ts">
|
||||
//import ProgressBar from 'progressbar.js';
|
||||
//import { onMount } from 'svelte';
|
||||
import { stockTicker, screenWidth } from "$lib/store";
|
||||
|
||||
export let stockDeck;
|
||||
|
||||
let info;
|
||||
let esgScore;
|
||||
let socialScore;
|
||||
let environmentalScore;
|
||||
let governanceScore;
|
||||
let esgRiskRating;
|
||||
|
||||
$: {
|
||||
if (
|
||||
$stockTicker &&
|
||||
typeof window !== "undefined" &&
|
||||
typeof stockDeck !== "undefined" &&
|
||||
stockDeck?.length !== 0
|
||||
) {
|
||||
info = stockDeck?.at(0);
|
||||
esgScore =
|
||||
info?.esgScore !== "n/a"
|
||||
? Math.round(info?.esgScore.toFixed(1))
|
||||
: "n/a";
|
||||
socialScore =
|
||||
info?.socialScore !== "n/a"
|
||||
? Math.round(info?.socialScore.toFixed(1))
|
||||
: "n/a";
|
||||
environmentalScore =
|
||||
info?.environmentalScore !== "n/a"
|
||||
? Math.round(info?.environmentalScore?.toFixed(1))
|
||||
: "n/a";
|
||||
governanceScore =
|
||||
info?.governanceScore !== "n/a"
|
||||
? Math.round(info?.governanceScore?.toFixed(1))
|
||||
: "n/a";
|
||||
esgRiskRating = info?.esgRiskRating;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!--Start ESG Card -->
|
||||
<div
|
||||
class="space-y-3 lg:pt-5 hidden lg:block lg:{esgScore &&
|
||||
esgRiskRating &&
|
||||
environmentalScore &&
|
||||
governanceScore !== 'n/a'
|
||||
? ''
|
||||
: 'hidden'}"
|
||||
>
|
||||
<div
|
||||
class="sm:rounded-md shadow-lg bg-[#000] sm:bg-default sm:border sm:border-gray-600 h-auto sm:h-[470px] {$screenWidth <
|
||||
640
|
||||
? 'w-screen pt-16'
|
||||
: ''} md:w-[420px] xl:w-[450px] -mx-1 sm:mx-0"
|
||||
>
|
||||
<!--Start Content-->
|
||||
<div
|
||||
class="w-auto lg:w-full p-1 flex flex-col m-auto pb-14 sm:pb-10 px-2 sm:px-0"
|
||||
>
|
||||
<h2 class="text-start text-2xl font-semibold text-white p-3 mt-3 ml-1">
|
||||
ESG Score
|
||||
</h2>
|
||||
|
||||
{#if esgScore && esgRiskRating && environmentalScore && governanceScore !== "n/a"}
|
||||
<p class="text-white mb-5 ml-4 mr-1">
|
||||
Gain valuable insights into a company's sustainability by evaluating
|
||||
its ESG (Environmental, Social, and Governance) scores.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col m-auto items-center rounded-md w-full mb-16">
|
||||
<div class="flex flex-col items-center w-full">
|
||||
<div class="flex flex-row items-center w-11/12 mt-2 mb-2">
|
||||
<span class="text-white font-medium text-start mr-auto">
|
||||
Total
|
||||
</span>
|
||||
<span class="text-white text-md font-medium ml-auto">
|
||||
{esgScore}
|
||||
</span>
|
||||
</div>
|
||||
<progress
|
||||
class="progress bg-[#3B3D3F] w-11/12 {esgScore >= 50
|
||||
? '[&::-webkit-progress-value]:bg-[#00FC50] [&::-moz-progress-bar]:bg-[#00FC50]'
|
||||
: '[&::-webkit-progress-value]:bg-[#FF2F1F] [&::-moz-progress-bar]:bg-[#FF2F1F]'}"
|
||||
value={esgScore}
|
||||
max="100"
|
||||
></progress>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center w-full mt-2">
|
||||
<div class="flex flex-row items-center w-11/12 mt-5 mb-2">
|
||||
<span class="text-white font-medium text-start mr-auto">
|
||||
Environment
|
||||
</span>
|
||||
<span class="text-white text-md font-medium ml-auto">
|
||||
{environmentalScore}
|
||||
</span>
|
||||
</div>
|
||||
<progress
|
||||
class="progress bg-[#3B3D3F] w-11/12 {environmentalScore >= 50
|
||||
? '[&::-webkit-progress-value]:bg-[#00FC50] [&::-moz-progress-bar]:bg-[#00FC50]'
|
||||
: '[&::-webkit-progress-value]:bg-[#FF2F1F] [&::-moz-progress-bar]:bg-[#FF2F1F]'}"
|
||||
value={environmentalScore}
|
||||
max="100"
|
||||
></progress>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center w-full">
|
||||
<div class="flex flex-row items-center w-11/12 mt-5 mb-2">
|
||||
<span class="text-white font-medium text-start mr-auto">
|
||||
Social
|
||||
</span>
|
||||
<span class="text-white text-md font-medium ml-auto">
|
||||
{socialScore}
|
||||
</span>
|
||||
</div>
|
||||
<progress
|
||||
class="progress bg-[#3B3D3F] w-11/12 {socialScore >= 50
|
||||
? '[&::-webkit-progress-value]:bg-[#00FC50] [&::-moz-progress-bar]:bg-[#00FC50]'
|
||||
: '[&::-webkit-progress-value]:bg-[#FF2F1F] [&::-moz-progress-bar]:bg-[#FF2F1F]'}"
|
||||
value={socialScore}
|
||||
max="100"
|
||||
></progress>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center w-full">
|
||||
<div class="flex flex-row items-center w-11/12 mt-5 mb-2">
|
||||
<span class="text-white font-medium text-start mr-auto">
|
||||
Governance
|
||||
</span>
|
||||
<span class="text-white text-md font-medium ml-auto">
|
||||
{governanceScore}
|
||||
</span>
|
||||
</div>
|
||||
<progress
|
||||
class="progress bg-[#3B3D3F] w-11/12 {governanceScore >= 50
|
||||
? '[&::-webkit-progress-value]:bg-[#00FC50] [&::-moz-progress-bar]:bg-[#00FC50]'
|
||||
: '[&::-webkit-progress-value]:bg-[#FF2F1F] [&::-moz-progress-bar]:bg-[#FF2F1F]'}"
|
||||
value={governanceScore}
|
||||
max="100"
|
||||
></progress>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<h2
|
||||
class="mt-20 justify-center items-center text-3xl font-bold text-slate-700 mb-20 m-auto"
|
||||
>
|
||||
No data available
|
||||
</h2>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--End ESG Card-->
|
||||
|
||||
<!--Start Mobile ESG Card-->
|
||||
<div class="lg:hidden space-y-3 sm:pt-5">
|
||||
<div class="bg-[#000] h-auto w-screen">
|
||||
<!--Start Header-->
|
||||
<div class="w-full p-1 flex flex-col items-center pb-5 h-auto">
|
||||
<h2
|
||||
class="text-center m-auto text-[1.1rem] font-semibold text-white mt-5"
|
||||
>
|
||||
ESG Score
|
||||
</h2>
|
||||
|
||||
<div class="flex flex-col items-center mt-10 mb-5 w-full px-3">
|
||||
<span class="text-white text-center text-md">
|
||||
Gain valuable insights into a company's sustainability by evaluating
|
||||
its ESG (Environmental, Social, and Governance) scores.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!--End Header-->
|
||||
|
||||
{#if esgScore && esgRiskRating && environmentalScore && governanceScore !== "n/a"}
|
||||
<div
|
||||
class="mt-5 flex flex-col m-auto items-center rounded-md w-full mb-16 p-3"
|
||||
>
|
||||
<div
|
||||
class="shadow-lg bg-primary w-full rounded-md p-4 mb-5 flex flex-row items-center"
|
||||
>
|
||||
<div class="flex flex-col -mt-2 w-full">
|
||||
<div class="flex flex-row items-center w-full">
|
||||
<span
|
||||
class="text-white text-md font-medium text-start mb-2 mr-auto mt-2"
|
||||
>
|
||||
Total
|
||||
</span>
|
||||
<span class="text-white text-md font-medium ml-auto">
|
||||
{esgScore}
|
||||
</span>
|
||||
</div>
|
||||
<progress
|
||||
class="progress w-full {esgScore >= 50
|
||||
? '[&::-webkit-progress-value]:bg-[#00FC50] [&::-moz-progress-bar]:bg-[#00FC50]'
|
||||
: '[&::-webkit-progress-value]:bg-[#FF2F1F] [&::-moz-progress-bar]:bg-[#FF2F1F]'}"
|
||||
value={esgScore}
|
||||
max="100"
|
||||
></progress>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="shadow-lg bg-primary w-full rounded-md p-4 mb-5 flex flex-row items-center"
|
||||
>
|
||||
<div class="flex flex-col -mt-2 w-full">
|
||||
<div class="flex flex-row items-center w-full">
|
||||
<span
|
||||
class="text-white text-md font-medium text-start mb-2 mr-auto mt-2"
|
||||
>
|
||||
Environment
|
||||
</span>
|
||||
<span class="text-white text-md font-medium ml-auto">
|
||||
{environmentalScore}
|
||||
</span>
|
||||
</div>
|
||||
<progress
|
||||
class="progress w-full {environmentalScore >= 50
|
||||
? '[&::-webkit-progress-value]:bg-[#00FC50] [&::-moz-progress-bar]:bg-[#00FC50]'
|
||||
: '[&::-webkit-progress-value]:bg-[#FF2F1F] [&::-moz-progress-bar]:bg-[#FF2F1F]'}"
|
||||
value={environmentalScore}
|
||||
max="100"
|
||||
></progress>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="shadow-lg bg-primary w-full rounded-md p-4 mb-5 flex flex-row items-center"
|
||||
>
|
||||
<div class="flex flex-col -mt-2 w-full">
|
||||
<div class="flex flex-row items-center w-full">
|
||||
<span
|
||||
class="text-white text-md font-medium text-start mb-2 mr-auto mt-2"
|
||||
>
|
||||
Social
|
||||
</span>
|
||||
<span class="text-white text-md font-medium ml-auto">
|
||||
{socialScore}
|
||||
</span>
|
||||
</div>
|
||||
<progress
|
||||
class="progress w-full {socialScore >= 50
|
||||
? '[&::-webkit-progress-value]:bg-[#00FC50] [&::-moz-progress-bar]:bg-[#00FC50]'
|
||||
: '[&::-webkit-progress-value]:bg-[#FF2F1F] [&::-moz-progress-bar]:bg-[#FF2F1F]'}"
|
||||
value={socialScore}
|
||||
max="100"
|
||||
></progress>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="shadow-lg bg-primary w-full rounded-md p-4 mb-5 flex flex-row items-center"
|
||||
>
|
||||
<div class="flex flex-col -mt-2 w-full">
|
||||
<div class="flex flex-row items-center w-full">
|
||||
<span
|
||||
class="text-white text-md font-medium text-start mb-2 mr-auto mt-2"
|
||||
>
|
||||
Governance
|
||||
</span>
|
||||
<span class="text-white text-md font-medium ml-auto">
|
||||
{governanceScore}
|
||||
</span>
|
||||
</div>
|
||||
<progress
|
||||
class="progress w-full {governanceScore >= 50
|
||||
? '[&::-webkit-progress-value]:bg-[#00FC50] [&::-moz-progress-bar]:bg-[#00FC50]'
|
||||
: '[&::-webkit-progress-value]:bg-[#FF2F1F] [&::-moz-progress-bar]:bg-[#FF2F1F]'}"
|
||||
value={governanceScore}
|
||||
max="100"
|
||||
></progress>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class=" mt-20 flex justify-center items-center text-2xl font-bold text-slate-700 mb-20 m-auto"
|
||||
>
|
||||
No data available
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<!--End Mobile ESG Card-->
|
||||
@ -1,145 +0,0 @@
|
||||
<script lang 'ts'></script>
|
||||
|
||||
<section class="overflow-hidden text-white h-full pb-8 sm:pb-2">
|
||||
<main class="overflow-hidden">
|
||||
<div class="flex flex-row items-center">
|
||||
<label
|
||||
for="enterpriseValueInfo"
|
||||
class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-3xl font-bold"
|
||||
>
|
||||
Enterprise Value
|
||||
</label>
|
||||
<InfoModal
|
||||
title={"Enterprise Value"}
|
||||
content={"Enterprise value (EV) is a comprehensive measure of a company's total value in the stock market, considering market capitalization, debt, cash, and minority interests. It helps in M&A analysis, comparative analysis, valuation metrics, and capital structure assessment, aiding investors, analysts, and companies in financial decision-making."}
|
||||
id={"enterpriseValueInfo"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if isLoaded}
|
||||
{#if rawData?.length !== 0}
|
||||
<div class="mt-2 pb-4">
|
||||
<div class="w-full flex flex-col items-start">
|
||||
<div class="text-white text-[1rem] mt-1 sm:mt-3 mb-1 w-full">
|
||||
{$displayCompanyName}'s' enterprise value provides a comprehensive
|
||||
snapshot of its total worth, crucial for assessing its financial
|
||||
health and making informed investment decisions.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href={"/stocks/" + $stockTicker + "/stats/income"}
|
||||
class="text-blue-400 hover:text-white flex justify-end mt-3 text-sm sm:text-[1rem]"
|
||||
>
|
||||
Full report
|
||||
</a>
|
||||
|
||||
<div class="app w-full h-[300px] mt-5">
|
||||
<Chart {init} options={optionsData} class="chart" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-row items-center justify-between mx-auto mt-5 w-full sm:w-11/12"
|
||||
>
|
||||
<div
|
||||
class="mt-3.5 sm:mt-0 flex flex-col sm:flex-row items-center ml-3 sm:ml-0 w-1/2 justify-center"
|
||||
>
|
||||
<div
|
||||
class="h-full transform -translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<div
|
||||
class="w-3 h-3 bg-[#22C55E] border-4 box-content border-[#27272A] rounded-full transform sm:-translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<span
|
||||
class="mt-2 sm:mt-0 text-white text-center sm:text-start text-xs sm:text-md inline-block"
|
||||
>
|
||||
Enterprise Value
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col sm:flex-row items-center ml-3 sm:ml-0 w-1/2 justify-center"
|
||||
>
|
||||
<div
|
||||
class="h-full transform -translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<div
|
||||
class="w-3 h-3 bg-[#E11D48] border-4 box-content border-[#27272A] rounded-full transform sm:-translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<span
|
||||
class="mt-2 sm:mt-0 text-white text-xs sm:text-md sm:font-medium inline-block"
|
||||
>
|
||||
Mkt Cap
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col sm:flex-row items-center ml-3 sm:ml-0 w-1/2 justify-center"
|
||||
>
|
||||
<div
|
||||
class="h-full transform -translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<div
|
||||
class="w-3 h-3 bg-[#3B82F6] border-4 box-content border-[#27272A] rounded-full transform sm:-translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<span
|
||||
class="mt-2 sm:mt-0 text-white text-xs sm:text-md inline-block"
|
||||
>
|
||||
Debt
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mt-3.5 sm:mt-0 flex flex-col sm:flex-row items-center sm:items-center ml-3 sm:ml-0 w-1/2 justify-center"
|
||||
>
|
||||
<div
|
||||
class="h-full transform -translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<div
|
||||
class="w-3 h-3 bg-[#fff] border-4 box-content border-[#27272A] rounded-full transform sm:-translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<span
|
||||
class="mt-2 sm:mt-0 text-white text-center sm:text-start text-xs sm:text-md inline-block"
|
||||
>
|
||||
Cash Equivalents
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex justify-center items-center h-80">
|
||||
<div class="relative">
|
||||
<label
|
||||
class="bg-secondary 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}
|
||||
</main>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.app {
|
||||
height: 300px;
|
||||
max-width: 100%; /* Ensure chart width doesn't exceed the container */
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.app {
|
||||
height: 230px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -1,356 +0,0 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
fomcImpactComponent,
|
||||
displayCompanyName,
|
||||
stockTicker,
|
||||
assetType,
|
||||
etfTicker,
|
||||
getCache,
|
||||
setCache,
|
||||
} from "$lib/store";
|
||||
import InfoModal from "$lib/components/InfoModal.svelte";
|
||||
import { Chart } from "svelte-echarts";
|
||||
import { init, use } from "echarts/core";
|
||||
import { LineChart } from "echarts/charts";
|
||||
import {
|
||||
GridComponent,
|
||||
MarkLineComponent,
|
||||
TooltipComponent,
|
||||
} from "echarts/components";
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
|
||||
use([
|
||||
LineChart,
|
||||
GridComponent,
|
||||
MarkLineComponent,
|
||||
TooltipComponent,
|
||||
CanvasRenderer,
|
||||
]);
|
||||
|
||||
let isLoaded = false;
|
||||
let rawData = [];
|
||||
let tableList = [];
|
||||
let optionsData;
|
||||
let showFullStats = false;
|
||||
|
||||
function getPlotOptions() {
|
||||
const xAxisData = rawData?.history.map((item) => item.date);
|
||||
|
||||
// Extract yAxis data from history (close prices)
|
||||
const seriesData = rawData?.history?.map((item) => [item.date, item.close]);
|
||||
|
||||
// Create markLine data for FOMC dates based on xAxis index
|
||||
|
||||
const markLineData = rawData.fomcData
|
||||
?.map((item) => {
|
||||
const index = xAxisData?.indexOf(item?.date);
|
||||
if (index !== -1) {
|
||||
return { xAxis: index }; // Mark the index in xAxis where FOMC date exists
|
||||
}
|
||||
return null;
|
||||
})
|
||||
?.filter((item) => item !== null); // Filter out null values in case FOMC date is not in history
|
||||
|
||||
tableList = rawData.fomcData?.sort(
|
||||
(a, b) => new Date(b?.date) - new Date(a?.date),
|
||||
);
|
||||
|
||||
const option = {
|
||||
silent: true,
|
||||
animation: false,
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
hideDelay: 100,
|
||||
},
|
||||
grid: {
|
||||
left: "3%",
|
||||
right: "3%",
|
||||
bottom: "2%",
|
||||
top: "5%",
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
boundaryGap: false,
|
||||
axisLabel: {
|
||||
color: "#fff",
|
||||
},
|
||||
data: xAxisData,
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "Stock Price",
|
||||
type: "line",
|
||||
symbol: "none",
|
||||
lineStyle: {
|
||||
color: "#fff",
|
||||
},
|
||||
markLine: {
|
||||
symbol: ["none", "none"],
|
||||
label: { show: false },
|
||||
lineStyle: {
|
||||
color: "orange", // Set the color to orange
|
||||
},
|
||||
data: markLineData, // Use dynamically created markLine data for FOMC dates
|
||||
},
|
||||
data: seriesData, // Populate series with [date, close] data
|
||||
},
|
||||
],
|
||||
};
|
||||
return option;
|
||||
}
|
||||
|
||||
const getFOMCImpact = async (ticker: string) => {
|
||||
// Get cached data for the specific tickerID
|
||||
const cachedData = getCache(ticker, "getFOMCImpact");
|
||||
if (cachedData) {
|
||||
rawData = cachedData;
|
||||
} else {
|
||||
const postData = { ticker: ticker, path: "fomc-impact" };
|
||||
const response = await fetch("/api/ticker-data", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(postData),
|
||||
});
|
||||
|
||||
rawData = await response?.json();
|
||||
// Cache the data for this specific tickerID with a specific name 'getFOMCImpact'
|
||||
setCache(ticker, rawData, "getFOMCImpact");
|
||||
}
|
||||
|
||||
if (Object?.keys(rawData)?.length !== 0) {
|
||||
$fomcImpactComponent = true;
|
||||
} else {
|
||||
$fomcImpactComponent = false;
|
||||
}
|
||||
};
|
||||
|
||||
$: {
|
||||
if (
|
||||
$assetType === "stock"
|
||||
? $stockTicker
|
||||
: $etfTicker && typeof window !== "undefined"
|
||||
) {
|
||||
isLoaded = false;
|
||||
showFullStats = false;
|
||||
tableList = [];
|
||||
const asyncFunctions = [
|
||||
getFOMCImpact($assetType === "stock" ? $stockTicker : $etfTicker),
|
||||
];
|
||||
Promise.all(asyncFunctions)
|
||||
.then(() => {
|
||||
if (Object?.keys(rawData)?.length !== 0) {
|
||||
optionsData = getPlotOptions();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("An error occurred:", error);
|
||||
});
|
||||
isLoaded = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="overflow-hidden text-white h-full">
|
||||
<main class="overflow-hidden">
|
||||
<div class="flex flex-row items-center">
|
||||
<label
|
||||
for="fomcInfo"
|
||||
class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-3xl font-bold"
|
||||
>
|
||||
FOMC Impact
|
||||
</label>
|
||||
<InfoModal
|
||||
title={"Federal Open Market Committee Meeting"}
|
||||
content={"FOMC meetings influence stock prices by setting interest rates and signaling monetary policy, affecting investor sentiment and market liquidity."}
|
||||
id={"fomcInfo"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if isLoaded}
|
||||
{#if Object?.keys(rawData)?.length !== 0}
|
||||
<div class="mt-2 pb-8 sm:pb-2 rounded-md bg-default sm:bg-default">
|
||||
<div class="w-full flex flex-col items-start">
|
||||
<div class="text-white text-[1rem] mt-1 sm:mt-3 mb-1 w-full">
|
||||
Examine how sensitive {$displayCompanyName}'s stock price is to
|
||||
the decisions made during FOMC meetings. The vertical line marks
|
||||
the interest rate decision.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="app w-full h-[300px]">
|
||||
<Chart {init} options={optionsData} class="chart" />
|
||||
</div>
|
||||
|
||||
<h2
|
||||
class="mt-10 mr-1 flex flex-row items-center text-white text-2xl font-bold mb-3"
|
||||
>
|
||||
Latest FED Interest Rate
|
||||
</h2>
|
||||
|
||||
<div class="w-full text-white text-[1rem] mt-6">
|
||||
The latest list of previous, forecasted, and actual interest rate
|
||||
decisions made by the FED.
|
||||
<br />
|
||||
The "% Price Change" column reflects the performance for each interval
|
||||
leading up to the next FOMC meeting.
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex justify-start items-center w-full m-auto mt-6 overflow-x-scroll"
|
||||
>
|
||||
<table class="table table-sm table-compact w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-[#27272A]">
|
||||
<th
|
||||
class="text-white font-semibold text-sm sm:text-[1rem] text-start bg-default"
|
||||
>Date</th
|
||||
>
|
||||
<th
|
||||
class="text-white font-semibold text-sm sm:text-[1rem] text-end bg-default"
|
||||
>Previous</th
|
||||
>
|
||||
<th
|
||||
class="text-white font-semibold text-sm sm:text-[1rem] text-end bg-default"
|
||||
>Forecast</th
|
||||
>
|
||||
<th
|
||||
class="text-white font-semibold text-sm sm:text-[1rem] text-end bg-default"
|
||||
>Actual</th
|
||||
>
|
||||
<th
|
||||
class="text-white font-semibold text-sm sm:text-[1rem] text-end bg-default"
|
||||
>% Price Change</th
|
||||
>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each showFullStats ? tableList?.slice(0, 10) : tableList?.slice(0, 3) as item, index}
|
||||
<tr
|
||||
class="border-y border-gray-800 odd:bg-odd {index === 2 &&
|
||||
!showFullStats &&
|
||||
tableList?.length > 3
|
||||
? 'opacity-[0.5]'
|
||||
: ''} sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] bg-default border-b-[#09090B]"
|
||||
>
|
||||
<td
|
||||
class="text-white font-medium text-sm sm:text-[1rem] whitespace-nowrap"
|
||||
>
|
||||
{new Date(item?.date ?? null)?.toLocaleString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
})}
|
||||
</td>
|
||||
|
||||
<td
|
||||
class="text-white text-end font-medium text-sm sm:text-[1rem] whitespace-nowrap"
|
||||
>
|
||||
{item?.previous}%
|
||||
</td>
|
||||
|
||||
<td
|
||||
class="text-white text-end font-medium text-sm sm:text-[1rem] whitespace-nowrap"
|
||||
>
|
||||
{item?.estimate}%
|
||||
</td>
|
||||
|
||||
<td
|
||||
class="text-white text-end font-medium text-sm sm:text-[1rem] whitespace-nowrap"
|
||||
>
|
||||
{item?.actual}%
|
||||
</td>
|
||||
|
||||
<td
|
||||
class="text-white font-normal text-end text-sm sm:text-[1rem] whitespace-nowrap"
|
||||
>
|
||||
{#if item?.changePercentage >= 0}
|
||||
<span class="text-[#00FC50]"
|
||||
>+{item?.changePercentage?.toFixed(2)}%</span
|
||||
>
|
||||
{:else}
|
||||
<span class="text-[#FF2F1F]"
|
||||
>{item?.changePercentage?.toFixed(2)}%
|
||||
</span>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<label
|
||||
on:click={() => (showFullStats = !showFullStats)}
|
||||
class="cursor-pointer flex justify-center items-center mt-5"
|
||||
>
|
||||
<svg
|
||||
class="w-10 h-10 transform {showFullStats ? 'rotate-180' : ''} "
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
fill="#2A323C"
|
||||
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2zm0 13.5L7.5 11l1.42-1.41L12 12.67l3.08-3.08L16.5 11L12 15.5z"
|
||||
/></svg
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
{:else}
|
||||
<h2
|
||||
class="mt-10 mb-5 flex justify-center items-center text-3xl font-bold text-slate-700 m-auto"
|
||||
>
|
||||
No data available
|
||||
<svg
|
||||
class="w-10 sm:w-12 inline-block"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
fill="#334155"
|
||||
d="M18.68 12.32a4.49 4.49 0 0 0-6.36.01a4.49 4.49 0 0 0 0 6.36a4.508 4.508 0 0 0 5.57.63L21 22.39L22.39 21l-3.09-3.11c1.13-1.77.87-4.09-.62-5.57m-1.41 4.95c-.98.98-2.56.97-3.54 0c-.97-.98-.97-2.56.01-3.54c.97-.97 2.55-.97 3.53 0c.97.98.97 2.56 0 3.54M10.9 20.1a6.527 6.527 0 0 1-1.48-2.32C6.27 17.25 4 15.76 4 14v3c0 2.21 3.58 4 8 4c-.4-.26-.77-.56-1.1-.9M4 9v3c0 1.68 2.07 3.12 5 3.7v-.2c0-.93.2-1.85.58-2.69C6.34 12.3 4 10.79 4 9m8-6C7.58 3 4 4.79 4 7c0 2 3 3.68 6.85 4h.05c1.2-1.26 2.86-2 4.6-2c.91 0 1.81.19 2.64.56A3.215 3.215 0 0 0 20 7c0-2.21-3.58-4-8-4Z"
|
||||
/></svg
|
||||
>
|
||||
</h2>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex justify-center items-center h-80">
|
||||
<div class="relative">
|
||||
<label
|
||||
class="bg-secondary 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"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</main>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.app {
|
||||
height: 300px;
|
||||
max-width: 100%; /* Ensure chart width doesn't exceed the container */
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.app {
|
||||
height: 230px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -172,7 +172,6 @@
|
||||
const ticker = $assetType === "stock" ? $stockTicker : $etfTicker;
|
||||
if (ticker) {
|
||||
isLoaded = false;
|
||||
console.log(rawData);
|
||||
if (rawData?.length > 0) {
|
||||
weightedFTD = (
|
||||
(rawData?.slice(-1)?.at(0)?.failToDeliver /
|
||||
|
||||
@ -1,266 +0,0 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
governmentContractComponent,
|
||||
displayCompanyName,
|
||||
stockTicker,
|
||||
screenWidth,
|
||||
getCache,
|
||||
setCache,
|
||||
} from "$lib/store";
|
||||
import InfoModal from "$lib/components/InfoModal.svelte";
|
||||
import { Chart } from "svelte-echarts";
|
||||
import { abbreviateNumber } from "$lib/utils";
|
||||
|
||||
import { init, use } from "echarts/core";
|
||||
import { BarChart } from "echarts/charts";
|
||||
import { GridComponent } from "echarts/components";
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
use([BarChart, GridComponent, CanvasRenderer]);
|
||||
|
||||
let isLoaded = false;
|
||||
let rawData = [];
|
||||
let optionsData;
|
||||
let avgNumberOfContracts = 0;
|
||||
let displayMaxContracts = 0;
|
||||
let displayYear = "n/a";
|
||||
let totalAmount;
|
||||
let totalContract;
|
||||
|
||||
function getPlotOptions() {
|
||||
let dates = [];
|
||||
let amountList = [];
|
||||
let numList = [];
|
||||
|
||||
rawData?.forEach((item) => {
|
||||
const fiscalYear = "FY" + item?.year?.slice(2);
|
||||
dates?.push(fiscalYear);
|
||||
amountList?.push(item?.amount);
|
||||
numList?.push(item?.numOfContracts);
|
||||
});
|
||||
|
||||
totalContract = rawData?.reduce(
|
||||
(sum, item) => sum + item?.numOfContracts,
|
||||
0,
|
||||
);
|
||||
totalAmount = rawData?.reduce((sum, item) => sum + item?.amount, 0);
|
||||
|
||||
avgNumberOfContracts = Math.floor(totalContract / rawData?.length);
|
||||
const { year: yearWithMaxContracts, numOfContracts: maxContracts } =
|
||||
rawData?.reduce(
|
||||
(max, contract) =>
|
||||
contract?.numOfContracts > max?.numOfContracts ? contract : max,
|
||||
rawData?.at(0),
|
||||
);
|
||||
displayYear = yearWithMaxContracts;
|
||||
displayMaxContracts = maxContracts;
|
||||
|
||||
const option = {
|
||||
silent: true,
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
hideDelay: 100, // Set the delay in milliseconds
|
||||
},
|
||||
animation: false,
|
||||
grid: {
|
||||
left: "2%",
|
||||
right: $screenWidth < 640 ? "0%" : "2%",
|
||||
bottom: "0%",
|
||||
top: "10%",
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
data: dates,
|
||||
type: "category",
|
||||
axisLabel: {
|
||||
color: "#fff",
|
||||
},
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
splitLine: {
|
||||
show: false, // Disable x-axis grid lines
|
||||
},
|
||||
axisLabel: {
|
||||
show: false, // Hide y-axis labels
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "value",
|
||||
splitLine: {
|
||||
show: false, // Disable x-axis grid lines
|
||||
},
|
||||
axisLabel: {
|
||||
show: false, // Hide y-axis labels
|
||||
},
|
||||
position: "right",
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "# of Contracts",
|
||||
data: numList,
|
||||
type: "line",
|
||||
yAxisIndex: 1,
|
||||
itemStyle: {
|
||||
color: "#fff", // Change bar color to white
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Amount",
|
||||
data: amountList,
|
||||
type: "bar",
|
||||
itemStyle: {
|
||||
color: "#fff", // Change bar color to orange
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
const getGovernmentContract = async (ticker) => {
|
||||
const cachedData = getCache(ticker, "getGovernmentContract");
|
||||
if (cachedData) {
|
||||
rawData = cachedData;
|
||||
} else {
|
||||
const postData = { ticker: ticker, path: "government-contract" };
|
||||
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, "getGovernmentContract");
|
||||
}
|
||||
|
||||
governmentContractComponent.set(rawData?.length !== 0);
|
||||
};
|
||||
|
||||
$: {
|
||||
if ($stockTicker && typeof window !== "undefined") {
|
||||
isLoaded = false;
|
||||
const ticker = $stockTicker;
|
||||
const asyncFunctions = [getGovernmentContract(ticker)];
|
||||
|
||||
Promise.all(asyncFunctions)
|
||||
.then(() => {
|
||||
if (rawData?.length !== 0) {
|
||||
optionsData = getPlotOptions();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("An error occurred:", error);
|
||||
});
|
||||
isLoaded = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="overflow-hidden text-white h-full pb-8">
|
||||
<main class="overflow-hidden">
|
||||
<div class="flex flex-row items-center">
|
||||
<label
|
||||
for="governmentContractInfo"
|
||||
class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-3xl font-bold"
|
||||
>
|
||||
US Government Contract
|
||||
</label>
|
||||
<InfoModal
|
||||
title={"Government Contract"}
|
||||
content={"Government contracts are agreements between the local government and companies for goods or services. They can be substantial revenue sources for companies, particularly in sectors like defense, technology, and infrastructure. Winning contracts can enhance a company's stability and credibility, but it often involves competitive bidding and compliance with strict regulations."}
|
||||
id={"governmentContractInfo"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if isLoaded}
|
||||
{#if rawData?.length !== 0}
|
||||
<div class="w-full flex flex-col items-start">
|
||||
<div class="text-white text-sm sm:text-[1rem] mt-2 mb-2 w-full">
|
||||
Since 2015, {$displayCompanyName} has secured a total of {totalContract}
|
||||
government contracts, amassing {abbreviateNumber(totalAmount, true)}
|
||||
in revenue. The company has averaged {avgNumberOfContracts} contracts
|
||||
per year, with a peak of {displayMaxContracts} contracts in {displayYear}.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pb-2 rounded-md bg-default">
|
||||
<div class="app w-full h-[300px] mt-5">
|
||||
<Chart {init} options={optionsData} class="chart" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-row items-center justify-between mx-auto mt-5 w-full sm:w-11/12"
|
||||
>
|
||||
<div
|
||||
class="mt-3.5 sm:mt-0 flex flex-col sm:flex-row items-center ml-3 sm:ml-0 w-1/2 justify-center"
|
||||
>
|
||||
<div
|
||||
class="h-full transform -translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<div
|
||||
class="w-3 h-3 bg-[#fff] border-4 box-content border-[#27272A] rounded-full transform sm:-translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<span
|
||||
class="mt-2 sm:mt-0 text-white text-center sm:text-start text-xs sm:text-md inline-block"
|
||||
>
|
||||
# of Contracts
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col sm:flex-row items-center ml-3 sm:ml-0 w-1/2 justify-center"
|
||||
>
|
||||
<div
|
||||
class="h-full transform -translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<div
|
||||
class="w-3 h-3 bg-[#FFAD24] border-4 box-content border-[#27272A] rounded-full transform sm:-translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<span
|
||||
class="mt-2 sm:mt-0 text-white text-xs sm:text-md sm:font-medium inline-block"
|
||||
>
|
||||
Amount
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex justify-center items-center h-80">
|
||||
<div class="relative">
|
||||
<label
|
||||
class="bg-secondary 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}
|
||||
</main>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.app {
|
||||
height: 300px;
|
||||
max-width: 100%; /* Ensure chart width doesn't exceed the container */
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.app {
|
||||
height: 210px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -1,258 +0,0 @@
|
||||
<script lang 'ts'></script>
|
||||
|
||||
<section class="overflow-hidden text-white h-full pb-8">
|
||||
<main class="overflow-hidden">
|
||||
<div class="flex flex-row items-center">
|
||||
<label
|
||||
for="marketMakerInfo"
|
||||
class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-3xl font-bold"
|
||||
>
|
||||
Market Maker Activity
|
||||
</label>
|
||||
<InfoModal
|
||||
title={"Market Maker Activity"}
|
||||
content={"Market makers provide liquidity by quoting buy and sell prices, stabilizing markets. For retail traders, understanding this helps navigate tight spreads, execute trades effectively, and gauge market sentiment."}
|
||||
id={"marketMakerInfo"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if isLoaded}
|
||||
{#if historyData?.length !== 0}
|
||||
<div class="w-full flex flex-col items-start">
|
||||
<div class="text-white text-[1rem] mt-2 mb-2 w-full">
|
||||
Over the past year, {$displayCompanyName} has seen a weekly average of
|
||||
<span class="font-semibold">{abbreviateNumber(avgTradeCount)}</span>
|
||||
trades, involving an average of
|
||||
<span class="font-semibold"
|
||||
>{abbreviateNumber(avgShareQuantity)}</span
|
||||
>
|
||||
shares bought and sold. This activity sums up to an average total
|
||||
notional value of
|
||||
<span class="font-semibold"
|
||||
>{abbreviateNumber(avgNotionalSum, true)}</span
|
||||
>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pb-2 rounded-md bg-default">
|
||||
<div class="app w-full h-[300px] mt-5">
|
||||
<Chart {init} options={optionsData} class="chart" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-row items-center justify-between mx-auto mt-5 w-full sm:w-11/12"
|
||||
>
|
||||
<div
|
||||
class="mt-3.5 sm:mt-0 flex flex-col sm:flex-row items-center ml-3 sm:ml-0 w-1/2 justify-center"
|
||||
>
|
||||
<div
|
||||
class="h-full transform -translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<div
|
||||
class="w-3 h-3 bg-[#fff] border-4 box-content border-[#27272A] rounded-full transform sm:-translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<span
|
||||
class="mt-2 sm:mt-0 text-white text-center sm:text-start text-xs sm:text-md inline-block"
|
||||
>
|
||||
Notional Sum
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col sm:flex-row items-center ml-3 sm:ml-0 w-1/2 justify-center"
|
||||
>
|
||||
<div
|
||||
class="h-full transform -translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<div
|
||||
class="w-3 h-3 bg-[#3B82F6] border-4 box-content border-[#27272A] rounded-full transform sm:-translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<span
|
||||
class="mt-2 sm:mt-0 text-white text-xs sm:text-md sm:font-medium inline-block"
|
||||
>
|
||||
Share Quantity
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2
|
||||
class="mt-10 mr-1 flex flex-row items-center text-white text-xl sm:text-2xl font-bold mb-3"
|
||||
>
|
||||
Latest Information
|
||||
</h2>
|
||||
|
||||
<div class="flex justify-start items-center w-full m-auto">
|
||||
<table class="w-full" data-test="statistics-table">
|
||||
<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">
|
||||
<span>Date</span>
|
||||
</td>
|
||||
<td
|
||||
class="px-[5px] py-1.5 text-right whitespace-nowrap font-medium xs:px-2.5 xs:py-2"
|
||||
>
|
||||
{formatDateRange(historyData?.slice(-1)?.at(0)?.date)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-y border-gray-800 odd:bg-odd">
|
||||
<td class="px-[5px] py-1.5 xs:px-2.5 xs:py-2">
|
||||
<span>Total Notional Sum</span>
|
||||
</td>
|
||||
<td
|
||||
class="px-[5px] py-1.5 text-right font-medium xs:px-2.5 xs:py-2"
|
||||
>
|
||||
${abbreviateNumber(
|
||||
historyData?.slice(-1)?.at(0)?.totalNotionalSum,
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-y border-gray-800 odd:bg-odd">
|
||||
<td class="px-[5px] py-1.5 xs:px-2.5 xs:py-2">
|
||||
<span>Total Trade Count</span>
|
||||
</td>
|
||||
<td
|
||||
class="px-[5px] py-1.5 text-right font-medium xs:px-2.5 xs:py-2"
|
||||
>
|
||||
{abbreviateNumber(
|
||||
historyData?.slice(-1)?.at(0)?.totalWeeklyTradeCount,
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-y border-gray-800 odd:bg-odd">
|
||||
<td class="px-[5px] py-1.5 xs:px-2.5 xs:py-2">
|
||||
<span>Total Share Quantity</span>
|
||||
</td>
|
||||
<td
|
||||
class="px-[5px] py-1.5 text-right font-medium xs:px-2.5 xs:py-2"
|
||||
>
|
||||
{abbreviateNumber(
|
||||
historyData?.slice(-1)?.at(0)?.totalWeeklyShareQuantity,
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3
|
||||
class="mt-10 mr-1 flex flex-row items-center text-white text-xl sm:text-2xl font-bold mb-3"
|
||||
>
|
||||
Top 10 Market Makers Activity
|
||||
</h3>
|
||||
These market makers represent the highest average trading activity for {$displayCompanyName}
|
||||
over the past 12 months, calculated on a weekly basis.
|
||||
|
||||
<div
|
||||
class="flex justify-start items-center w-full m-auto mt-6 overflow-x-scroll no-scrollbar"
|
||||
>
|
||||
<table class="table table-sm table-compact w-full">
|
||||
<thead>
|
||||
<tr class="">
|
||||
<th
|
||||
class="text-white shadow-md font-semibold text-sm text-start bg-default"
|
||||
>Name</th
|
||||
>
|
||||
<th
|
||||
class="text-white shadow-md font-semibold text-sm text-end bg-default"
|
||||
>Trade Count</th
|
||||
>
|
||||
<th
|
||||
class="text-white shadow-md font-semibold text-sm text-end bg-default"
|
||||
>Share Quantity</th
|
||||
>
|
||||
<th
|
||||
class="text-white shadow-md font-semibold text-sm text-end bg-default"
|
||||
>Notional Sum</th
|
||||
>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each showFullStats ? topMarketMakers?.slice(0, 10) : topMarketMakers?.slice(0, 3) as item, index}
|
||||
<tr
|
||||
class="border-y border-gray-800 odd:bg-odd {index ===
|
||||
2 &&
|
||||
!showFullStats &&
|
||||
topMarketMakers?.length > 3
|
||||
? 'opacity-[0.5]'
|
||||
: ''} sm:hover:bg-[#245073] sm:hover:bg-opacity-[0.2] bg-default border-b-[#09090B]"
|
||||
>
|
||||
<td
|
||||
class="text-white text-sm sm:text-[1rem] font-medium whitespace-nowrap"
|
||||
>
|
||||
{item?.name?.length > charNumber
|
||||
? formatString(item?.name?.slice(0, charNumber)) + "..."
|
||||
: formatString(item?.name)}
|
||||
</td>
|
||||
|
||||
<td
|
||||
class="text-white text-end text-sm sm:text-[1rem] font-medium whitespace-nowrap"
|
||||
>
|
||||
{abbreviateNumber(Math.floor(item?.avgWeeklyTradeCount))}
|
||||
</td>
|
||||
|
||||
<td
|
||||
class="text-white text-end text-sm sm:text-[1rem] font-medium whitespace-nowrap"
|
||||
>
|
||||
{abbreviateNumber(Math.floor(item?.avgWeeklyShareQuantity))}
|
||||
</td>
|
||||
|
||||
<td
|
||||
class="text-white text-end text-sm sm:text-[1rem] font-medium whitespace-nowrap"
|
||||
>
|
||||
{abbreviateNumber(item?.avgNotionalSum, true)}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<label
|
||||
on:click={() => (showFullStats = !showFullStats)}
|
||||
class="cursor-pointer flex justify-center items-center mt-5"
|
||||
>
|
||||
<svg
|
||||
class="w-10 h-10 transform {showFullStats ? 'rotate-180' : ''} "
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
fill="#2A323C"
|
||||
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2zm0 13.5L7.5 11l1.42-1.41L12 12.67l3.08-3.08L16.5 11L12 15.5z"
|
||||
/></svg
|
||||
>
|
||||
</label>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex justify-center items-center h-80">
|
||||
<div class="relative">
|
||||
<label
|
||||
class="bg-secondary 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"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</main>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.app {
|
||||
height: 300px;
|
||||
max-width: 100%; /* Ensure chart width doesn't exceed the container */
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.app {
|
||||
height: 210px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -1,135 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { isOpen, screenWidth } from "$lib/store";
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row items-center">
|
||||
<label for={$screenWidth > 640 ? "marketHour" : ""} class="cursor-pointer">
|
||||
{#if $isOpen}
|
||||
<div
|
||||
class="sm:border sm:border-gray-800 b sm:hover:bg-[#333333] sm:ease-in-out sm:duration-100 rounded-2xl pl-3 pr-3 pt-1 pb-1 flex flex-row items-center"
|
||||
>
|
||||
<span class="text-xs py-0.5 text-slate-200 font-semibold">
|
||||
Market Open
|
||||
</span>
|
||||
<span class="relative flex h-1.5 w-1.5 sm:h-2 sm:w-2 ml-2">
|
||||
<span
|
||||
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#00FC50] opacity-75"
|
||||
></span>
|
||||
<span
|
||||
class="relative inline-flex rounded-full h-1.5 w-1.5 sm:h-2 sm:w-2 bg-[#00FC50]"
|
||||
></span>
|
||||
</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="sm:border sm:border-gray-800 sm:hover:bg-[#333333] sm:ease-in-out sm:duration-100 rounded-2xl pl-3 pr-3 pt-1 pb-1 flex flex-row items-center"
|
||||
>
|
||||
<span class="text-xs py-0.5 text-slate-200 font-semibold">
|
||||
Market Closed
|
||||
</span>
|
||||
<span class="relative flex h-1.5 w-1.5 sm:h-2 sm:w-2 ml-2">
|
||||
<span
|
||||
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#FF2F1F] opacity-75"
|
||||
></span>
|
||||
<span
|
||||
class="relative inline-flex rounded-full h-1.5 w-1.5 sm:h-2 sm:w-2 bg-[#FF2F1F]"
|
||||
></span>
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Put this part before </body> tag -->
|
||||
<input type="checkbox" id="marketHour" class="modal-toggle" />
|
||||
|
||||
<label
|
||||
for="marketHour"
|
||||
class="modal modal-bottom sm:modal-middle cursor-pointer"
|
||||
>
|
||||
<label for="marketHour" class="cursor-pointer modal-backdrop"></label>
|
||||
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label
|
||||
class="modal-box w-full relative bg-primary border border-gray-800 h-auto"
|
||||
>
|
||||
<label
|
||||
for="marketHour"
|
||||
class="cursor-pointer absolute right-5 top-2 bg-primary text-2xl text-white"
|
||||
>
|
||||
✕
|
||||
</label>
|
||||
|
||||
<h3 class="text-2xl font-bold text-white">Opening Hours</h3>
|
||||
<p class="py-4 text-gray-200 bg-primary w-full">
|
||||
The New York Stock Exchange, one of the largest stock exchanges in the
|
||||
world, operates on a regular trading schedule. Its trading hours are from <span
|
||||
class="text-white font-semibold underline">9:30</span
|
||||
>
|
||||
ET in the morning to
|
||||
<span class="text-white font-semibold underline">16:00</span>
|
||||
ET in the afternoon. Monday through Friday.
|
||||
<br />
|
||||
<br />
|
||||
The market is closed on the following holidays:
|
||||
</p>
|
||||
|
||||
<table
|
||||
class="table table-sm table-compact bg-primary w-full mt-5 mb-10 text-white"
|
||||
>
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr class="border-b border-slate-700 odd:bg-odd">
|
||||
<th class="bg-primary text-white text-sm font-semibold">
|
||||
Exchange holidays
|
||||
</th>
|
||||
<th class="bg-primary text-white text-sm font-semibold"> Date </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- row 1 -->
|
||||
<tr class="border-b border-slate-700 odd:bg-odd">
|
||||
<td class="font-semibold"> New Years Day</td>
|
||||
<td class="">01.01.2024</td>
|
||||
</tr>
|
||||
<!-- row 2 -->
|
||||
<tr class="border-b border-slate-700 odd:bg-odd">
|
||||
<td class="font-semibold">Martin Luther King, Jr. Day</td>
|
||||
<td class="">15.01.2024</td>
|
||||
</tr>
|
||||
<tr class="border-b border-slate-700 odd:bg-odd">
|
||||
<td class="font-semibold">Washington's Birthday</td>
|
||||
<td class="">19.02.2024</td>
|
||||
</tr>
|
||||
<tr class="border-b border-slate-700 odd:bg-odd">
|
||||
<td class="font-semibold"> Good Friday </td>
|
||||
<td class="bg-primary">29.03.2024</td>
|
||||
</tr>
|
||||
<tr class="border-b border-slate-700 odd:bg-odd">
|
||||
<td class="font-semibold">Memorial Day</td>
|
||||
<td class="">27.05.2024</td>
|
||||
</tr>
|
||||
<tr class="border-b border-slate-700 odd:bg-odd">
|
||||
<td class="font-semibold">Juneteenth National Independence Day</td>
|
||||
<td class="">19.06.2024</td>
|
||||
</tr>
|
||||
<tr class="border-b border-slate-700 odd:bg-odd">
|
||||
<td class="font-semibold">Independence Day</td>
|
||||
<td class="">04.07.2024</td>
|
||||
</tr>
|
||||
<tr class="border-b border-slate-700 odd:bg-odd">
|
||||
<td class="font-semibold">Labor Day</td>
|
||||
<td class="">02.09.2024</td>
|
||||
</tr>
|
||||
<tr class="border-b border-slate-700 odd:bg-odd">
|
||||
<td class="font-semibold">Thanksgiving Day</td>
|
||||
<td class="">28.11.2024</td>
|
||||
</tr>
|
||||
<tr class="odd:bg-odd">
|
||||
<td class="font-semibold">Christmas</td>
|
||||
<td class="">25.12.2024</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</label>
|
||||
</label>
|
||||
@ -1,378 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
import toast from "svelte-french-toast";
|
||||
import { commentAdded } from "$lib/store";
|
||||
import { pb } from "$lib/pocketbase";
|
||||
|
||||
export let data;
|
||||
export let postId = "";
|
||||
|
||||
let post = data?.getOnePost;
|
||||
|
||||
let imageId = "image-input";
|
||||
|
||||
let expandField = false;
|
||||
|
||||
//let characterCount = 0;
|
||||
|
||||
let inputValue = "";
|
||||
let imageInput = "";
|
||||
let imageComment;
|
||||
|
||||
const showPreview = (event) => {
|
||||
const target = event.target;
|
||||
const files = target.files;
|
||||
if (files.length > 0) {
|
||||
const src = URL.createObjectURL(files[0]);
|
||||
const preview = document.getElementById("image-preview");
|
||||
preview.src = src;
|
||||
}
|
||||
};
|
||||
|
||||
const createComment = async (event) => {
|
||||
event.preventDefault(); // prevent the default form submission behavior
|
||||
let output;
|
||||
let newComment;
|
||||
const userId = data?.user?.id;
|
||||
|
||||
if (inputValue.length !== 0) {
|
||||
const postData = {
|
||||
comment: inputValue,
|
||||
post: postId,
|
||||
image: imageInput.length !== 0 ? imageInput[0] : "",
|
||||
user: userId,
|
||||
};
|
||||
|
||||
/*
|
||||
const response = await fetch(fastifyURL+'/create-comment', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(postData),
|
||||
}); // make a POST request to the server with the FormData object
|
||||
|
||||
output = await response.json()
|
||||
}
|
||||
*/
|
||||
//Create comment in User Browser because i need to learn how to transfer images.
|
||||
try {
|
||||
newComment = await pb.collection("comments").create(postData);
|
||||
|
||||
newComment = await pb.collection("comments").getOne(newComment?.id, {
|
||||
expand: `user`,
|
||||
});
|
||||
|
||||
//User always upvotes their post in the intial state
|
||||
await pb.collection("comments").update(newComment.id, {
|
||||
"vote+": 1,
|
||||
});
|
||||
|
||||
const opPost = await pb.collection("posts").getOne(postId);
|
||||
//create new record for notifications collections
|
||||
if (userId !== opPost?.user) {
|
||||
let formDataNotifications = new FormData();
|
||||
formDataNotifications.append("opUser", opPost?.user);
|
||||
formDataNotifications.append("user", userId);
|
||||
formDataNotifications.append("post", postId);
|
||||
formDataNotifications.append("comment", newComment?.id);
|
||||
formDataNotifications.append("notifyType", "comment");
|
||||
|
||||
await pb.collection("notifications").create(formDataNotifications);
|
||||
}
|
||||
output = "success";
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
output = "failure";
|
||||
}
|
||||
|
||||
if (output === "success") {
|
||||
toast.success("Commented successfully", {
|
||||
style: "border-radius: 200px; background: #2A2E39; color: #fff;",
|
||||
});
|
||||
|
||||
$commentAdded = newComment;
|
||||
|
||||
//console.log("comment added: ", $commentAdded)
|
||||
inputValue = "";
|
||||
imageInput = "";
|
||||
|
||||
handleCancel();
|
||||
} else {
|
||||
toast.error("Something went wrong. Please try again...", {
|
||||
style: "border-radius: 200px; background: #2A2E39; color: #fff;",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function handleInput(event) {
|
||||
inputValue = event.target.value;
|
||||
|
||||
const textarea = event.target;
|
||||
textarea.style.height = "auto";
|
||||
textarea.style.height = Math.min(textarea.scrollHeight, 140) + "px";
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
if (editor) {
|
||||
editor.innerHTML = "";
|
||||
}
|
||||
inputValue = "";
|
||||
imageInput = "";
|
||||
const closePopup = document.getElementById("mobileTextEditorModal");
|
||||
closePopup?.dispatchEvent(new MouseEvent("click"));
|
||||
|
||||
const textarea = document.querySelector("textarea");
|
||||
textarea.style.height = "36px"; // 48px are h-12 in tailwindcss
|
||||
}
|
||||
|
||||
function handleImageInput(event) {
|
||||
imageInput = event.target.files;
|
||||
|
||||
expandField = true;
|
||||
}
|
||||
|
||||
const toolbarOptions = {
|
||||
container: [
|
||||
["bold", "italic", "underline", "strike"],
|
||||
[{ list: "ordered" }, { list: "bullet" }],
|
||||
["link"],
|
||||
[{ header: [1, 2, 3, 4, 5, 6, false] }],
|
||||
],
|
||||
};
|
||||
|
||||
let quill;
|
||||
let editor;
|
||||
const regex = /^(\s*<p><br><\/p>\s*)*$/;
|
||||
|
||||
let loadingEditor = false;
|
||||
|
||||
onMount(async () => {
|
||||
const { default: Quill } = await import("quill");
|
||||
|
||||
quill = new Quill(".quill-editor", {
|
||||
modules: {
|
||||
toolbar: toolbarOptions,
|
||||
},
|
||||
theme: "snow",
|
||||
placeholder: "Leave a comment...",
|
||||
keepFocus: true,
|
||||
});
|
||||
|
||||
quill.on("text-change", function () {
|
||||
editor = quill.root;
|
||||
// Add text-4xl class to all h1 elements
|
||||
editor.querySelectorAll("h1").forEach((h1) => {
|
||||
h1.classList.add("text-4xl");
|
||||
});
|
||||
editor.querySelectorAll("h2").forEach((h2) => {
|
||||
h2.classList.add("text-3xl");
|
||||
});
|
||||
editor.querySelectorAll("h3").forEach((h3) => {
|
||||
h3.classList.add("text-xl");
|
||||
});
|
||||
editor.querySelectorAll("h4").forEach((h4) => {
|
||||
h4.classList.add("text-md");
|
||||
});
|
||||
editor.querySelectorAll("h5").forEach((h5) => {
|
||||
h5.classList.add("text-sm");
|
||||
});
|
||||
editor.querySelectorAll("h6").forEach((h6) => {
|
||||
h6.classList.add("text-xs");
|
||||
});
|
||||
|
||||
editor.querySelectorAll("a").forEach((a) => {
|
||||
a.classList.add("text-blue-400", "hover:text-white", "underline");
|
||||
});
|
||||
|
||||
editor.querySelectorAll("ol").forEach((ol) => {
|
||||
ol.classList.add("list-decimal", "ml-10", "text-sm");
|
||||
});
|
||||
|
||||
editor.querySelectorAll("ul").forEach((ul) => {
|
||||
ul.classList.add("list-disc", "ml-10", "text-sm");
|
||||
});
|
||||
|
||||
/*
|
||||
editor.querySelectorAll('pre').forEach((pre) => {
|
||||
pre.classList.add('bg-[#23241F]', 'w-auto','h-auto','max-w-6xl','breaks-all');
|
||||
});
|
||||
*/
|
||||
|
||||
const contents = editor.innerHTML;
|
||||
inputValue = contents;
|
||||
|
||||
console.log(inputValue);
|
||||
|
||||
if (regex?.test(inputValue)) {
|
||||
editor.innerHTML = "";
|
||||
inputValue = "";
|
||||
}
|
||||
|
||||
//console.log(inputValue)
|
||||
|
||||
// Force Svelte to update the DOM
|
||||
$: {
|
||||
}
|
||||
});
|
||||
|
||||
loadingEditor = true;
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (quill) {
|
||||
quill.off("text-change");
|
||||
quill = null;
|
||||
}
|
||||
});
|
||||
|
||||
1;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0"
|
||||
/>
|
||||
</svelte:head>
|
||||
|
||||
<div class="drawer drawer-end overflow-hidden" style="z-index: 9999;">
|
||||
<input id="mobileTextEditorModal" type="checkbox" class="drawer-toggle" />
|
||||
<div class="drawer-side overflow-hidden">
|
||||
<div
|
||||
class="rounded-xl bg-[#000] min-h-screen w-screen pb-20 overflow-y-auto overflow-hidden"
|
||||
>
|
||||
<!--
|
||||
<h1 class="text-white sm:hidden font-medium text-xl mt-10 pl-2">
|
||||
{@html post?.title}
|
||||
</h1>
|
||||
-->
|
||||
|
||||
<h1 class="text-white text-lg pl-7 pt-16">Share your opinion</h1>
|
||||
|
||||
<!--Start Quill Editor-->
|
||||
<div class="{!loadingEditor ? 'hidden' : ''} mt-3 pl-7 pr-7">
|
||||
<div
|
||||
class="quill-editor min-h-[96px] h-[120px] resize-none focus-none ring-none rounded-none bg-secondary text-white"
|
||||
>
|
||||
<select class="ql-header" aria-label="Header" title="Header">
|
||||
<option selected></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
placeholder="Loading editor..."
|
||||
class="{loadingEditor
|
||||
? 'hidden'
|
||||
: ''} min-h-[96px] h-[120px] text-sm italic w-full resize-none focus-none ring-none rounded-none bg-secondary text-white"
|
||||
></textarea>
|
||||
<!--End Quill Editor-->
|
||||
|
||||
<textarea
|
||||
class="hidden"
|
||||
type="text"
|
||||
value={inputValue}
|
||||
on:input={handleInput}
|
||||
/>
|
||||
|
||||
<div class="flex flex-row justify-start mt-4 pb-16 pl-7 pr-7">
|
||||
<div class="relative">
|
||||
<label
|
||||
for={imageId}
|
||||
class="{imageInput.length !== 0
|
||||
? 'hidden'
|
||||
: ''} inline-flex mr-auto items-center py-2.5 px-4 text-xs font-medium text-center text-white rounded-md hover:bg-gray-800 cursor-pointer"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 inline-block"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></g><g id="SVGRepo_iconCarrier">
|
||||
<path
|
||||
opacity="0.4"
|
||||
d="M22.0206 16.8198L18.8906 9.49978C18.3206 8.15978 17.4706 7.39978 16.5006 7.34978C15.5406 7.29978 14.6106 7.96978 13.9006 9.24978L12.0006 12.6598C11.6006 13.3798 11.0306 13.8098 10.4106 13.8598C9.78063 13.9198 9.15063 13.5898 8.64063 12.9398L8.42063 12.6598C7.71063 11.7698 6.83063 11.3398 5.93063 11.4298C5.03063 11.5198 4.26063 12.1398 3.75063 13.1498L2.02063 16.5998C1.40063 17.8498 1.46063 19.2998 2.19063 20.4798C2.92063 21.6598 4.19063 22.3698 5.58063 22.3698H18.3406C19.6806 22.3698 20.9306 21.6998 21.6706 20.5798C22.4306 19.4598 22.5506 18.0498 22.0206 16.8198Z"
|
||||
fill="#A6ADBB"
|
||||
></path>
|
||||
<path
|
||||
d="M6.96984 8.38012C8.83657 8.38012 10.3498 6.86684 10.3498 5.00012C10.3498 3.13339 8.83657 1.62012 6.96984 1.62012C5.10312 1.62012 3.58984 3.13339 3.58984 5.00012C3.58984 6.86684 5.10312 8.38012 6.96984 8.38012Z"
|
||||
fill="#E5E7EB"
|
||||
></path>
|
||||
</g></svg
|
||||
>
|
||||
|
||||
<input
|
||||
class="hidden rounded text-gray-200"
|
||||
type="file"
|
||||
id={imageId}
|
||||
name={imageId}
|
||||
on:input={handleImageInput}
|
||||
on:change={showPreview}
|
||||
/>
|
||||
</label>
|
||||
|
||||
{#if imageInput.length !== 0}
|
||||
<div class="ml-2">
|
||||
<label
|
||||
on:click={() => (imageInput = "")}
|
||||
class="btn btn-xs btn-circle bg-red-600 absolute -top-2 -right-2"
|
||||
>
|
||||
✕
|
||||
</label>
|
||||
<img
|
||||
class="object-contain w-10 h-10 mx-auto rounded"
|
||||
alt="Image preview"
|
||||
id="image-preview"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="relative flex justify-end items-center ml-auto mr-2">
|
||||
<label
|
||||
on:click={handleCancel}
|
||||
class="inline-flex justify-end items-center py-2.5 px-4 text-xs font-medium text-center text-white rounded-md hover:bg-gray-800 cursor-pointer mr-3"
|
||||
>
|
||||
Cancel
|
||||
</label>
|
||||
<label
|
||||
on:click={createComment}
|
||||
class="inline-flex justify-end items-center bg-[#fff] {inputValue.length !==
|
||||
0
|
||||
? 'opacity-100 cursor-pointer'
|
||||
: 'opacity-60'} py-2.5 px-4 text-xs font-medium text-center text-white rounded-md focus:ring-purple-300"
|
||||
>
|
||||
Post
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label on:click={() => handleCancel()} class="absolute left-6 top-4">
|
||||
<svg
|
||||
class="w-6 h-6 inline-block mb-0.5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
fill="#fff"
|
||||
d="M9.125 21.1L.7 12.7q-.15-.15-.213-.325T.425 12q0-.2.063-.375T.7 11.3l8.425-8.425q.35-.35.875-.35t.9.375q.375.375.375.875t-.375.875L3.55 12l7.35 7.35q.35.35.35.863t-.375.887q-.375.375-.875.375t-.875-.375Z"
|
||||
/></svg
|
||||
>
|
||||
<span class="text-white text-md font-medium"> Return </span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@import "/src/lib/assets/style_quill.css";
|
||||
|
||||
.quill-editor,
|
||||
textarea {
|
||||
touch-action: none; /* or 'none' if manipulation doesn't work */
|
||||
}
|
||||
</style>
|
||||
@ -1,214 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import InfoModal from "$lib/components/InfoModal.svelte";
|
||||
import { Chart } from "svelte-echarts";
|
||||
import { init, use } from "echarts/core";
|
||||
import { BarChart } from "echarts/charts";
|
||||
import { GridComponent, TooltipComponent } from "echarts/components";
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
import { monthNames } from "$lib/utils";
|
||||
|
||||
export let rawData: Array<{
|
||||
date: string;
|
||||
price: number;
|
||||
netCall: number;
|
||||
netPut: number;
|
||||
}>;
|
||||
|
||||
use([BarChart, GridComponent, TooltipComponent, CanvasRenderer]);
|
||||
|
||||
let isLoaded = false;
|
||||
let optionsData;
|
||||
let sentiment;
|
||||
|
||||
function getPlotOptions() {
|
||||
const dates = [];
|
||||
const priceList = [];
|
||||
const netCallList = [];
|
||||
const netPutList = [];
|
||||
|
||||
if (rawData && rawData.length) {
|
||||
// Populate arrays with data
|
||||
rawData.forEach((item) => {
|
||||
dates.push(item.date);
|
||||
priceList.push(item.price);
|
||||
netCallList.push(item.netCall);
|
||||
netPutList.push(item.netPut);
|
||||
});
|
||||
|
||||
// Determine sentiment
|
||||
sentiment =
|
||||
netCallList.at(-1) > netPutList.at(-1) ? "bullish" : "bearish";
|
||||
|
||||
return {
|
||||
silent: true,
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
hideDelay: 100, // Tooltip delay in milliseconds
|
||||
},
|
||||
animation: false,
|
||||
grid: {
|
||||
left: "3%",
|
||||
right: "3%",
|
||||
bottom: "0%",
|
||||
top: "10%",
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
boundaryGap: false,
|
||||
data: dates,
|
||||
axisLabel: {
|
||||
color: "#fff",
|
||||
formatter: (value, index) => {
|
||||
if (index % 2 === 0) {
|
||||
const [year, month, day] = value.split("-");
|
||||
return `${day} ${monthNames[parseInt(month, 10) - 1]}`;
|
||||
}
|
||||
return "";
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
splitLine: { show: false },
|
||||
axisLabel: { show: false },
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "Net Call",
|
||||
data: netCallList,
|
||||
type: "bar",
|
||||
stack: "NetFlow",
|
||||
itemStyle: { color: "#2256FF" },
|
||||
showSymbol: false,
|
||||
},
|
||||
{
|
||||
name: "Net Put",
|
||||
data: netPutList,
|
||||
type: "bar",
|
||||
stack: "NetFlow",
|
||||
itemStyle: { color: "#FF2256" },
|
||||
showSymbol: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
console.warn("No raw data available to populate chart options");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
optionsData = getPlotOptions();
|
||||
isLoaded = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<section class="overflow-hidden text-white h-full pb-8">
|
||||
<main class="overflow-hidden">
|
||||
<div class="flex flex-row items-center">
|
||||
<label
|
||||
for="optionsNetFlowInfo"
|
||||
class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-2xl font-bold"
|
||||
>
|
||||
Options Net Flow
|
||||
</label>
|
||||
<InfoModal
|
||||
title={"Options Net Flow"}
|
||||
content={"An Options Net Flow of XY% means the market expects significant price fluctuations for the stock, with an annualized potential range of ±XY% from its current price. This indicates high uncertainty and risk, leading to more expensive options but doesn't predict price direction."}
|
||||
id={"optionsNetFlowInfo"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if isLoaded}
|
||||
{#if rawData?.length !== 0}
|
||||
<div class="w-full flex flex-col items-start">
|
||||
<div class="text-white text-[1rem] mt-2 mb-2 w-full">
|
||||
The options net flow demonstrates a {sentiment} trend in the last 2 trading
|
||||
hours, characterized by the {sentiment === "bullish"
|
||||
? "Net Call Flow exceeding the Net Put Flow"
|
||||
: "Net Put Flow exceeding the Net Call Flow"}.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pb-2 rounded-md bg-default">
|
||||
<div class="app w-full h-[300px] mt-5">
|
||||
<Chart {init} options={optionsData} class="chart" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-row items-center justify-between mx-auto mt-5 w-full sm:w-11/12"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col sm:flex-row items-center ml-3 sm:ml-0 w-1/2 justify-center"
|
||||
>
|
||||
<div
|
||||
class="h-full transform -translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<div
|
||||
class="w-3 h-3 bg-[#2256FF] border-4 box-content border-[#27272A] rounded-full transform sm:-translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<span
|
||||
class="mt-2 sm:mt-0 text-white text-xs sm:text-md sm:font-medium inline-block"
|
||||
>
|
||||
Net Call
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col sm:flex-row items-center ml-3 sm:ml-0 w-1/2 justify-center"
|
||||
>
|
||||
<div
|
||||
class="h-full transform -translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<div
|
||||
class="w-3 h-3 bg-[#FF2F1F] border-4 box-content border-[#27272A] rounded-full transform sm:-translate-x-1/2"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
<span
|
||||
class="mt-2 sm:mt-0 text-white text-xs sm:text-md sm:font-medium inline-block"
|
||||
>
|
||||
Net Put
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="flex justify-center items-center h-80">
|
||||
<div class="relative">
|
||||
<label
|
||||
class="bg-secondary 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}
|
||||
</main>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.app {
|
||||
height: 300px;
|
||||
max-width: 100%; /* Ensure chart width doesn't exceed the container */
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.app {
|
||||
height: 210px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -1,233 +0,0 @@
|
||||
<script lang='ts'>
|
||||
import {stockTicker, etfTicker, cryptoTicker, assetType} from '$lib/store';
|
||||
import { Chart } from 'svelte-echarts'
|
||||
import { init, use } from 'echarts/core'
|
||||
|
||||
import { BarChart } from 'echarts/charts'
|
||||
import { GridComponent, TooltipComponent } from 'echarts/components'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
|
||||
use([BarChart, GridComponent, TooltipComponent, CanvasRenderer])
|
||||
|
||||
export let quantData;
|
||||
|
||||
|
||||
let firstElementKey;
|
||||
|
||||
let monthlyReturnsData;
|
||||
let benchmarkMonthlyReturnsData;
|
||||
|
||||
|
||||
|
||||
const plotAnnualReturn = () => {
|
||||
|
||||
const annualReturnList = [];
|
||||
const annualReturnListBenchmark = [];
|
||||
|
||||
// Find the maximum year in monthlyReturnsData
|
||||
// Find the maximum year in monthlyReturnsData
|
||||
let maxYear = Math?.max(...Object?.keys(monthlyReturnsData)?.map(year => parseInt(year)));
|
||||
|
||||
// Iterate over the last 4 years
|
||||
for (let year = maxYear; year > maxYear - 4; year--) {
|
||||
if (monthlyReturnsData.hasOwnProperty(year.toString())) {
|
||||
const values = monthlyReturnsData[year.toString()];
|
||||
annualReturnList.push(values[values.length - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Find the maximum year in benchmarkMonthlyReturnsData
|
||||
maxYear = Math?.max(...Object?.keys(benchmarkMonthlyReturnsData)?.map(year => parseInt(year)));
|
||||
|
||||
// Iterate over the last 4 years
|
||||
for (let year = maxYear; year > maxYear - 4; year--) {
|
||||
if (benchmarkMonthlyReturnsData.hasOwnProperty(year.toString())) {
|
||||
const values = benchmarkMonthlyReturnsData[year.toString()];
|
||||
annualReturnListBenchmark.push(values[values.length - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const options = {
|
||||
silent: true,
|
||||
animation: false,
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
hideDelay: 100, // Set the delay in milliseconds
|
||||
},
|
||||
grid: {
|
||||
left: "0%",
|
||||
right: "0%",
|
||||
bottom: '0%',
|
||||
height: "90%",
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
data: ['2020', '2021', '2022', '2023','2024'],
|
||||
type: 'category',
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#fff',
|
||||
}
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
show: false, // Disable y-axis grid lines
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#fff',
|
||||
formatter: function (value, index) {
|
||||
// Display every second tick
|
||||
if (index % 2 === 0) {
|
||||
return '%'+value; // Format value in millions
|
||||
} else {
|
||||
return ''; // Hide this tick
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: '{value} %', // Display value with a percent sign
|
||||
},
|
||||
splitLine: {
|
||||
show: false, // Disable y-axis grid lines
|
||||
},
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: $assetType === 'etf' ? $etfTicker : $assetType === 'crypto' ? $cryptoTicker : $stockTicker,
|
||||
data: annualReturnList,
|
||||
type: 'bar',
|
||||
barWidth: '30%',
|
||||
smooth: true,
|
||||
itemStyle: {
|
||||
color: '#398EC2',
|
||||
},
|
||||
|
||||
},
|
||||
{
|
||||
name: 'S&P500',
|
||||
data: annualReturnListBenchmark,
|
||||
type: 'bar',
|
||||
barWidth: '30%',
|
||||
smooth: true,
|
||||
itemStyle: {
|
||||
color: '#FEDD78',
|
||||
},
|
||||
|
||||
|
||||
},
|
||||
],
|
||||
|
||||
};
|
||||
|
||||
|
||||
return options;
|
||||
|
||||
}
|
||||
|
||||
|
||||
let optionsAnnualReturn;
|
||||
|
||||
|
||||
|
||||
$: {
|
||||
|
||||
if (($assetType === 'etf' ? $etfTicker : $assetType === 'crypto' ? $cryptoTicker : $stockTicker) && typeof window !== 'undefined')
|
||||
{
|
||||
try {
|
||||
firstElementKey = Object?.keys(quantData)[0];
|
||||
monthlyReturnsData = quantData[firstElementKey]['Monthly Return'];
|
||||
benchmarkMonthlyReturnsData = quantData['SPY']['Monthly Return']
|
||||
}
|
||||
|
||||
catch(e) {
|
||||
monthlyReturnsData = [];
|
||||
benchmarkMonthlyReturnsData = [];
|
||||
}
|
||||
|
||||
|
||||
optionsAnnualReturn = plotAnnualReturn()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<section class="mt-4 overflow-hidden text-white h-full">
|
||||
|
||||
<div class="w-full m-auto h-full overflow-hidden">
|
||||
<main>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="rounded-2xl pl-3 sm:pr-0 pt-3 w-full mt-4">
|
||||
|
||||
<h1 class="text-white m-auto font-bold text-lg text-center">
|
||||
Annual Return (%)
|
||||
</h1>
|
||||
|
||||
<div class="text-white flex flex-row items-center justify-center m-auto font-medium text-md text-center mt-3">
|
||||
|
||||
<div class="flex flex-row items-center">
|
||||
<div class="h-full bg-gray-800 transform -translate-x-1/2 " aria-hidden="true"></div>
|
||||
<div class="w-4 h-4 bg-[#3E9CD5] border-4 box-content border-gray-900 rounded-full transform -translate-x-1/2" aria-hidden="true"></div>
|
||||
<span class="text-white font-medium inline-block">
|
||||
{$assetType === 'etf' ? $etfTicker : $assetType === 'crypto' ? $cryptoTicker : $stockTicker}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center ml-10">
|
||||
<div class="h-full bg-gray-800 transform -translate-x-1/2 " aria-hidden="true"></div>
|
||||
<div class="w-4 h-4 bg-[#FFF384] border-4 box-content border-gray-900 rounded-full transform -translate-x-1/2" aria-hidden="true"></div>
|
||||
<span class="text-white font-medium inline-block">
|
||||
S&P500
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="app w-full h-[300px] mt-5 mb-16">
|
||||
<Chart {init} options={optionsAnnualReturn} class="chart" />
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
|
||||
.app {
|
||||
height: 300px;
|
||||
max-width: 100%; /* Ensure chart width doesn't exceed the container */
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.app {
|
||||
height: 210px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
@ -4,12 +4,12 @@
|
||||
displayCompanyName,
|
||||
stockTicker,
|
||||
etfTicker,
|
||||
cryptoTicker,
|
||||
assetType,
|
||||
getCache,
|
||||
setCache,
|
||||
} from "$lib/store";
|
||||
import InfoModal from "$lib/components/InfoModal.svelte";
|
||||
import Infobox from "$lib/components/Infobox.svelte";
|
||||
|
||||
import { Chart } from "svelte-echarts";
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
|
||||
export let data;
|
||||
export let rawData = {};
|
||||
|
||||
use([LineChart, GridComponent, TooltipComponent, CanvasRenderer]);
|
||||
|
||||
@ -26,17 +27,18 @@
|
||||
let rating: string | undefined;
|
||||
let outlook: string | undefined;
|
||||
let valueAtRisk: number | string | undefined;
|
||||
let varDict: Record<string, any> = {};
|
||||
let optionsData: any;
|
||||
let monthlyVarAvg: string | undefined;
|
||||
|
||||
function getPlotOptions() {
|
||||
const dates: string[] = [];
|
||||
const varList: number[] = [];
|
||||
const priceList = [];
|
||||
|
||||
varDict?.history?.forEach((item: { date: string; var: number }) => {
|
||||
dates.push(item.date);
|
||||
varList.push(item.var);
|
||||
rawData?.history?.forEach((item: { date: string; var: number }) => {
|
||||
dates.push(item?.date);
|
||||
varList.push(item?.var);
|
||||
priceList.push(item?.price);
|
||||
});
|
||||
|
||||
const sum = varList.reduce((acc, curr) => acc + curr, 0);
|
||||
@ -77,62 +79,44 @@
|
||||
splitLine: { show: false },
|
||||
axisLabel: { show: false },
|
||||
},
|
||||
{
|
||||
type: "value",
|
||||
splitLine: { show: false },
|
||||
axisLabel: { show: false },
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "VaR",
|
||||
data: varList,
|
||||
type: "line",
|
||||
areaStyle: { opacity: 0.8 },
|
||||
itemStyle: { color: "#E11D48" },
|
||||
showSymbol: false,
|
||||
},
|
||||
{
|
||||
name: "Stock Price",
|
||||
data: priceList,
|
||||
type: "line",
|
||||
yAxisIndex: 1,
|
||||
itemStyle: { color: "#fff" },
|
||||
showSymbol: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
const getVaR = async (ticker: string) => {
|
||||
const cachedData = getCache(ticker, "getVaR");
|
||||
if (cachedData) {
|
||||
varDict = cachedData;
|
||||
} else {
|
||||
const postData = { ticker, path: "value-at-risk" };
|
||||
const response = await fetch("/api/ticker-data", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(postData),
|
||||
});
|
||||
|
||||
varDict = await response.json();
|
||||
setCache(ticker, varDict, "getVaR");
|
||||
}
|
||||
|
||||
$varComponent = Object.keys(varDict).length !== 0;
|
||||
};
|
||||
|
||||
$: {
|
||||
const ticker =
|
||||
$assetType === "stock"
|
||||
? $stockTicker
|
||||
: $assetType === "etf"
|
||||
? $etfTicker
|
||||
: $cryptoTicker;
|
||||
if (ticker && typeof window !== "undefined") {
|
||||
const ticker = $assetType === "stock" ? $stockTicker : "etf";
|
||||
|
||||
if (ticker) {
|
||||
isLoaded = false;
|
||||
|
||||
getVaR(ticker)
|
||||
.then(() => {
|
||||
rating = varDict.rating;
|
||||
outlook = varDict.outlook;
|
||||
valueAtRisk = varDict.history?.slice(-1)?.at(0)?.var ?? "n/a";
|
||||
optionsData = getPlotOptions();
|
||||
})
|
||||
.catch((error) => console.error("An error occurred:", error))
|
||||
.finally(() => {
|
||||
isLoaded = true;
|
||||
});
|
||||
rating = rawData.rating;
|
||||
outlook = rawData.outlook;
|
||||
valueAtRisk = rawData.history?.slice(-1)?.at(0)?.var ?? "n/a";
|
||||
optionsData = getPlotOptions();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -142,7 +126,7 @@
|
||||
<div class="flex flex-row items-center">
|
||||
<label
|
||||
for="varInfo"
|
||||
class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-3xl font-bold"
|
||||
class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-2xl font-bold"
|
||||
>
|
||||
Value at Risk
|
||||
</label>
|
||||
@ -153,7 +137,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if Object?.keys(varDict)?.length !== 0}
|
||||
{#if Object?.keys(rawData)?.length !== 0}
|
||||
<div class="pb-4 w-full mt-5">
|
||||
<div
|
||||
class="w-auto p-4 sm:p-6 bg-default sm:bg-default rounded-md relative"
|
||||
@ -223,13 +207,7 @@
|
||||
class="font-semibold">95%</span
|
||||
>
|
||||
probability that
|
||||
<span class="text-blue-400"
|
||||
>${$assetType === "stock"
|
||||
? $stockTicker
|
||||
: $assetType === "etf"
|
||||
? $etfTicker
|
||||
: $cryptoTicker}</span
|
||||
>
|
||||
{$assetType === "stock" ? $stockTicker : $etfTicker}
|
||||
will incur a maximum loss of
|
||||
<span class="text-[#FF2F1F] font-semibold">{valueAtRisk}%</span>
|
||||
in the upcoming week.
|
||||
@ -244,18 +222,15 @@
|
||||
</h2>
|
||||
<div class="text-white text-[1rem] mt-3">
|
||||
Based on historical price data, the company experienced an average
|
||||
monthly Value at Risk (VaR) of {monthlyVarAvg}%.
|
||||
monthly Value-at-Risk <span class="font-semibold">{monthlyVarAvg}%</span
|
||||
>.
|
||||
</div>
|
||||
|
||||
<div class="app w-full h-[300px] mt-5">
|
||||
<Chart {init} options={optionsData} class="chart" />
|
||||
</div>
|
||||
{:else}
|
||||
<h2
|
||||
class="mt-10 mb-5 flex justify-center items-center text-2xl font-bold text-slate-700 m-auto"
|
||||
>
|
||||
No data available
|
||||
</h2>
|
||||
<Infobox text="No data available" />
|
||||
{/if}
|
||||
</main>
|
||||
</section>
|
||||
|
||||
@ -1,145 +0,0 @@
|
||||
<script lang='ts'>
|
||||
|
||||
import { onMount, onDestroy} from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { screenWidth } from '$lib/store';
|
||||
|
||||
let id = uuidv4();
|
||||
|
||||
export let src;
|
||||
export let hideProgressbar = false;
|
||||
|
||||
|
||||
let container;
|
||||
let observer;
|
||||
let video;
|
||||
let progress = -1;
|
||||
|
||||
|
||||
onMount(async () => {
|
||||
|
||||
video = document.getElementById(id);
|
||||
|
||||
|
||||
if (typeof window !== 'undefined' && browser) {
|
||||
const handleIntersect = (entries, observer) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry?.isIntersecting) {
|
||||
//console.log("video id:", id, "should play now");
|
||||
playVideo();
|
||||
} else {
|
||||
pauseVideo();
|
||||
//console.log("video id:", id, "should stop now");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const options = { threshold: 0.4, rootMargin: '-0px 0px' };
|
||||
observer = new IntersectionObserver(handleIntersect, options);
|
||||
|
||||
if (container instanceof Element) {
|
||||
observer.observe(container);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function updateProgress() {
|
||||
if (video && video?.duration && !video?.paused) {
|
||||
const { currentTime, duration } = video;
|
||||
progress = (currentTime / duration) * 100;
|
||||
// Use decimal values for smoother progress
|
||||
progress = parseFloat(progress.toFixed(2));
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if (video) {
|
||||
video.ontimeupdate = updateProgress;
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
onDestroy(() => {
|
||||
if (observer) {
|
||||
observer?.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
function playVideo() {
|
||||
if (video) {
|
||||
video.play();
|
||||
video.muted = true;
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function pauseVideo() {
|
||||
|
||||
if (video) {
|
||||
video.pause();
|
||||
video.muted = true;
|
||||
isVideoMuted[id] = video.muted;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
let isVideoMuted = {};
|
||||
|
||||
isVideoMuted[id] = true;
|
||||
|
||||
function toggleMute(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (video?.paused)
|
||||
{
|
||||
video?.play();
|
||||
isVideoMuted[id] = false;
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
video.muted = !video?.muted;
|
||||
isVideoMuted[id] = video?.muted;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<div class="absolute inset-0 bg-cover object-fill bg-center bg-[#000]">
|
||||
</div>
|
||||
|
||||
<video
|
||||
id={id}
|
||||
playsinline
|
||||
loop
|
||||
class="relative m-auto w-full max-w-screen max-h-[520px] sm:max-h-[700px]"
|
||||
src={src}
|
||||
on:click={toggleMute}
|
||||
bind:this={container}
|
||||
on:loadedmetadata={updateProgress}
|
||||
/>
|
||||
|
||||
|
||||
|
||||
<div class="z-20 bg-center bg-[#000] rounded-full w-11 h-11 bg-opacity-60 absolute bottom-2 left-2 flex items-center justify-center">
|
||||
{#if isVideoMuted[id]}
|
||||
<svg class="w-6 h-6 m-auto" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#ffffff" stroke="#ffffff"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title>sound-off-filled</title> <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="icon" fill="#fff" transform="translate(42.666667, 59.581722)"> <path d="M47.0849493,-1.42108547e-14 L298.668,251.583611 L304.101001,257.015597 L304.101,257.016 L353.573532,306.488791 C353.573732,306.488458 353.573933,306.488124 353.574133,306.48779 L384.435257,337.348961 L384.434,337.349 L409.751616,362.666662 L379.581717,392.836561 L191.749,205.003 L191.749973,369.105851 L81.0208,283.647505 L7.10542736e-15,283.647505 L7.10542736e-15,112.980838 L80.8957867,112.980838 L91.433,104.688 L16.9150553,30.169894 L47.0849493,-1.42108547e-14 Z M361.298133,28.0146513 C429.037729,103.653701 443.797162,209.394226 405.578884,298.151284 L372.628394,265.201173 C396.498256,194.197542 381.626623,113.228555 328.013013,54.642278 L361.298133,28.0146513 Z M276.912853,95.5237713 C305.539387,127.448193 318.4688,168.293162 315.701304,208.275874 L266.464558,159.040303 C261.641821,146.125608 254.316511,133.919279 244.488548,123.156461 L243.588693,122.182545 L276.912853,95.5237713 Z M191.749973,25.7516113 L191.749,84.3256113 L158.969,51.5456113 L191.749973,25.7516113 Z" id="Combined-Shape"> </path> </g> </g> </g></svg>
|
||||
{:else}
|
||||
<svg class="w-6 h-6 m-auto" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#fff"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title>sound-loud-filled</title> <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="icon" fill="#ffffff" transform="translate(42.666667, 85.333333)"> <path d="M361.299413,341.610667 L328.014293,314.98176 C402.206933,233.906133 402.206933,109.96608 328.013013,28.8906667 L361.298133,2.26304 C447.910187,98.97536 447.908907,244.898347 361.299413,341.610667 Z M276.912853,69.77216 L243.588693,96.4309333 C283.38432,138.998613 283.38304,204.87488 243.589973,247.44256 L276.914133,274.101333 C329.118507,215.880107 329.118507,127.992107 276.912853,69.77216 Z M191.749973,1.42108547e-14 L80.8957867,87.2292267 L7.10542736e-15,87.2292267 L7.10542736e-15,257.895893 L81.0208,257.895893 L191.749973,343.35424 L191.749973,1.42108547e-14 L191.749973,1.42108547e-14 Z" id="Shape"> </path> </g> </g> </g></svg>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if !hideProgressbar}
|
||||
<progress class="progress [&::-webkit-progress-value]:bg-[#FF0000] [&::-moz-progress-bar]:bg-[#FF0000] w-full bg-white" value={progress} max="100" style="height: 1.5px"></progress>
|
||||
{/if}
|
||||
|
||||
@ -13,10 +13,8 @@
|
||||
|
||||
if (state !== "overview" && subSectionMap[state]) {
|
||||
displaySubSection = state;
|
||||
//goto(`/stocks/${$stockTicker}${subSectionMap[state]}`);
|
||||
} else {
|
||||
displaySubSection = state;
|
||||
//goto(`/stocks/${$stockTicker}/statistics`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,6 +75,7 @@
|
||||
>
|
||||
Employees
|
||||
</a>
|
||||
|
||||
<a
|
||||
href={`/stocks/${$stockTicker}/statistics/fail-to-deliver`}
|
||||
on:click={() => changeSubSection("fail-to-deliver")}
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
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(),
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,92 @@
|
||||
<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?.var - a?.var);
|
||||
</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"
|
||||
>Value-at-Risk</th
|
||||
></tr
|
||||
></thead
|
||||
>
|
||||
<tbody>
|
||||
{#each similarStocks?.slice(0, 8) as item, index}
|
||||
<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?.var) + "%"}</td
|
||||
>
|
||||
</tr>
|
||||
{/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>
|
||||
@ -0,0 +1,46 @@
|
||||
export const load = async ({ locals, params }) => {
|
||||
const { apiKey, apiURL } = locals;
|
||||
|
||||
const getVaR = async () => {
|
||||
const postData = {
|
||||
ticker: params.tickerID,
|
||||
};
|
||||
|
||||
// make the POST request to the endpoint
|
||||
const response = await fetch(apiURL + "/value-at-risk", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-API-KEY": apiKey,
|
||||
},
|
||||
body: JSON.stringify(postData),
|
||||
});
|
||||
|
||||
const output = await response.json();
|
||||
return output;
|
||||
};
|
||||
|
||||
|
||||
const getHistoricalPrice = async () => {
|
||||
const postData = { ticker: params.tickerID, timePeriod: "max" };
|
||||
const response = await fetch(apiURL + "/historical-price", {
|
||||
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 {
|
||||
getVaR: await getVaR(),
|
||||
getHistoricalPrice: await getHistoricalPrice(),
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,98 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
numberOfUnreadNotification,
|
||||
displayCompanyName,
|
||||
stockTicker,
|
||||
} from "$lib/store";
|
||||
import Infobox from "$lib/components/Infobox.svelte";
|
||||
import VaR from "$lib/components/VaR.svelte";
|
||||
|
||||
export let data;
|
||||
|
||||
let rawData = data?.getVaR || {};
|
||||
if (!rawData?.history || !data?.getHistoricalPrice) {
|
||||
console.error(
|
||||
"Missing required data: rawData.history or data.getHistoricalPrice is undefined.",
|
||||
);
|
||||
} else {
|
||||
for (let i = 0; i < rawData.history.length; i++) {
|
||||
const entry = rawData.history[i];
|
||||
const entryMonth = entry?.date; // "YYYY-MM" format
|
||||
|
||||
let matchingData = null;
|
||||
|
||||
// Find the first price at the beginning of the month
|
||||
for (let j = 0; j < data.getHistoricalPrice.length; j++) {
|
||||
const priceData = data.getHistoricalPrice[j];
|
||||
|
||||
// Extract year and month from the `time` field
|
||||
const priceDate = priceData?.time?.slice(0, 7); // "YYYY-MM" format
|
||||
if (priceDate === entryMonth) {
|
||||
matchingData = priceData;
|
||||
break; // Use the first matching entry
|
||||
}
|
||||
}
|
||||
|
||||
// Add the 'close' price to the entry if matching data is found
|
||||
if (matchingData) {
|
||||
entry.price = matchingData.close;
|
||||
} else {
|
||||
console.warn(`No matching data found for month: ${entry?.date}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(rawData);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>
|
||||
{$numberOfUnreadNotification > 0 ? `(${$numberOfUnreadNotification})` : ""}
|
||||
{$displayCompanyName} ({$stockTicker}) Value-at-Risk Stocknear
|
||||
</title>
|
||||
<meta
|
||||
name="description"
|
||||
content={`Historical Value-at-Risk of ${$stockTicker}.`}
|
||||
/>
|
||||
<meta
|
||||
property="og:title"
|
||||
content={`${$displayCompanyName} (${$stockTicker}) Value-at-Risk · Stocknear`}
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content={`Historical Value-at-Risk of ${$stockTicker}.`}
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content={`${$displayCompanyName} (${$stockTicker}) Value-at-Risk · Stocknear`}
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content={`Historical Value-at-Risk 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"
|
||||
>
|
||||
<main class="w-full">
|
||||
<div class="sm:p-7 m-auto mt-2 sm:mt-0">
|
||||
{#if rawData?.length !== 0}
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
<VaR {data} {rawData} />
|
||||
</div>
|
||||
{:else}
|
||||
<Infobox text="No data available" />
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
Loading…
x
Reference in New Issue
Block a user