breakup overview => fundamental
This commit is contained in:
parent
ec480afd27
commit
27b294aa03
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
764
src/routes/stocks/[tickerID]/ai-analysis/+page.svelte
Normal file
764
src/routes/stocks/[tickerID]/ai-analysis/+page.svelte
Normal file
@ -0,0 +1,764 @@
|
||||
<script lang="ts">
|
||||
|
||||
import {numberOfUnreadNotification, displayCompanyName, screenWidth, stockTicker} from '$lib/store';
|
||||
import { Chart } from 'svelte-echarts'
|
||||
import { abbreviateNumber } from '$lib/utils';
|
||||
import InfoModal from '$lib/components/InfoModal.svelte';
|
||||
import { onMount } from 'svelte'
|
||||
import UpgradeToPro from '$lib/components/UpgradeToPro.svelte';
|
||||
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 data;
|
||||
|
||||
let rawPlotData = data?.getOptionsPlotData;
|
||||
let filteredList = [];
|
||||
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
|
||||
let optionsPlotData = data?.getOptionsPlotData?.plot;
|
||||
let displayData = 'volume';
|
||||
let options;
|
||||
let rawData = data?.getOptionsFlowData
|
||||
let optionList = rawData?.slice(0,30);
|
||||
let flowSentiment = 'n/a';
|
||||
let callPercentage;
|
||||
let putPercentage;
|
||||
let displayCallVolume;
|
||||
let displayPutVolume;
|
||||
let latestPutCallRatio;
|
||||
let displayOTMRatio;
|
||||
|
||||
|
||||
|
||||
let totalVolume //= data?.getOptionsPlotData?.totalVolume;
|
||||
|
||||
let totalOpenInterest //= data?.getOptionsPlotData?.totalOpenInterest;
|
||||
|
||||
|
||||
// Computing the put-call ratio for open interest
|
||||
let putCallOpenInterestRatio //= data?.getOptionsPlotData?.putCallOpenInterestRatio;
|
||||
let putCallRatio;
|
||||
let displayTotalVolume //= new Intl.NumberFormat("en", {minimumFractionDigits: 0, maximumFractionDigits: 0})?.format(totalVolume);
|
||||
let displayTotalOpenInterest //= new Intl.NumberFormat("en", {minimumFractionDigits: 0, maximumFractionDigits: 0})?.format(totalOpenInterest);
|
||||
let displayTotalPutCall
|
||||
let dateList //= data?.getOptionsPlotData?.dateList;
|
||||
|
||||
let callVolumeList //= data?.getOptionsPlotData?.callVolumeList;
|
||||
let putVolumeList //= data?.getOptionsPlotData?.putVolumeList;
|
||||
let callOpenInterestList //= data?.getOptionsPlotData?.callOpenInterestList;
|
||||
let putOpenInterestList //= data?.getOptionsPlotData?.putOpenInterestList;
|
||||
|
||||
|
||||
let displayTimePeriod = 'threeMonths'
|
||||
|
||||
|
||||
function formatDate(dateStr) {
|
||||
// Parse the input date string (YYYY-mm-dd)
|
||||
var date = new Date(dateStr);
|
||||
|
||||
// Get month, day, and year
|
||||
var month = date.getMonth() + 1; // Month starts from 0
|
||||
var day = date.getDate();
|
||||
var year = date.getFullYear();
|
||||
|
||||
// Extract the last two digits of the year
|
||||
var shortYear = year.toString().slice(-2);
|
||||
|
||||
// Add leading zeros if necessary
|
||||
month = (month < 10 ? "0" : "") + month;
|
||||
day = (day < 10 ? "0" : "") + day;
|
||||
|
||||
var formattedDate = month + "/" + day + "/" + year;
|
||||
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
function formatTime(timeString) {
|
||||
// Split the time string into components
|
||||
const [hours, minutes, seconds] = timeString.split(':').map(Number);
|
||||
|
||||
// Determine AM or PM
|
||||
const period = hours >= 12 ? 'PM' : 'AM';
|
||||
|
||||
// Convert hours from 24-hour to 12-hour format
|
||||
const formattedHours = hours % 12 || 12; // Converts 0 to 12 for midnight
|
||||
|
||||
// Format the time string
|
||||
const formattedTimeString = `${formattedHours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')} ${period}`;
|
||||
|
||||
return formattedTimeString;
|
||||
}
|
||||
|
||||
function changeStatement(event)
|
||||
{
|
||||
displayData = event.target.value;
|
||||
}
|
||||
|
||||
function changeTimePeriod(event)
|
||||
{
|
||||
displayTimePeriod = event.target.value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function plotData(callData, putData) {
|
||||
const options = {
|
||||
animation: false,
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
silent: true,
|
||||
grid: {
|
||||
left: $screenWidth < 640 ? '5%' : '2%',
|
||||
right: $screenWidth < 640 ? '5%' : '2%',
|
||||
bottom: '20%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
data: dateList,
|
||||
axisLabel: {
|
||||
formatter: function (value) {
|
||||
// Assuming dates are in the format 'yyyy-mm-dd'
|
||||
const dateParts = value.split('-');
|
||||
const monthIndex = parseInt(dateParts[1]) - 1; // Months are zero-indexed in JavaScript Date objects
|
||||
const year = parseInt(dateParts[0]);
|
||||
const day = parseInt(dateParts[2])
|
||||
return `${day} ${monthNames[monthIndex]} ${year}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
show: false // Hide the y-axis label
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: 'Call',
|
||||
type: 'bar',
|
||||
stack: 'Put-Call Ratio',
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: callData,
|
||||
itemStyle: {
|
||||
color: '#00FC50'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Put',
|
||||
type: 'bar',
|
||||
stack: 'Put-Call Ratio',
|
||||
emphasis: {
|
||||
focus: 'series'
|
||||
},
|
||||
data: putData,
|
||||
itemStyle: {
|
||||
color: '#EE5365' //'#7A1C16'
|
||||
},
|
||||
},
|
||||
]
|
||||
};
|
||||
return options;
|
||||
}
|
||||
|
||||
function calculateStats() {
|
||||
const currentPrice = parseFloat(data?.getStockQuote?.price);
|
||||
|
||||
const { callVolumeSum, putVolumeSum, bullishCount, bearishCount, otmVolume, itmVolume } = rawData?.reduce((acc, item) => {
|
||||
const volume = parseInt(item?.volume);
|
||||
const strikePrice = parseFloat(item?.strike_price);
|
||||
|
||||
if (item?.put_call === "Calls") {
|
||||
acc.callVolumeSum += volume;
|
||||
if (strikePrice > currentPrice) {
|
||||
acc.otmVolume += volume;
|
||||
} else {
|
||||
acc.itmVolume += volume;
|
||||
}
|
||||
} else if (item?.put_call === "Puts") {
|
||||
acc.putVolumeSum += volume;
|
||||
if (strikePrice < currentPrice) {
|
||||
acc.itmVolume += volume;
|
||||
} else {
|
||||
acc.otmVolume += volume;
|
||||
}
|
||||
}
|
||||
|
||||
if (item?.sentiment === "Bullish") {
|
||||
acc.bullishCount += 1;
|
||||
} else if (item?.sentiment === "Bearish") {
|
||||
acc.bearishCount += 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, { callVolumeSum: 0, putVolumeSum: 0, bullishCount: 0, bearishCount: 0, otmVolume: 0, itmVolume: 0 });
|
||||
|
||||
if (bullishCount > bearishCount) {
|
||||
flowSentiment = 'Bullish';
|
||||
} else if (bullishCount < bearishCount) {
|
||||
flowSentiment = 'Bearish';
|
||||
} else {
|
||||
flowSentiment = 'Neutral';
|
||||
}
|
||||
|
||||
latestPutCallRatio = (putVolumeSum / callVolumeSum);
|
||||
callPercentage = Math.floor((callVolumeSum) / (callVolumeSum + putVolumeSum) * 100);
|
||||
putPercentage = (100 - callPercentage);
|
||||
displayCallVolume = callVolumeSum;
|
||||
displayPutVolume = putVolumeSum;
|
||||
|
||||
// Calculate OTM/ITM ratio
|
||||
displayOTMRatio = otmVolume / (itmVolume+otmVolume) ?? 0;
|
||||
}
|
||||
|
||||
function filterDate(filteredList, displayTimePeriod) {
|
||||
const now = Date.now();
|
||||
let cutoffDate;
|
||||
|
||||
switch (displayTimePeriod) {
|
||||
case 'oneWeek':
|
||||
cutoffDate = now - 7 * 24 * 60 * 60 * 1000;
|
||||
break;
|
||||
case 'oneMonth':
|
||||
cutoffDate = now - 30 * 24 * 60 * 60 * 1000;
|
||||
break;
|
||||
case 'threeMonths':
|
||||
cutoffDate = now - 90 * 24 * 60 * 60 * 1000;
|
||||
break;
|
||||
case 'sixMonths':
|
||||
cutoffDate = now - 180 * 24 * 60 * 60 * 1000;
|
||||
break;
|
||||
case 'oneYear':
|
||||
cutoffDate = now - 365 * 24 * 60 * 60 * 1000;
|
||||
break;
|
||||
default:
|
||||
throw new Error('Invalid time period');
|
||||
}
|
||||
|
||||
return filteredList?.filter(item => {
|
||||
// Convert YYYY-MM-DD to a timestamp
|
||||
const [year, month, day] = item?.date?.split('-')?.map(Number);
|
||||
const itemTimestamp = new Date(year, month - 1, day)?.getTime();
|
||||
|
||||
return itemTimestamp >= cutoffDate;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function processPlotData(filteredList: any[]) {
|
||||
const totals = filteredList?.reduce((acc, obj) => {
|
||||
acc.callVolume += obj?.CALL?.volume;
|
||||
acc.putVolume += obj?.PUT?.volume;
|
||||
acc.callOpenInterest += obj?.CALL?.open_interest;
|
||||
acc.putOpenInterest += obj?.PUT?.open_interest;
|
||||
return acc;
|
||||
}, { callVolume: 0, putVolume: 0, callOpenInterest: 0, putOpenInterest: 0 });
|
||||
|
||||
putCallRatio = (totals.putVolume / totals.callVolume)?.toFixed(2);
|
||||
totalVolume = totals.callVolume + totals.putVolume;
|
||||
totalOpenInterest = totals.callOpenInterest + totals.putOpenInterest;
|
||||
putCallOpenInterestRatio = (totals.putOpenInterest / totals.callOpenInterest)?.toFixed(2);
|
||||
|
||||
dateList = filteredList?.map(item => item.date);
|
||||
callVolumeList = filteredList?.map(item => item?.CALL?.volume);
|
||||
putVolumeList = filteredList?.map(item => item?.PUT?.volume);
|
||||
callOpenInterestList = filteredList?.map(item => item?.CALL?.open_interest);
|
||||
putOpenInterestList = filteredList?.map(item => item?.PUT?.open_interest);
|
||||
|
||||
displayTotalVolume = new Intl.NumberFormat("en", {minimumFractionDigits: 0, maximumFractionDigits: 0}).format(totalVolume);
|
||||
displayTotalPutCall = new Intl.NumberFormat("en", {minimumFractionDigits: 0, maximumFractionDigits: 0}).format(putCallRatio);
|
||||
displayTotalOpenInterest = new Intl.NumberFormat("en", {minimumFractionDigits: 0, maximumFractionDigits: 0}).format(totalOpenInterest);
|
||||
|
||||
// Determine the type of plot data to generate based on displayData
|
||||
if (displayData === 'volume') {
|
||||
options = plotData(callVolumeList, putVolumeList);
|
||||
} else if (displayData === 'openInterest') {
|
||||
options = plotData(callOpenInterestList, putOpenInterestList);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function handleScroll() {
|
||||
const scrollThreshold = document.body.offsetHeight * 0.8; // 80% of the website height
|
||||
const isBottom = window.innerHeight + window.scrollY >= scrollThreshold;
|
||||
if (isBottom && optionList?.length !== rawData?.length) {
|
||||
const nextIndex = optionList?.length;
|
||||
const filteredNewResults = rawData?.slice(nextIndex, nextIndex + 25);
|
||||
optionList = [...optionList, ...filteredNewResults];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
onMount(async () => {
|
||||
calculateStats();
|
||||
|
||||
if(data?.user?.tier === 'Pro') {
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
$: {
|
||||
if ((displayTimePeriod || displayData) && optionsPlotData?.length !== 0 && typeof window !== 'undefined') {
|
||||
// Filter the raw plot data based on the selected time period
|
||||
filteredList = filterDate(rawPlotData, displayTimePeriod);
|
||||
|
||||
// Process the filtered list to generate the plot data
|
||||
processPlotData(filteredList);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<svelte:head>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>
|
||||
{$numberOfUnreadNotification > 0 ? `(${$numberOfUnreadNotification})` : ''} {$displayCompanyName} ({$stockTicker}) Options Activity · stocknear
|
||||
</title>
|
||||
<meta name="description" content={`Detailed informaton of unusual options activity for ${$displayCompanyName} (${$stockTicker}).`} />
|
||||
|
||||
<!-- Other meta tags -->
|
||||
<meta property="og:title" content={`${$displayCompanyName} (${$stockTicker}) Options Activity · stocknear`}/>
|
||||
<meta property="og:description" content={`Detailed informaton of unusual options activity for ${$displayCompanyName} (${$stockTicker}).`} />
|
||||
<meta property="og:type" content="website"/>
|
||||
<!-- Add more Open Graph meta tags as needed -->
|
||||
|
||||
<!-- Twitter specific meta tags -->
|
||||
<meta name="twitter:card" content="summary_large_image"/>
|
||||
<meta name="twitter:title" content={`${$displayCompanyName} (${$stockTicker}) Options Activity · stocknear`}/>
|
||||
<meta name="twitter:description" content={`Detailed informaton of unusual options activity for ${$displayCompanyName} (${$stockTicker}).`} />
|
||||
<!-- Add more Twitter meta tags as needed -->
|
||||
|
||||
</svelte:head>
|
||||
|
||||
|
||||
|
||||
<section class="bg-[#09090B] overflow-hidden text-white h-full mb-40 sm:mb-0 w-full">
|
||||
<div class="flex justify-center m-auto h-full overflow-hidden w-full">
|
||||
<div class="relative flex justify-center items-center overflow-hidden w-full">
|
||||
<div class="sm:p-7 w-full m-auto mt-2 sm:mt-0">
|
||||
<div class="mb-6">
|
||||
<h2 class="text-2xl sm:text-3xl text-gray-200 font-bold mb-4">
|
||||
Unsual Options Activity
|
||||
</h2>
|
||||
|
||||
<div class="w-fit text-white p-3 sm:p-5 mb-5 rounded-lg sm:flex sm:flex-row sm:items-center border border-slate-800 text-sm sm:text-[1rem]">
|
||||
<svg class="w-6 h-6 flex-shrink-0 inline-block sm:mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#a474f6" d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24m-4 48a12 12 0 1 1-12 12a12 12 0 0 1 12-12m12 112a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 0 16"/></svg>
|
||||
|
||||
{#if optionsPlotData?.length !== 0}
|
||||
1 Year of options activity involving {$displayCompanyName} by major institutional traders and hedge funds.
|
||||
{:else}
|
||||
There's no data available, indicating that major traders may not be actively betting on {$displayCompanyName}.
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{#if optionsPlotData?.length !== 0}
|
||||
|
||||
<div class="mb-4 grid grid-cols-2 grid-rows-2 divide-gray-500 rounded-lg border border-gray-600 bg-[#272727] shadow md:grid-cols-4 md:grid-rows-1 md:divide-x">
|
||||
<div class="p-4 bp:p-5 sm:p-6">
|
||||
<label for="totaVolume" class="mr-1 cursor-pointer flex flex-row items-center text-white text-[1rem]">
|
||||
Total Volume
|
||||
<InfoModal
|
||||
title={"Total Volume"}
|
||||
content={"The total volume is the combined number of puts and calls traded over the past three months in options trading."}
|
||||
id={"totaVolume"}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div class="mt-1 break-words font-semibold leading-8 text-light text-lg">
|
||||
{displayTotalVolume}
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 bp:p-5 sm:p-6 border-l border-contrast md:border-0">
|
||||
<label for="totalOpenInterest" class="mr-1 cursor-pointer flex flex-row items-center text-white text-[1rem]">
|
||||
Total OI
|
||||
<InfoModal
|
||||
title={"Total OI"}
|
||||
content={"The total open interest reflects the aggregate number of outstanding options contracts in options trading."}
|
||||
id={"totalOpenInterest"}
|
||||
/>
|
||||
</label>
|
||||
<div class="mt-1 break-words font-semibold leading-8 text-light text-lg">
|
||||
{displayTotalOpenInterest}
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 bp:p-5 sm:p-6 border-t border-contrast md:border-0">
|
||||
<label for="putCallRatio" class="mr-1 cursor-pointer flex flex-row items-center text-white text-[1rem]">
|
||||
P/C Ratio
|
||||
<InfoModal
|
||||
title={"P/C Ratio"}
|
||||
content={"The put-call ratio assesses market sentiment and potential movements by comparing traded put options to call options."}
|
||||
id={"putCallRatio"}
|
||||
/>
|
||||
</label>
|
||||
<div class="mt-1 break-words font-semibold leading-8 text-light text-lg">
|
||||
{putCallRatio !== 'Infinity' ? putCallRatio : '> 1'}
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 bp:p-5 sm:p-6 border-t border-contrast md:border-0 border-l border-contrast md:border-0">
|
||||
<label for="openInteresteRatio" class="mr-1 cursor-pointer flex flex-row items-center text-white text-[1rem]">
|
||||
OI P/C Ratio
|
||||
<InfoModal
|
||||
title={"OI P/C Ratio"}
|
||||
content={"The open interest put-call ratio measures market sentiment in options trading by comparing the total number of outstanding put options contracts to outstanding call options contracts. A higher ratio suggests bearish sentiment, while a lower ratio indicates bullish sentiment."}
|
||||
id={"openInteresteRatio"}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div class="mt-1 break-words font-semibold leading-8 text-light text-lg">
|
||||
{putCallOpenInterestRatio !== 'Infinity' ? putCallOpenInterestRatio : '> 1'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center w-full mt-10">
|
||||
<select class="ml-1 w-40 select select-bordered select-sm p-0 pl-5 bg-[#2A303C]" on:change={changeTimePeriod}>
|
||||
<option disabled>Choose a time period</option>
|
||||
<option value="oneWeek">1 Week</option>
|
||||
<option value="oneMonth" selected>1 Month</option>
|
||||
<option value="threeMonths" selected>3 Months</option>
|
||||
<option value="sixMonths">6 Months</option>
|
||||
<option value="oneYear">1 Year</option>
|
||||
</select>
|
||||
|
||||
<select class="ml-auto sm:ml-3 w-40 select select-bordered select-sm p-0 pl-5 bg-[#2A303C]" on:change={changeStatement}>
|
||||
<option disabled>Choose a category</option>
|
||||
<option value="volume" selected>Volume</option>
|
||||
<option value="openInterest">Open Interest</option>
|
||||
</select>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app w-full bg-[#09090B] rounded-xl">
|
||||
{#if filteredList?.length !== 0}
|
||||
<Chart {init} options={options} class="chart" />
|
||||
{:else}
|
||||
<span class="text-xl text-white m-auto flex justify-center items-center h-full">
|
||||
<div class="text-gray-100 text-sm sm:text-[1rem] sm:rounded-lg h-auto border border-slate-800 p-4">
|
||||
<svg class="w-5 h-5 inline-block sm:mr-2 flex-shrink-0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#a474f6" d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24m-4 48a12 12 0 1 1-12 12a12 12 0 0 1 12-12m12 112a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 0 16"/></svg>
|
||||
No Options activity found
|
||||
</div>
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<h3 class="text-2xl sm:text-3xl text-gray-200 font-bold mb-4 text-center sm:text-start">
|
||||
Latest Options Activity
|
||||
</h3>
|
||||
|
||||
|
||||
|
||||
{#if optionList?.length !== 0}
|
||||
<!--Start Widget-->
|
||||
|
||||
<div class="w-full mt-5 mb-10 m-auto flex justify-center items-center">
|
||||
<div class="w-full grid grid-cols-2 xl:grid-cols-3 gap-y-3 lg:gap-y-3 gap-x-3 ">
|
||||
<!--Start Flow Sentiment-->
|
||||
<div class="flex flex-row items-center flex-wrap w-full px-3 sm:px-5 bg-[#262626] shadow-lg rounded-md h-20">
|
||||
<div class="flex flex-col items-start">
|
||||
<span class="font-medium text-gray-200 text-[1rem] ">Flow Sentiment</span>
|
||||
<span class="text-start text-[1rem] font-medium {flowSentiment === 'Bullish' ? 'text-[#00FC50]' : 'text-[#FC2120]'}">{flowSentiment}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!--End Flow Sentiment-->
|
||||
<!--Start Put/Call-->
|
||||
<div class="flex flex-row items-center flex-wrap w-full px-3 sm:px-5 bg-[#262626] shadow-lg rounded-md h-20">
|
||||
<div class="flex flex-col items-start">
|
||||
<span class="font-medium text-gray-200 text-[1rem] ">Put/Call</span>
|
||||
<span class="text-start text-sm sm:text-[1rem] font-medium text-white">
|
||||
{latestPutCallRatio?.toFixed(3)}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Circular Progress -->
|
||||
<div class="relative size-14 ml-auto">
|
||||
<svg class="size-full w-14 h-14" 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-[#3E3E3E]" stroke-width="3"></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 text-blue-500" stroke-width="3" stroke-dasharray="100" stroke-dashoffset={latestPutCallRatio >=1 ? 0 : 100-(latestPutCallRatio*100)?.toFixed(2)}></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-sm">{latestPutCallRatio?.toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Circular Progress -->
|
||||
|
||||
</div>
|
||||
<!--End Put/Call-->
|
||||
<!--Start Call Flow-->
|
||||
<div class="flex flex-row items-center flex-wrap w-full px-3 sm:px-5 bg-[#262626] shadow-lg rounded-md h-20">
|
||||
<div class="flex flex-col items-start">
|
||||
<span class="font-medium text-gray-200 text-[1rem] ">Call Flow</span>
|
||||
<span class="text-start text-sm sm:text-[1rem] font-medium text-white">
|
||||
{new Intl.NumberFormat("en", {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
}).format(displayCallVolume)}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Circular Progress -->
|
||||
<div class="relative size-14 ml-auto">
|
||||
<svg class="size-full w-14 h-14" 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-[#3E3E3E]" stroke-width="3"></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 text-[#00FC50]" stroke-width="3" stroke-dasharray="100" stroke-dashoffset={100-callPercentage?.toFixed(2)}></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-sm">{callPercentage}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Circular Progress -->
|
||||
</div>
|
||||
<!--End Call Flow-->
|
||||
<!--Start Put Flow-->
|
||||
<div class="flex flex-row items-center flex-wrap w-full px-3 sm:px-5 bg-[#262626] shadow-lg rounded-md h-20">
|
||||
<div class="flex flex-col items-start">
|
||||
<span class="font-medium text-gray-200 text-[1rem] ">Put Flow</span>
|
||||
<span class="text-start text-sm sm:text-[1rem] font-medium text-white">
|
||||
{new Intl.NumberFormat("en", {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
}).format(displayPutVolume)}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Circular Progress -->
|
||||
<div class="relative size-14 ml-auto">
|
||||
<svg class="size-full w-14 h-14" 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-[#3E3E3E]" stroke-width="3"></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 text-[#EE5365]" stroke-width="3" stroke-dasharray="100" stroke-dashoffset={100-putPercentage?.toFixed(2)}></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-sm">{putPercentage}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Circular Progress -->
|
||||
|
||||
</div>
|
||||
<!--End Put Flow-->
|
||||
|
||||
<!--Start Put/Call-->
|
||||
<div class="flex flex-row items-center flex-wrap w-full px-3 sm:px-5 bg-[#262626] shadow-lg rounded-md h-20">
|
||||
<div class="flex flex-col items-start">
|
||||
<span class="font-medium text-gray-200 text-[1rem] ">OTM Ratio</span>
|
||||
<span class="text-start text-sm sm:text-[1rem] font-medium text-white">
|
||||
Volume in %
|
||||
</span>
|
||||
</div>
|
||||
<!-- Circular Progress -->
|
||||
<div class="relative size-14 ml-auto">
|
||||
<svg class="size-full w-14 h-14" 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-[#3E3E3E]" stroke-width="3"></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 text-[#EE5365]" stroke-width="3" stroke-dasharray="100" stroke-dashoffset={displayOTMRatio >=1 ? 0 : 100-(displayOTMRatio*100)?.toFixed(2)}></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-sm">{(displayOTMRatio*100)?.toFixed(0)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Circular Progress -->
|
||||
|
||||
</div>
|
||||
<!--End Put/Call-->
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!--End Widget-->
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="flex justify-start items-center m-auto overflow-x-auto">
|
||||
|
||||
|
||||
<table class="table table-pin-cols table-sm table-compact rounded-none sm:rounded-md w-full border-bg-[#09090B] m-auto mt-4 overflow-x-auto">
|
||||
<thead>
|
||||
<tr class="">
|
||||
<td class="text-slate-200 font-semibold text-sm text-start">Time</td>
|
||||
<td class="text-slate-200 font-semibold text-sm text-start">Date</td>
|
||||
<td class="text-slate-200 font-semibold text-sm text-end">Expiry</td>
|
||||
<td class="text-slate-200 font-semibold text-sm text-end">Strike</td>
|
||||
<td class="text-slate-200 font-semibold text-sm text-end">C/P</td>
|
||||
<td class="text-slate-200 font-semibold text-sm text-start">Sent.</td>
|
||||
<td class="text-slate-200 font-semibold text-sm text-end">Spot</td>
|
||||
<td class="text-slate-200 font-semibold text-sm text-end">Price</td>
|
||||
<td class="text-slate-200 font-semibold text-sm text-end">Prem.</td>
|
||||
<td class="text-slate-200 font-semibold text-sm text-start">Type</td>
|
||||
<td class="text-slate-200 font-semibold text-sm text-end">Vol.</td>
|
||||
<td class="text-slate-200 font-semibold text-sm text-end">OI</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each (data?.user?.tier === 'Pro' ? optionList : optionList?.slice(0,3)) as item, index}
|
||||
<!-- row -->
|
||||
<tr class="odd:bg-[#27272A] border-b-[#09090B] {index+1 === optionList?.slice(0,3)?.length && data?.user?.tier !== 'Pro' ? 'opacity-[0.1]' : ''}">
|
||||
|
||||
<td class="text-white text-sm text-start whitespace-nowrap">
|
||||
{formatTime(item?.time)}
|
||||
</td>
|
||||
|
||||
<td class="text-white text-sm sm:text-[1rem] text-start">
|
||||
{formatDate(item?.date)}
|
||||
</td>
|
||||
|
||||
<td class="text-white text-sm sm:text-[1rem] text-end">
|
||||
{item?.dte < 0 ? 'expired' : item?.dte +'d'}
|
||||
</td>
|
||||
|
||||
<td class="text-sm sm:text-[1rem] text-end text-white">
|
||||
{item?.strike_price}
|
||||
</td>
|
||||
|
||||
<td class="text-sm sm:text-[1rem] {item?.put_call === 'Calls' ? 'text-[#00FC50]' : 'text-[#FC2120]'} text-start">
|
||||
{item?.put_call}
|
||||
</td>
|
||||
|
||||
<td class="text-sm sm:text-[1rem] {item?.sentiment === 'Bullish' ? 'text-[#00FC50]' : item?.sentiment === 'Bearish' ? 'text-[#FC2120]' : 'text-[#C6A755]'} text-start">
|
||||
{item?.sentiment}
|
||||
</td>
|
||||
|
||||
<td class="text-sm sm:text-[1rem] text-end text-white">
|
||||
{item?.underlying_price}
|
||||
</td>
|
||||
|
||||
<td class="text-sm sm:text-[1rem] text-end text-white">
|
||||
{item?.price}
|
||||
</td>
|
||||
|
||||
<td class="text-sm sm:text-[1rem] text-end font-medium {item?.put_call === 'Puts' ? 'text-[#CB281C]' : 'text-[#0FB307]'} ">
|
||||
{abbreviateNumber(item?.cost_basis)}
|
||||
</td>
|
||||
|
||||
<td class="text-sm sm:text-[1rem] text-start {item?.type === 'Sweep' ? 'text-[#C6A755]' : 'text-[#976DB7]'}">
|
||||
{item?.type}
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
<td class="text-white text-end">
|
||||
{new Intl.NumberFormat("en", {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
}).format(item?.volume)}
|
||||
</td>
|
||||
|
||||
<td class="text-white text-end">
|
||||
{new Intl.NumberFormat("en", {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0
|
||||
}).format(item?.open_interest)}
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
|
||||
{/each}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<UpgradeToPro data={data} title="Get the recent Options Flow Data from Hedge Funds and major institutional traders to never miss out"/>
|
||||
|
||||
{:else}
|
||||
<div class="flex justify-center items-center m-auto mt-16 mb-6">
|
||||
<div class="text-gray-100 text-sm sm:text-[1rem] rounded-lg h-auto border border-slate-800 p-4">
|
||||
<svg class="w-5 h-5 inline-block sm:mr-2 flex-shrink-0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="#a474f6" d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24m-4 48a12 12 0 1 1-12 12a12 12 0 0 1 12-12m12 112a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 0 16"/></svg>
|
||||
No Options activity found
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{/if}
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
.app {
|
||||
height: 420px;
|
||||
max-width: 1500px;
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.app {
|
||||
max-width: 520px;
|
||||
height: 420px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
89
src/routes/stocks/[tickerID]/ai-analysis/+page.ts
Normal file
89
src/routes/stocks/[tickerID]/ai-analysis/+page.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { getCache, setCache } from '$lib/store';
|
||||
|
||||
|
||||
function daysLeft(targetDate) {
|
||||
const targetTime = new Date(targetDate).getTime();
|
||||
const currentTime = new Date().getTime();
|
||||
const difference = targetTime - currentTime;
|
||||
|
||||
const millisecondsPerDay = 1000 * 60 * 60 * 24;
|
||||
const daysLeft = Math?.ceil(difference / millisecondsPerDay);
|
||||
|
||||
return daysLeft;
|
||||
}
|
||||
|
||||
|
||||
export const load = async ({ parent, params }) => {
|
||||
|
||||
|
||||
const {apiKey, apiURL} = await parent();
|
||||
|
||||
|
||||
const getOptionsPlotData = async () => {
|
||||
|
||||
const cachedData = getCache(params.tickerID, 'getOptionsPlotData');
|
||||
if (cachedData) {
|
||||
return cachedData;
|
||||
} else {
|
||||
|
||||
const postData = {
|
||||
ticker: params.tickerID
|
||||
};
|
||||
|
||||
const response = await fetch(apiURL + '/options-plot-ticker', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "application/json", "X-API-KEY": apiKey
|
||||
},
|
||||
body: JSON.stringify(postData)
|
||||
});
|
||||
|
||||
const output = await response.json();
|
||||
|
||||
setCache(params.tickerID, output, 'getOptionsPlotData');
|
||||
return output;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const getOptionsFlowData = async () => {
|
||||
|
||||
let output;
|
||||
const cachedData = getCache(params.tickerID, 'getOptionsFlowData');
|
||||
if (cachedData) {
|
||||
output = cachedData;
|
||||
} else {
|
||||
|
||||
const postData = {
|
||||
ticker: params.tickerID
|
||||
};
|
||||
|
||||
// make the POST request to the endpoint
|
||||
const response = await fetch(apiURL + '/options-flow-ticker', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "application/json", "X-API-KEY": apiKey
|
||||
},
|
||||
body: JSON.stringify(postData)
|
||||
});
|
||||
|
||||
output = await response.json();
|
||||
|
||||
output?.forEach(item => {
|
||||
item.dte = daysLeft(item?.date_expiration);
|
||||
});
|
||||
|
||||
setCache(params.tickerID, output, 'getOptionsFlowData');
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
// Make sure to return a promise
|
||||
return {
|
||||
getOptionsPlotData: await getOptionsPlotData(),
|
||||
getOptionsFlowData: await getOptionsFlowData()
|
||||
};
|
||||
|
||||
};
|
||||
@ -1,121 +1,132 @@
|
||||
<script lang='ts'>
|
||||
import { stockTicker, screenWidth } from "$lib/store";
|
||||
import { page } from "$app/stores";
|
||||
<script lang="ts">
|
||||
import { stockTicker, screenWidth } from "$lib/store";
|
||||
import { page } from "$app/stores";
|
||||
|
||||
let displaySubSection = 'fundamental';
|
||||
let displaySubSection = "overview";
|
||||
|
||||
if (displaySubSection) {
|
||||
const parts = $page?.url?.pathname.split("/");
|
||||
const sectionMap = {
|
||||
"market-cap": "market-cap",
|
||||
employees: "employees",
|
||||
income: "income",
|
||||
"balance-sheet": "balance-sheet",
|
||||
"cash-flow": "cash-flow",
|
||||
ratios: "ratios",
|
||||
};
|
||||
|
||||
const foundSection = parts?.find((part) => Object?.values(sectionMap)?.includes(part));
|
||||
|
||||
|
||||
if (displaySubSection) {
|
||||
const parts = $page?.url?.pathname.split('/');
|
||||
const sectionMap = {
|
||||
'market-cap': 'market-cap',
|
||||
'employees': 'employees',
|
||||
'income': 'income',
|
||||
'balance-sheet': 'balance-sheet',
|
||||
'cash-flow': 'cash-flow',
|
||||
'ratios': 'ratios',
|
||||
};
|
||||
|
||||
const foundSection = parts?.find(part => Object?.values(sectionMap)?.includes(part));
|
||||
|
||||
displaySubSection = Object?.keys(sectionMap)?.find(key => sectionMap[key] === foundSection) || 'fundamental';
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function changeSubSection(state) {
|
||||
const subSectionMap = {
|
||||
'market-cap': '/stats/market-cap',
|
||||
'employees': '/stats/employees',
|
||||
'income': '/stats/income',
|
||||
'balance-sheet': '/stats/balance-sheet',
|
||||
'cash-flow': '/stats/cash-flow',
|
||||
'ratios': '/stats/ratios',
|
||||
};
|
||||
|
||||
if (state !== 'fundamental' && subSectionMap[state]) {
|
||||
displaySubSection = state;
|
||||
//goto(`/stocks/${$stockTicker}${subSectionMap[state]}`);
|
||||
} else {
|
||||
displaySubSection = state;
|
||||
//goto(`/stocks/${$stockTicker}/stats`);
|
||||
displaySubSection = Object?.keys(sectionMap)?.find((key) => sectionMap[key] === foundSection) || "overview";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function changeSubSection(state) {
|
||||
const subSectionMap = {
|
||||
"market-cap": "/stats/market-cap",
|
||||
employees: "/stats/employees",
|
||||
income: "/stats/income",
|
||||
"balance-sheet": "/stats/balance-sheet",
|
||||
"cash-flow": "/stats/cash-flow",
|
||||
ratios: "/stats/ratios",
|
||||
};
|
||||
|
||||
if (state !== "overview" && subSectionMap[state]) {
|
||||
displaySubSection = state;
|
||||
//goto(`/stocks/${$stockTicker}${subSectionMap[state]}`);
|
||||
} else {
|
||||
displaySubSection = state;
|
||||
//goto(`/stocks/${$stockTicker}/stats`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="w-auto max-w-5xl bg-[#09090B] overflow-hidden text-black h-full mb-40">
|
||||
<div class="m-auto h-full overflow-hidden">
|
||||
<main class="w-fit sm:w-full sm:max-w-2xl">
|
||||
<div class="m-auto">
|
||||
|
||||
|
||||
<div class="-ml-2 sm:ml-8 w-screen sm:w-full {$screenWidth < 640 ? 'overflow-auto scrollbar' : 'no-scrollbar'} mb-2" >
|
||||
<ul class="pr-4 sm:pr-0 w-screen flex flex-row items-center bg-[#09090B] overflow-x-scroll sm:overflow-hidden space-x-4 rtl:space-x-reverse py-2">
|
||||
<li class="cursor-pointer flex flex-col items-center">
|
||||
<a href={`/stocks/${$stockTicker}/stats`} on:click={() => (changeSubSection('fundamental'))} class="px-2 text-sm sm:text-[1rem] font-medium text-gray-400 sm:hover:text-white {displaySubSection === 'fundamental' ? 'text-white ' : 'bg-[#09090B]'}" >
|
||||
Fundamental
|
||||
</a>
|
||||
<div class="{displaySubSection === 'fundamental' ? 'bg-[#75D377]' : 'bg-[#09090B]'} mt-1 h-[3px] rounded-full w-[4rem]" />
|
||||
</li>
|
||||
<li class="cursor-pointer flex flex-col items-center">
|
||||
<a href={`/stocks/${$stockTicker}/stats/market-cap`} on:click={() => (changeSubSection('market-cap'))} class="whitespace-nowrap px-2 text-sm sm:text-[1rem] font-medium text-gray-400 sm:hover:text-white {displaySubSection === 'market-cap' ? 'text-white ' : 'bg-[#09090B]'}" >
|
||||
Market Cap
|
||||
</a>
|
||||
<div class="{displaySubSection === 'market-cap' ? 'bg-[#75D377]' : 'bg-[#09090B]'} mt-1 h-[3px] rounded-full w-[3.5rem]" />
|
||||
</li>
|
||||
<li class="cursor-pointer flex flex-col items-center">
|
||||
<a href={`/stocks/${$stockTicker}/stats/employees`} on:click={() => (changeSubSection('employees'))} class="px-2 text-sm sm:text-[1rem] font-medium text-gray-400 sm:hover:text-white {displaySubSection === 'employees' ? 'text-white ' : 'bg-[#09090B]'}" >
|
||||
Employees
|
||||
</a>
|
||||
<div class="{displaySubSection === 'employees' ? 'bg-[#75D377]' : 'bg-[#09090B]'} mt-1 h-[3px] rounded-full w-[3.5rem]" />
|
||||
</li>
|
||||
<li class="cursor-pointer flex flex-col items-center">
|
||||
<a href={`/stocks/${$stockTicker}/stats/income`} on:click={() => (changeSubSection('income'))} class="px-2 text-sm sm:text-[1rem] font-medium text-gray-400 sm:hover:text-white {displaySubSection === 'income' ? 'text-white ' : 'bg-[#09090B]'}" >
|
||||
Income
|
||||
</a>
|
||||
<div class="{displaySubSection === 'income' ? 'bg-[#75D377]' : 'bg-[#09090B]'} mt-1 h-[3px] rounded-full w-[2.5rem]" />
|
||||
</li>
|
||||
<li class="cursor-pointer flex flex-col items-center">
|
||||
<a href={`/stocks/${$stockTicker}/stats/balance-sheet`} on:click={() => (changeSubSection('balance-sheet'))} class="px-2 text-sm sm:text-[1rem] font-medium text-gray-400 sm:hover:text-white {displaySubSection === 'balance-sheet' ? 'text-white ' : 'bg-[#09090B]'}" >
|
||||
Balance
|
||||
</a>
|
||||
<div class="{displaySubSection === 'balance-sheet' ? 'bg-[#75D377]' : 'bg-[#09090B]'} mt-1 h-[3px] rounded-full w-[2.5rem]" />
|
||||
</li>
|
||||
<li class="cursor-pointer flex flex-col items-center">
|
||||
<a href={`/stocks/${$stockTicker}/stats/cash-flow`} on:click={() => (changeSubSection('cash-flow'))} class="px-2 text-sm sm:text-[1rem] font-medium text-gray-400 sm:hover:text-white {displaySubSection === 'cash-flow' ? 'text-white ' : 'bg-[#09090B]'}" >
|
||||
Cashflow
|
||||
</a>
|
||||
<div class="{displaySubSection === 'cash-flow' ? 'bg-[#75D377]' : 'bg-[#09090B]'} mt-1 h-[3px] rounded-full w-[2.5rem]" />
|
||||
</li>
|
||||
<li class="cursor-pointer flex flex-col items-center">
|
||||
<a href={`/stocks/${$stockTicker}/stats/ratios`} on:click={() => (changeSubSection('ratios'))} class="px-2 text-sm sm:text-[1rem] font-medium text-gray-400 sm:hover:text-white {displaySubSection === 'ratios' ? 'text-white ' : 'bg-[#09090B]'}" >
|
||||
Ratios
|
||||
</a>
|
||||
<div class="{displaySubSection === 'ratios' ? 'bg-[#75D377]' : 'bg-[#09090B]'} mt-1 h-[3px] rounded-full w-[2rem]" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<slot/>
|
||||
<main class="w-fit sm:w-full sm:max-w-2xl">
|
||||
<div class="m-auto">
|
||||
<div class="-ml-2 sm:ml-8 w-screen sm:w-full {$screenWidth < 640 ? 'overflow-auto scrollbar' : 'no-scrollbar'} mb-2">
|
||||
<ul class="pr-4 sm:pr-0 w-screen flex flex-row items-center bg-[#09090B] overflow-x-scroll sm:overflow-hidden space-x-4 rtl:space-x-reverse py-2">
|
||||
<li class="cursor-pointer flex flex-col items-center">
|
||||
<a
|
||||
href={`/stocks/${$stockTicker}/stats`}
|
||||
on:click={() => changeSubSection("overview")}
|
||||
class="px-2 text-sm sm:text-[1rem] font-medium text-gray-400 sm:hover:text-white {displaySubSection === 'overview' ? 'text-white ' : 'bg-[#09090B]'}"
|
||||
>
|
||||
Overview
|
||||
</a>
|
||||
<div class="{displaySubSection === 'overview' ? 'bg-[#75D377]' : 'bg-[#09090B]'} mt-1 h-[3px] rounded-full w-[4rem]" />
|
||||
</li>
|
||||
<li class="cursor-pointer flex flex-col items-center">
|
||||
<a
|
||||
href={`/stocks/${$stockTicker}/stats/ratios`}
|
||||
on:click={() => changeSubSection("ratios")}
|
||||
class="px-2 text-sm sm:text-[1rem] font-medium text-gray-400 sm:hover:text-white {displaySubSection === 'ratios' ? 'text-white ' : 'bg-[#09090B]'}"
|
||||
>
|
||||
Ratios
|
||||
</a>
|
||||
<div class="{displaySubSection === 'ratios' ? 'bg-[#75D377]' : 'bg-[#09090B]'} mt-1 h-[3px] rounded-full w-[2rem]" />
|
||||
</li>
|
||||
<li class="cursor-pointer flex flex-col items-center">
|
||||
<a
|
||||
href={`/stocks/${$stockTicker}/stats/income`}
|
||||
on:click={() => changeSubSection("income")}
|
||||
class="px-2 text-sm sm:text-[1rem] font-medium text-gray-400 sm:hover:text-white {displaySubSection === 'income' ? 'text-white ' : 'bg-[#09090B]'}"
|
||||
>
|
||||
Income
|
||||
</a>
|
||||
<div class="{displaySubSection === 'income' ? 'bg-[#75D377]' : 'bg-[#09090B]'} mt-1 h-[3px] rounded-full w-[2.5rem]" />
|
||||
</li>
|
||||
<li class="cursor-pointer flex flex-col items-center">
|
||||
<a
|
||||
href={`/stocks/${$stockTicker}/stats/cash-flow`}
|
||||
on:click={() => changeSubSection("cash-flow")}
|
||||
class="px-2 text-sm sm:text-[1rem] font-medium text-gray-400 sm:hover:text-white {displaySubSection === 'cash-flow' ? 'text-white ' : 'bg-[#09090B]'}"
|
||||
>
|
||||
Cashflow
|
||||
</a>
|
||||
<div class="{displaySubSection === 'cash-flow' ? 'bg-[#75D377]' : 'bg-[#09090B]'} mt-1 h-[3px] rounded-full w-[2.5rem]" />
|
||||
</li>
|
||||
<li class="cursor-pointer flex flex-col items-center">
|
||||
<a
|
||||
href={`/stocks/${$stockTicker}/stats/balance-sheet`}
|
||||
on:click={() => changeSubSection("balance-sheet")}
|
||||
class="px-2 text-sm sm:text-[1rem] font-medium text-gray-400 sm:hover:text-white {displaySubSection === 'balance-sheet' ? 'text-white ' : 'bg-[#09090B]'}"
|
||||
>
|
||||
Balance
|
||||
</a>
|
||||
<div class="{displaySubSection === 'balance-sheet' ? 'bg-[#75D377]' : 'bg-[#09090B]'} mt-1 h-[3px] rounded-full w-[2.5rem]" />
|
||||
</li>
|
||||
<li class="cursor-pointer flex flex-col items-center">
|
||||
<a
|
||||
href={`/stocks/${$stockTicker}/stats/market-cap`}
|
||||
on:click={() => changeSubSection("market-cap")}
|
||||
class="whitespace-nowrap px-2 text-sm sm:text-[1rem] font-medium text-gray-400 sm:hover:text-white {displaySubSection === 'market-cap' ? 'text-white ' : 'bg-[#09090B]'}"
|
||||
>
|
||||
Market Cap
|
||||
</a>
|
||||
<div class="{displaySubSection === 'market-cap' ? 'bg-[#75D377]' : 'bg-[#09090B]'} mt-1 h-[3px] rounded-full w-[3.5rem]" />
|
||||
</li>
|
||||
<li class="cursor-pointer flex flex-col items-center">
|
||||
<a
|
||||
href={`/stocks/${$stockTicker}/stats/employees`}
|
||||
on:click={() => changeSubSection("employees")}
|
||||
class="px-2 text-sm sm:text-[1rem] font-medium text-gray-400 sm:hover:text-white {displaySubSection === 'employees' ? 'text-white ' : 'bg-[#09090B]'}"
|
||||
>
|
||||
Employees
|
||||
</a>
|
||||
<div class="{displaySubSection === 'employees' ? 'bg-[#75D377]' : 'bg-[#09090B]'} mt-1 h-[3px] rounded-full w-[3.5rem]" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
.scrollbar {
|
||||
.scrollbar {
|
||||
display: grid;
|
||||
grid-gap: 18px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
|
||||
@ -123,15 +134,15 @@ function changeSubSection(state) {
|
||||
overflow-x: auto;
|
||||
scrollbar-width: thin; /* Hide the default scrollbar in Firefox */
|
||||
scrollbar-color: transparent transparent; /* Hide the default scrollbar in Firefox */
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom scrollbar for Webkit (Chrome, Safari) */
|
||||
.scrollbar::-webkit-scrollbar {
|
||||
/* Custom scrollbar for Webkit (Chrome, Safari) */
|
||||
.scrollbar::-webkit-scrollbar {
|
||||
width: 0; /* Hide the width of the scrollbar */
|
||||
height: 0; /* Hide the height of the scrollbar */
|
||||
}
|
||||
}
|
||||
|
||||
.scrollbar::-webkit-scrollbar-thumb {
|
||||
.scrollbar::-webkit-scrollbar-thumb {
|
||||
background: transparent; /* Make the thumb transparent */
|
||||
}
|
||||
</style>
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user