279 lines
8.3 KiB
Svelte
279 lines
8.3 KiB
Svelte
<script lang="ts">
|
|
import {
|
|
varComponent,
|
|
displayCompanyName,
|
|
stockTicker,
|
|
etfTicker,
|
|
cryptoTicker,
|
|
assetType,
|
|
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, TooltipComponent } from "echarts/components";
|
|
import { CanvasRenderer } from "echarts/renderers";
|
|
|
|
export let data;
|
|
|
|
use([LineChart, GridComponent, TooltipComponent, CanvasRenderer]);
|
|
|
|
let isLoaded = false;
|
|
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[] = [];
|
|
|
|
varDict?.history?.forEach((item: { date: string; var: number }) => {
|
|
dates.push(item.date);
|
|
varList.push(item.var);
|
|
});
|
|
|
|
const sum = varList.reduce((acc, curr) => acc + curr, 0);
|
|
monthlyVarAvg = (sum / varList.length)?.toFixed(2);
|
|
|
|
const option = {
|
|
silent: true,
|
|
tooltip: {
|
|
trigger: "axis",
|
|
hideDelay: 100,
|
|
},
|
|
animation: false,
|
|
grid: {
|
|
left: "2%",
|
|
right: "2%",
|
|
bottom: "2%",
|
|
top: "5%",
|
|
containLabel: true,
|
|
},
|
|
xAxis: {
|
|
type: "category",
|
|
boundaryGap: false,
|
|
data: dates,
|
|
axisLabel: {
|
|
color: "#fff",
|
|
formatter: (value: string) => {
|
|
const date = new Date(value + "-01");
|
|
return new Intl.DateTimeFormat("en-US", {
|
|
year: "numeric",
|
|
month: "short",
|
|
}).format(date);
|
|
},
|
|
},
|
|
},
|
|
yAxis: [
|
|
{
|
|
type: "value",
|
|
splitLine: { show: false },
|
|
axisLabel: { show: false },
|
|
},
|
|
],
|
|
series: [
|
|
{
|
|
name: "VaR",
|
|
data: varList,
|
|
type: "line",
|
|
areaStyle: { opacity: 0.8 },
|
|
itemStyle: { color: "#E11D48" },
|
|
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") {
|
|
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;
|
|
});
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<section class="overflow-hidden text-white h-full pb-10 sm:pb-0">
|
|
<main class="overflow-hidden">
|
|
<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"
|
|
>
|
|
Value at Risk
|
|
</label>
|
|
<InfoModal
|
|
title={"Value at Risk"}
|
|
content={`Value at Risk (VaR) quantifies the potential loss of investment or capital within a specified time frame (N days) under typical market conditions, providing an estimate of potential losses with a given probability for ${$displayCompanyName}.`}
|
|
id={"varInfo"}
|
|
/>
|
|
</div>
|
|
|
|
{#if Object?.keys(varDict)?.length !== 0}
|
|
<div class="pb-4 w-full mt-5">
|
|
<div
|
|
class="w-auto p-4 sm:p-6 bg-[#09090B] sm:bg-[#09090B] rounded-md relative"
|
|
>
|
|
<div class="flex flex-row items-center justify-between">
|
|
<div class="relative size-[60px] sm:size-[90px] ml-auto">
|
|
<svg
|
|
class="size-full w-[60px] h-[60px] sm:w-[90px] sm:h-[90px]"
|
|
viewBox="0 0 36 36"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
>
|
|
<!-- Background Circle -->
|
|
<circle
|
|
cx="18"
|
|
cy="18"
|
|
r="16"
|
|
fill="none"
|
|
class="stroke-current text-[#303030]"
|
|
stroke-width="4"
|
|
></circle>
|
|
<!-- Progress Circle inside a group with rotation -->
|
|
<g class="origin-center -rotate-90 transform">
|
|
<circle
|
|
cx="18"
|
|
cy="18"
|
|
r="16"
|
|
fill="none"
|
|
class="stroke-current {rating > 5
|
|
? 'text-[#00FC50]'
|
|
: rating < 5
|
|
? 'text-[#FF2F1F]'
|
|
: 'text-white'} "
|
|
stroke-width="4"
|
|
stroke-dasharray="100"
|
|
stroke-dashoffset={100 - rating * 10}
|
|
></circle>
|
|
</g>
|
|
</svg>
|
|
<!-- Percentage Text -->
|
|
<div
|
|
class="absolute top-1/2 start-1/2 transform -translate-y-1/2 -translate-x-1/2"
|
|
>
|
|
<span
|
|
class="text-center text-white text-xl sm:text-2xl font-semibold"
|
|
>
|
|
{rating}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
class="flex flex-col items-start ml-4 sm:ml-10 mr-auto sm:-top-3 sm:relative"
|
|
>
|
|
<h3
|
|
class="hidden sm:block text-gray-300 text-[1rem] sm:text-lg font-semibold"
|
|
>
|
|
<span
|
|
class={outlook === "Minimum Risk"
|
|
? "text-[#10BC09]"
|
|
: outlook === "Risky"
|
|
? "text-red-500"
|
|
: "text-white"}>{outlook}</span
|
|
> outlook:
|
|
</h3>
|
|
<span class="text-gray-200 text-sm sm:text-lg mt-1">
|
|
Under typical market conditions, there is a <span
|
|
class="font-semibold">95%</span
|
|
>
|
|
probability that
|
|
<span class="text-blue-400"
|
|
>${$assetType === "stock"
|
|
? $stockTicker
|
|
: $assetType === "etf"
|
|
? $etfTicker
|
|
: $cryptoTicker}</span
|
|
>
|
|
will incur a maximum loss of
|
|
<span class="text-[#FF2F1F] font-semibold">{valueAtRisk}%</span>
|
|
in the upcoming week.
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h2 class="text-white text-xl sm:text-2xl font-semibold mt-5">
|
|
Historical VaR
|
|
</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}%.
|
|
</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>
|
|
{/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>
|