clean code
This commit is contained in:
parent
f8161d5cd8
commit
fbab3bb01e
327
package-lock.json
generated
327
package-lock.json
generated
@ -38,15 +38,11 @@
|
||||
"date-fns": "^3.6.0",
|
||||
"date-fns-tz": "^3.1.3",
|
||||
"date-picker-svelte": "^2.12.0",
|
||||
"echarts": "^5.5.1",
|
||||
"file-saver": "^2.0.5",
|
||||
"flowbite-svelte": "^0.46.15",
|
||||
"got": "^14.4.2",
|
||||
"html2canvas": "^1.4.1",
|
||||
"html2canvas-pro": "^1.5.8",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"katex": "^0.16.11",
|
||||
"lightweight-charts": "^4.1.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-svelte": "^0.438.0",
|
||||
"luxon": "^3.5.0",
|
||||
@ -58,8 +54,6 @@
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-svelte": "^3.2.7",
|
||||
"quill": "^2.0.2",
|
||||
"quill-delta-to-html": "^0.12.1",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"rollup-plugin-web-worker-loader": "^1.6.1",
|
||||
"sass": "^1.75.0",
|
||||
@ -67,10 +61,8 @@
|
||||
"string-similarity": "^4.0.4",
|
||||
"svelte": "^4.2.15",
|
||||
"svelte-check": "^3.6.9",
|
||||
"svelte-echarts": "^1.0.0-rc3",
|
||||
"svelte-inview": "^4.0.2",
|
||||
"svelte-lazy": "^1.2.11",
|
||||
"svelte-lightweight-charts": "^2.2.0",
|
||||
"svelte-loading-spinners": "^0.3.6",
|
||||
"svelte-preprocess": "^5.1.4",
|
||||
"svelte-sonner": "^0.3.27",
|
||||
@ -1280,16 +1272,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-commonjs": {
|
||||
"version": "28.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz",
|
||||
@ -2822,12 +2804,6 @@
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@yr/monotone-cubic-spline": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
|
||||
"integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/abs-svg-path": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz",
|
||||
@ -2989,21 +2965,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/apexcharts": {
|
||||
"version": "3.53.0",
|
||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.53.0.tgz",
|
||||
"integrity": "sha512-QESZHZY3w9LPQ64PGh1gEdfjYjJ5Jp+Dfy0D/CLjsLOPTpXzdxwlNMqRj+vPbTcP0nAHgjWv1maDqcEq6u5olw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@yr/monotone-cubic-spline": "^1.0.3",
|
||||
"svg.draggable.js": "^2.2.2",
|
||||
"svg.easing.js": "^2.0.0",
|
||||
"svg.filter.js": "^2.0.2",
|
||||
"svg.pathmorphing.js": "^0.1.3",
|
||||
"svg.resize.js": "^1.4.3",
|
||||
"svg.select.js": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/aria-query": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
|
||||
@ -4331,22 +4292,6 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/echarts": {
|
||||
"version": "5.5.1",
|
||||
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.5.1.tgz",
|
||||
"integrity": "sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tslib": "2.3.0",
|
||||
"zrender": "5.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/echarts/node_modules/tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.79",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.79.tgz",
|
||||
@ -4688,12 +4633,6 @@
|
||||
"es5-ext": "~0.10.14"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||
@ -4772,12 +4711,6 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fancy-canvas": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fancy-canvas/-/fancy-canvas-2.1.0.tgz",
|
||||
"integrity": "sha512-nifxXJ95JNLFR2NgRV4/MxVP45G9909wJTEKz5fg/TZS20JJZA6hfgRVh/bC9bwl2zBtBNcYPjiBE4njQHVBwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@ -4785,12 +4718,6 @@
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/fast-diff": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
|
||||
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||
@ -4876,46 +4803,6 @@
|
||||
"dtype": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/flowbite": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.5.1.tgz",
|
||||
"integrity": "sha512-7jP1jy9c3QP7y+KU9lc8ueMkTyUdMDvRP+lteSWgY5TigSZjf9K1kqZxmqjhbx2gBnFQxMl1GAjVThCa8cEpKA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.9.3",
|
||||
"flowbite-datepicker": "^1.3.0",
|
||||
"mini-svg-data-uri": "^1.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/flowbite-datepicker": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.3.0.tgz",
|
||||
"integrity": "sha512-CLVqzuoE2vkUvWYK/lJ6GzT0be5dlTbH3uuhVwyB67+PjqJWABm2wv68xhBf5BqjpBxvTSQ3mrmLHpPJ2tvrSQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"flowbite": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/flowbite-svelte": {
|
||||
"version": "0.46.15",
|
||||
"resolved": "https://registry.npmjs.org/flowbite-svelte/-/flowbite-svelte-0.46.15.tgz",
|
||||
"integrity": "sha512-xWhyLDez/gafTAmQayPMPKmWj1BAoq80SoA48yHZ12Wk3Vu3hFrELLUZf0UksxnjQL9hMOKRUlYl3/IH6pHwnQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.6.7",
|
||||
"apexcharts": "^3.49.2",
|
||||
"flowbite": "^2.4.1",
|
||||
"tailwind-merge": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"pnpm": ">=8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^3.55.1 || ^4.0.0 || ^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/focus-trap": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz",
|
||||
@ -5578,20 +5465,6 @@
|
||||
"integrity": "sha512-08iL2VyCRbkQKBySkSh6m8zMUa3sADAxGVWs3Z1aPcUkTJeK0ETG4Fc27tEmQBGUAXZjIsXOZqBvacuVNSC/fQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/html2canvas": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-line-break": "^2.1.0",
|
||||
"text-segmentation": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html2canvas-pro": {
|
||||
"version": "1.5.8",
|
||||
"resolved": "https://registry.npmjs.org/html2canvas-pro/-/html2canvas-pro-1.5.8.tgz",
|
||||
@ -6455,15 +6328,6 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightweight-charts": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lightweight-charts/-/lightweight-charts-4.1.3.tgz",
|
||||
"integrity": "sha512-SJacmEyx3LmT2Qsc7Kq7cEX7nEHtQv0MOlujhRlcDxhW62pG6nkBlcM52/jNqkq8B28KQeVmgOQ7zrdJ4BCPDw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fancy-canvas": "2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
@ -6515,12 +6379,6 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
},
|
||||
"node_modules/lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
@ -6533,12 +6391,6 @@
|
||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.isinteger": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
@ -6875,15 +6727,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/mini-svg-data-uri": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
||||
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mini-svg-data-uri": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/minimalistic-assert": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
@ -7250,12 +7093,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/parchment": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz",
|
||||
"integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/parenthesis": {
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/parenthesis/-/parenthesis-3.1.8.tgz",
|
||||
@ -7830,44 +7667,6 @@
|
||||
"integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/quill": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.2.tgz",
|
||||
"integrity": "sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eventemitter3": "^5.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"parchment": "^3.0.0",
|
||||
"quill-delta": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=8.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/quill-delta": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz",
|
||||
"integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-diff": "^1.3.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.isequal": "^4.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/quill-delta-to-html": {
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/quill-delta-to-html/-/quill-delta-to-html-0.12.1.tgz",
|
||||
"integrity": "sha512-QhpeMk9+5ge3HYbL5A0Ewz3pXCsbemqGvIF/kw5D6D4V68AtcUp7yt9xNUkzOk/0IQz43hKy3IkzBzRhLIE+oA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lodash.isequal": "^4.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/raf": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
||||
@ -8693,16 +8492,6 @@
|
||||
"svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-echarts": {
|
||||
"version": "1.0.0-rc3",
|
||||
"resolved": "https://registry.npmjs.org/svelte-echarts/-/svelte-echarts-1.0.0-rc3.tgz",
|
||||
"integrity": "sha512-jM9EU4m7yFJaISCXhnGgtYirMD1VP2DzoI3Lhbl2jsvGzZFIAg8wsn/5n8GccZxm7wHgzcEL48EVJVlrKPU7gQ==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"echarts": "^5.0.0",
|
||||
"svelte": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-hmr": {
|
||||
"version": "0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz",
|
||||
@ -8734,16 +8523,6 @@
|
||||
"svelte": "^3.0.0 || ^4.0.0 || ^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-lightweight-charts": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-lightweight-charts/-/svelte-lightweight-charts-2.2.0.tgz",
|
||||
"integrity": "sha512-LXdha4vfLMuOPc0Yyetu+DLSDJkPryGkufUQgpCkfguCscSQUcrLiI9MdqKwQk6Fkm6AZbg8SQ5qDIYxcyC+Dg==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"lightweight-charts": ">=4.0.0",
|
||||
"svelte": ">=3.44.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-loading-spinners": {
|
||||
"version": "0.3.6",
|
||||
"resolved": "https://registry.npmjs.org/svelte-loading-spinners/-/svelte-loading-spinners-0.3.6.tgz",
|
||||
@ -8891,97 +8670,6 @@
|
||||
"svg-path-bounds": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.draggable.js": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
|
||||
"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.easing.js": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
|
||||
"integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": ">=2.3.x"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.filter.js": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
|
||||
"integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.js": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
|
||||
"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/svg.pathmorphing.js": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
|
||||
"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.resize.js": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
|
||||
"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.6.5",
|
||||
"svg.select.js": "^2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.resize.js/node_modules/svg.select.js": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
|
||||
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.select.js": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
|
||||
"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"svg.js": "^2.6.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tabbable": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
|
||||
@ -10025,21 +9713,6 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/zrender": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.0.tgz",
|
||||
"integrity": "sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tslib": "2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/zrender/node_modules/tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,15 +38,11 @@
|
||||
"date-fns": "^3.6.0",
|
||||
"date-fns-tz": "^3.1.3",
|
||||
"date-picker-svelte": "^2.12.0",
|
||||
"echarts": "^5.5.1",
|
||||
"file-saver": "^2.0.5",
|
||||
"flowbite-svelte": "^0.46.15",
|
||||
"got": "^14.4.2",
|
||||
"html2canvas": "^1.4.1",
|
||||
"html2canvas-pro": "^1.5.8",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"katex": "^0.16.11",
|
||||
"lightweight-charts": "^4.1.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-svelte": "^0.438.0",
|
||||
"luxon": "^3.5.0",
|
||||
@ -58,8 +54,6 @@
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-svelte": "^3.2.7",
|
||||
"quill": "^2.0.2",
|
||||
"quill-delta-to-html": "^0.12.1",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"rollup-plugin-web-worker-loader": "^1.6.1",
|
||||
"sass": "^1.75.0",
|
||||
@ -67,10 +61,8 @@
|
||||
"string-similarity": "^4.0.4",
|
||||
"svelte": "^4.2.15",
|
||||
"svelte-check": "^3.6.9",
|
||||
"svelte-echarts": "^1.0.0-rc3",
|
||||
"svelte-inview": "^4.0.2",
|
||||
"svelte-lazy": "^1.2.11",
|
||||
"svelte-lightweight-charts": "^2.2.0",
|
||||
"svelte-loading-spinners": "^0.3.6",
|
||||
"svelte-preprocess": "^5.1.4",
|
||||
"svelte-sonner": "^0.3.27",
|
||||
|
||||
@ -1,411 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Chart } from "svelte-echarts";
|
||||
import InfoModal from "$lib/components/InfoModal.svelte";
|
||||
import {
|
||||
dcfComponent,
|
||||
stockTicker,
|
||||
screenWidth,
|
||||
getCache,
|
||||
setCache,
|
||||
} from "$lib/store";
|
||||
import Lazy from "svelte-lazy";
|
||||
|
||||
//export let quantData;
|
||||
export let data;
|
||||
|
||||
let fairPrice;
|
||||
let isLoaded = false;
|
||||
let lastPrice: Number;
|
||||
|
||||
let optionsBarChart;
|
||||
|
||||
let change: Number;
|
||||
|
||||
const contentModal = `<span class="text-white">
|
||||
Discounted Cash Flow (DCF) is a core method for valuing a company's true worth. It starts by predicting the company's growth and how it affects its future cash flow.
|
||||
<br>
|
||||
<br>
|
||||
Then, it adjusts these future cash flows to their present value using a discount rate. This considers the risk associated with future cash flow predictions. Simply put, higher discount rates signal greater risk.
|
||||
</span>`;
|
||||
|
||||
const plotBarChart = () => {
|
||||
const options = {
|
||||
grid: {
|
||||
left: "0%",
|
||||
right: "0%",
|
||||
top: "0%",
|
||||
bottom: "0%",
|
||||
containLabel: true,
|
||||
},
|
||||
animation: false,
|
||||
silent: true,
|
||||
xAxis: {
|
||||
type: "value",
|
||||
axisLabel: {
|
||||
show: false, // Hide the x-axis labels
|
||||
},
|
||||
axisLine: {
|
||||
show: false, // Hide the y-axis lines
|
||||
},
|
||||
splitLine: {
|
||||
show: false, // Hide the grid lines on the y-axis
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: "category",
|
||||
axisLabel: {
|
||||
show: false, // Hide the x-axis labels
|
||||
},
|
||||
axisLine: {
|
||||
show: true, // Hide the y-axis lines
|
||||
},
|
||||
splitLine: {
|
||||
show: false, // Hide the grid lines on the y-axis
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "Current Price",
|
||||
type: "bar",
|
||||
barWidth: $screenWidth < 640 ? "30%" : "30%",
|
||||
smooth: true,
|
||||
stack: change < 0 ? "lastPriceStack" : "",
|
||||
data: [lastPrice],
|
||||
label: {
|
||||
show: true,
|
||||
position: "inside",
|
||||
formatter: function (params) {
|
||||
return [
|
||||
"{a|Current Price}",
|
||||
"{b|" + "$" + params.value + "}",
|
||||
].join("\n");
|
||||
},
|
||||
rich: {
|
||||
a: {
|
||||
color: "white",
|
||||
fontSize: $screenWidth < 640 ? 15 : 20,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
b: {
|
||||
color: "white",
|
||||
fontSize: $screenWidth < 640 ? 24 : 30,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
},
|
||||
markLine: {
|
||||
symbol: "none",
|
||||
label: {
|
||||
position: "middle",
|
||||
formatter: "{b}",
|
||||
},
|
||||
lineStyle: {
|
||||
color: "white",
|
||||
fontWeight: "bold", // Make the mark line bold
|
||||
type: "dashed",
|
||||
width: 2, // Increase the dashed line width
|
||||
},
|
||||
data: [{ type: "average", name: "" }],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Fair Price",
|
||||
type: "bar",
|
||||
barWidth: $screenWidth < 640 ? "30%" : "30%",
|
||||
smooth: true,
|
||||
stack: change > 0 ? "fairPriceStack" : "",
|
||||
data: [fairPrice],
|
||||
itemStyle: {
|
||||
color: "#2DC97E",
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: lastPrice > fairPrice ? "outside" : "inside",
|
||||
formatter: function (params) {
|
||||
return ["{a|Fair Price}", "{b|" + "$" + params.value + "}"].join(
|
||||
"\n",
|
||||
);
|
||||
},
|
||||
rich: {
|
||||
a: {
|
||||
color: "white",
|
||||
fontSize: $screenWidth < 640 ? 15 : 20,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
b: {
|
||||
color: "white",
|
||||
fontSize: $screenWidth < 640 ? 24 : 30,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
markLine: {
|
||||
symbol: "none",
|
||||
label: {
|
||||
position: "middle",
|
||||
formatter: "{b}",
|
||||
},
|
||||
lineStyle: {
|
||||
color: "white",
|
||||
fontWeight: "bold", // Make the mark line bold
|
||||
type: "dashed",
|
||||
width: 2, // Increase the dashed line width
|
||||
},
|
||||
data: [{ type: "average", name: "" }],
|
||||
},
|
||||
},
|
||||
// Add the new bar chart on top
|
||||
|
||||
{
|
||||
name: "Difference",
|
||||
type: "bar",
|
||||
barWidth: "100%", // Set the width to cover the other bars
|
||||
smooth: true,
|
||||
stack: change > 0 ? "fairPriceStack" : "lastPriceStack", // Stack this bar on top of the others
|
||||
data: change > 0 ? [lastPrice - fairPrice] : [fairPrice - lastPrice], // Set the value to 200
|
||||
itemStyle: {
|
||||
color: "#FF2F1F",
|
||||
},
|
||||
},
|
||||
],
|
||||
aria: {
|
||||
enabled: true,
|
||||
decal: {
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
async function getFairPrice(ticker) {
|
||||
const cachedData = getCache(ticker, "getFairPrice");
|
||||
if (cachedData) {
|
||||
fairPrice = cachedData;
|
||||
} else {
|
||||
try {
|
||||
const response = await fetch("/api/ticker-data", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ ticker: ticker, path: "fair-price" }),
|
||||
});
|
||||
fairPrice = await response.json();
|
||||
|
||||
setCache(ticker, fairPrice, "getFairPrice");
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch swap data:", error);
|
||||
fairPrice = null;
|
||||
}
|
||||
}
|
||||
if (fairPrice !== null && fairPrice >= 0) {
|
||||
$dcfComponent = true;
|
||||
} else {
|
||||
$dcfComponent = false;
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($stockTicker && typeof window !== "undefined") {
|
||||
isLoaded = false;
|
||||
getFairPrice($stockTicker).then(() => {
|
||||
if (fairPrice !== null) {
|
||||
lastPrice = data?.getStockQuote?.price?.toFixed(2);
|
||||
change = ((1 - fairPrice / lastPrice) * 100)?.toFixed(2);
|
||||
optionsBarChart = plotBarChart();
|
||||
}
|
||||
|
||||
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="dcfInfo"
|
||||
class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-3xl font-bold"
|
||||
>
|
||||
Discounted Cashflow Model
|
||||
</label>
|
||||
<InfoModal
|
||||
title={"Discounted Cashflow Model"}
|
||||
content={contentModal}
|
||||
id={"dcfInfo"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if ["Pro", "Plus"]?.includes(data?.user?.tier)}
|
||||
{#if isLoaded}
|
||||
{#if fairPrice !== null}
|
||||
<div
|
||||
class="p-3 sm:p-0 mt-2 pb-8 sm:pb-2 rounded-md bg-default sm:bg-default"
|
||||
>
|
||||
<div
|
||||
class="mt-4 text-white text-[1rem] sm:text-xl pb-4 sm:pb-0 m-auto text-start"
|
||||
>
|
||||
The DCF model signals a
|
||||
|
||||
{#if change < -3}
|
||||
<span class="text-green-700 dark:text-[#00FC50]">
|
||||
<svg
|
||||
class="w-6 h-6 sm:w-7 sm:h-7 inline-block"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
><g
|
||||
fill="none"
|
||||
stroke="#00FC50"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2.5"
|
||||
><path d="m3 17l6-6l4 4l8-8" /><path d="M17 7h4v4" /></g
|
||||
></svg
|
||||
>
|
||||
Buy
|
||||
</span>
|
||||
{:else if change > 3}
|
||||
<span class="text-[#E57C34]">
|
||||
<svg
|
||||
class="w-6 h-6 sm:w-7 sm:h-7 inline-block"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
><path
|
||||
fill="#ff2f1f"
|
||||
d="M244 136v64a12 12 0 0 1-12 12h-64a12 12 0 0 1 0-24h35l-67-67l-31.51 31.52a12 12 0 0 1-17 0l-72-72a12 12 0 0 1 17-17L96 127l31.51-31.52a12 12 0 0 1 17 0L220 171v-35a12 12 0 0 1 24 0Z"
|
||||
/></svg
|
||||
>
|
||||
Sell
|
||||
</span>
|
||||
{:else}
|
||||
<span class="text-red-700 dark:text-[#FF2F1F]">
|
||||
<svg
|
||||
class="w-6 h-6 sm:w-7 sm:h-7 inline-block"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
fill="#e57c34"
|
||||
d="m22 12l-4-4v3H3v2h15v3l4-4Z"
|
||||
/></svg
|
||||
>
|
||||
Hold
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if change > 0}
|
||||
<div class="text-white">
|
||||
The Stock Price is
|
||||
<span class="text-[#FF2F1F] sm:text-lg"
|
||||
>{Math?.abs(change)}% overvalued</span
|
||||
>.
|
||||
</div>
|
||||
{:else if change < 0}
|
||||
<div class="text-white">
|
||||
The Stock Price is
|
||||
<span class="text-green-700 dark:text-[#00FC50]"
|
||||
>{Math?.abs(change)}% undervalued</span
|
||||
>.
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="text-white text-md mt-2">
|
||||
<span class="text-blue-400">${$stockTicker}</span>
|
||||
(${lastPrice}) is trading {change < 0 ? "below" : "above"}
|
||||
our estimate of fair value (${fairPrice}).
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="text-white text-md mb-10 -mt-3">
|
||||
What is the Fair Price of <span class="font-normal text-blue-400"
|
||||
>${$stockTicker}</span
|
||||
> when looking at its future cash flows? For this estimate we use a
|
||||
Discounted Cash Flow model (DCF).
|
||||
</div>
|
||||
|
||||
<Lazy
|
||||
height={300}
|
||||
fadeOption={{ delay: 100, duration: 500 }}
|
||||
keep={true}
|
||||
>
|
||||
<div class="app w-full m-auto mb-5">
|
||||
<Chart options={optionsBarChart} class="chart w-full" />
|
||||
</div>
|
||||
</Lazy>
|
||||
|
||||
{#if Math?.abs(change) > 30}
|
||||
<div
|
||||
class=" mb-5 text-gray-100 text-sm sm:text-[1rem] sm:rounded-md h-auto border border-gray-600 p-4"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 inline-block mr-0.5 shrink-0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
><path
|
||||
fill="#fff"
|
||||
d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24m-4 48a12 12 0 1 1-12 12a12 12 0 0 1 12-12m12 112a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 0 16"
|
||||
/></svg
|
||||
>
|
||||
Caution: The DCF model may not be reliable for
|
||||
<span class="text-blue-400">${$stockTicker}</span> due to significant
|
||||
deviation between intrinsic value and current price.
|
||||
</div>
|
||||
{/if}
|
||||
</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}
|
||||
{:else}
|
||||
<div
|
||||
class="shadow-lg shadow-bg-[#000] bg-[#111112] sm:bg-opacity-[0.5] text-sm sm:text-[1rem] rounded-md w-full p-4 min-h-24 mt-4 text-white m-auto flex justify-center items-center text-center font-semibold"
|
||||
>
|
||||
<svg
|
||||
class="mr-1.5 w-5 h-5 inline-block"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
fill="#A3A3A3"
|
||||
d="M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9z"
|
||||
/></svg
|
||||
>
|
||||
Unlock content with
|
||||
<a
|
||||
class="inline-block ml-2 text-blue-400 sm:hover:text-white"
|
||||
href="/pricing">Pro Subscription</a
|
||||
>
|
||||
</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: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -277,7 +277,7 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="chart mt-5 sm:mt-0 border border-gray-300 dark:border-gray-800 rounded"
|
||||
class="shadow-sm mt-5 sm:mt-0 border border-gray-300 dark:border-gray-800 rounded"
|
||||
use:highcharts={config}
|
||||
></div>
|
||||
|
||||
|
||||
@ -1,57 +1,8 @@
|
||||
<script lang="ts">
|
||||
import * as HoverCard from "$lib/components/shadcn/hover-card/index.js";
|
||||
|
||||
import { ColorType } from "lightweight-charts";
|
||||
import { Chart, BaselineSeries, PriceLine } from "svelte-lightweight-charts";
|
||||
import { screenWidth, getCache, setCache } from "$lib/store";
|
||||
import { abbreviateNumber } from "$lib/utils";
|
||||
import { afterUpdate } from "svelte";
|
||||
export let symbol;
|
||||
export let assetType = "stock";
|
||||
export let link = null;
|
||||
|
||||
let priceData = [];
|
||||
let changesPercentage = 0;
|
||||
let change = 0;
|
||||
let stockChartData;
|
||||
|
||||
async function getStockData(ticker: string) {
|
||||
const cachedData = getCache(ticker, "hoverStockChart");
|
||||
if (cachedData) {
|
||||
stockChartData = cachedData;
|
||||
} else {
|
||||
const postData = { ticker: ticker };
|
||||
const response = await fetch("/api/hover-stock-chart", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(postData),
|
||||
});
|
||||
|
||||
stockChartData = (await response?.json()) ?? {};
|
||||
|
||||
setCache(ticker, stockChartData, "hoverStockChart");
|
||||
}
|
||||
|
||||
changesPercentage = stockChartData?.changesPercentage;
|
||||
change = stockChartData?.change;
|
||||
|
||||
priceData = stockChartData?.history;
|
||||
|
||||
priceData = priceData
|
||||
?.map((item) => ({
|
||||
time: Date?.parse(item?.time), // Assuming 'time' is the correct property to parse
|
||||
value: item?.close ?? null,
|
||||
}))
|
||||
?.filter(
|
||||
(item) =>
|
||||
item?.value !== 0 &&
|
||||
item?.value !== null &&
|
||||
item?.value !== undefined,
|
||||
);
|
||||
}
|
||||
|
||||
function getHref(symbol: string) {
|
||||
let path = "";
|
||||
if (symbol?.length !== 0) {
|
||||
@ -67,208 +18,10 @@
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
$: topLineColor = changesPercentage >= 0 ? "#71CA96" : "#FF7070";
|
||||
|
||||
let width = $screenWidth < 640 ? 80 : 150; //= ($screenWidth <= 1200 && $screenWidth > 900) ? 360 : ($screenWidth <= 900 && $screenWidth > 700) ? 260 : ($screenWidth <= 700 && $screenWidth >=600 ) ? 200 : ($screenWidth < 600 && $screenWidth >=500 ) ? 150 : 80;
|
||||
|
||||
//Initial height of graph
|
||||
let height = $screenWidth < 640 ? 50 : 60;
|
||||
|
||||
let observer;
|
||||
let ref;
|
||||
let chart;
|
||||
|
||||
ref = (element) => {
|
||||
if (observer) {
|
||||
observer?.disconnect();
|
||||
}
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
observer = new ResizeObserver(([entry]) => {
|
||||
width = entry.contentRect.width;
|
||||
height = entry.contentRect.height;
|
||||
});
|
||||
observer.observe(element);
|
||||
};
|
||||
|
||||
const THEMES = {
|
||||
Dark: {
|
||||
chart: {
|
||||
layout: {
|
||||
background: {
|
||||
type: ColorType.Solid,
|
||||
color: "#11151D",
|
||||
},
|
||||
lineColor: "#2B2B43",
|
||||
textColor: "#D9D9D9",
|
||||
},
|
||||
crosshair: {
|
||||
mode: 2,
|
||||
},
|
||||
grid: {
|
||||
vertLines: {
|
||||
visible: false,
|
||||
},
|
||||
horzLines: {
|
||||
visible: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
series: {
|
||||
baseValue: { type: "price", price: priceData?.at(0)?.value },
|
||||
priceLineVisible: false,
|
||||
baseLineVisible: true,
|
||||
baseLineColor: "#B2B5BE",
|
||||
baseLineWidth: 1,
|
||||
baseLineStyle: 1,
|
||||
lineWidth: 1.5,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const AVAILABLE_THEMES = Object?.keys(THEMES);
|
||||
|
||||
let selected = AVAILABLE_THEMES[0];
|
||||
$: theme = THEMES[selected];
|
||||
|
||||
const options = {
|
||||
width: width,
|
||||
height: height,
|
||||
rightPriceScale: {
|
||||
visible: false,
|
||||
},
|
||||
timeScale: {
|
||||
borderColor: "#FFFFFF",
|
||||
textColor: "#FFFFFF",
|
||||
visible: false,
|
||||
fixLeftEdge: true,
|
||||
fixRightEdge: true,
|
||||
},
|
||||
handleScale: {
|
||||
mouseWheel: false,
|
||||
},
|
||||
handleScroll: {
|
||||
mouseWheel: false,
|
||||
horzTouchDrag: false,
|
||||
vertTouchDrag: false,
|
||||
pressedMouseMove: false,
|
||||
},
|
||||
};
|
||||
|
||||
afterUpdate(async () => {
|
||||
if (
|
||||
$screenWidth &&
|
||||
stockChartData &&
|
||||
chart &&
|
||||
typeof window !== "undefined"
|
||||
) {
|
||||
chart?.timeScale()?.fitContent();
|
||||
}
|
||||
});
|
||||
|
||||
$: charNumber = 20;
|
||||
</script>
|
||||
|
||||
<div on:mouseover={() => getStockData(symbol)} class="inline-block">
|
||||
<HoverCard.Root>
|
||||
<div on:mouseover>
|
||||
<HoverCard.Trigger
|
||||
class="rounded-sm underline-offset-4 hover:underline focus-visible:outline-2 focus-visible:outline-offset-8 focus-visible:outline-black"
|
||||
>
|
||||
<a
|
||||
href={getHref(symbol)}
|
||||
class="sm:hover:text-muted dark:sm:hover:text-white text-blue-700 dark:text-blue-400"
|
||||
>{symbol?.length !== 0 ? symbol : "-"}</a
|
||||
>
|
||||
</HoverCard.Trigger>
|
||||
</div>
|
||||
<HoverCard.Content class=" w-96 bg-[#11151D] border border-gray-600">
|
||||
<div class="flex justify-between space-x-4 w-full text-white">
|
||||
<div class="space-y-1 w-full">
|
||||
<!--Hover Stock Chart-->
|
||||
<label
|
||||
class=" text-sm flex flex-row items-center justify-start bg-[#11151D]"
|
||||
>
|
||||
<div class="flex flex-col items-start w-full">
|
||||
<div class=" flex flex-col items-start pb-1">
|
||||
<h4 class="text-sm text-blue-400 inline-block">
|
||||
{symbol}
|
||||
</h4>
|
||||
<h5 class="text-sm inline-block">
|
||||
{stockChartData?.name?.length > charNumber
|
||||
? stockChartData?.name?.slice(0, charNumber) + "..."
|
||||
: stockChartData?.name}
|
||||
</h5>
|
||||
</div>
|
||||
<p>
|
||||
Current Price: {stockChartData?.price?.toFixed(2)} (<span
|
||||
class="text-xs {change >= 0
|
||||
? "before:content-['+'] text-green-700 dark:text-[#00FC50]"
|
||||
: 'text-red-700 dark:text-[#FF2F1F]'}"
|
||||
>{change?.toFixed(2)}</span
|
||||
>)
|
||||
</p>
|
||||
<p>
|
||||
<svg
|
||||
class="-ml-1 inline-block w-4 h-4 {changesPercentage >= 0
|
||||
? ''
|
||||
: 'rotate-180'}"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill={changesPercentage >= 0 ? "#00FC50" : "#FF2F1F"}
|
||||
d="M12.884 5.116a1.253 1.253 0 0 0-1.768 0l-5 5a1.25 1.25 0 0 0 1.768 1.768l2.866-2.866V18a1.25 1.25 0 1 0 2.5 0V9.018l2.866 2.866a1.25 1.25 0 1 0 1.768-1.768z"
|
||||
/>
|
||||
</svg>
|
||||
{#if changesPercentage != null}
|
||||
<span
|
||||
class="-ml-1"
|
||||
style="color: {changesPercentage >= 0
|
||||
? '#00FC50'
|
||||
: '#FF2F1F'}"
|
||||
>
|
||||
{changesPercentage >= 1000 || changesPercentage <= -1000
|
||||
? abbreviateNumber(changesPercentage)
|
||||
: changesPercentage?.toFixed(2)}%
|
||||
</span>
|
||||
{:else}
|
||||
-
|
||||
{/if}
|
||||
today
|
||||
</p>
|
||||
</div>
|
||||
{#if priceData?.length > 0}
|
||||
<div class="w-[40%]">
|
||||
<Chart
|
||||
{...options}
|
||||
{...theme.chart}
|
||||
autoSize={true}
|
||||
ref={(ref) => (chart = ref)}
|
||||
>
|
||||
<BaselineSeries
|
||||
data={priceData}
|
||||
{...theme.series}
|
||||
{topLineColor}
|
||||
>
|
||||
<PriceLine
|
||||
price={priceData?.at(0)?.value}
|
||||
lineWidth={1}
|
||||
color="#fff"
|
||||
/>
|
||||
</BaselineSeries>
|
||||
</Chart>
|
||||
</div>
|
||||
{/if}
|
||||
</label>
|
||||
|
||||
<!--Hover Stock Chart-->
|
||||
</div>
|
||||
</div>
|
||||
</HoverCard.Content>
|
||||
</HoverCard.Root>
|
||||
</div>
|
||||
<a
|
||||
href={getHref(symbol)}
|
||||
class="sm:hover:text-muted dark:sm:hover:text-white text-blue-700 dark:text-blue-400"
|
||||
>{symbol?.length !== 0 ? symbol : "-"}</a
|
||||
>
|
||||
|
||||
@ -1,195 +0,0 @@
|
||||
<script>
|
||||
import { ColorType } from "lightweight-charts";
|
||||
import { Chart, BaselineSeries, PriceLine } from "svelte-lightweight-charts";
|
||||
import { screenWidth, etfTicker } from "$lib/store";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
export let title;
|
||||
export let priceData;
|
||||
export let changesPercentage;
|
||||
export let previousClose;
|
||||
|
||||
const topLineColor = "#71CA96";
|
||||
const topFillColor1 = "rgba( 38, 166, 154, 0.2)";
|
||||
const bottomLineColor = "#FF7070";
|
||||
const bottomFillColor1 = "rgba(239, 83, 80, 0.2)";
|
||||
|
||||
let width = $screenWidth < 640 ? 80 : $screenWidth < 1500 ? 150 : 220; //= ($screenWidth <= 1200 && $screenWidth > 900) ? 360 : ($screenWidth <= 900 && $screenWidth > 700) ? 260 : ($screenWidth <= 700 && $screenWidth >=600 ) ? 200 : ($screenWidth < 600 && $screenWidth >=500 ) ? 150 : 80;
|
||||
|
||||
//Initial height of graph
|
||||
let height = $screenWidth < 640 ? 50 : 60;
|
||||
|
||||
let observer;
|
||||
let ref;
|
||||
let chart;
|
||||
|
||||
ref = (element) => {
|
||||
if (observer) {
|
||||
observer?.disconnect();
|
||||
}
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
observer = new ResizeObserver(([entry]) => {
|
||||
width = entry.contentRect.width;
|
||||
height = entry.contentRect.height;
|
||||
});
|
||||
observer.observe(element);
|
||||
};
|
||||
|
||||
const THEMES = {
|
||||
Dark: {
|
||||
chart: {
|
||||
layout: {
|
||||
background: {
|
||||
type: ColorType.Solid,
|
||||
color: "#09090B",
|
||||
},
|
||||
lineColor: "#2B2B43",
|
||||
textColor: "#D9D9D9",
|
||||
},
|
||||
crosshair: {
|
||||
mode: 2,
|
||||
},
|
||||
grid: {
|
||||
vertLines: {
|
||||
visible: false,
|
||||
},
|
||||
horzLines: {
|
||||
visible: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
series: {
|
||||
baseValue: { type: "price", price: priceData?.at(0)?.value },
|
||||
priceLineVisible: false,
|
||||
baseLineVisible: true,
|
||||
baseLineColor: "#B2B5BE",
|
||||
baseLineWidth: 1,
|
||||
baseLineStyle: 1,
|
||||
lineWidth: 1.5,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const AVAILABLE_THEMES = Object?.keys(THEMES);
|
||||
|
||||
let selected = AVAILABLE_THEMES[0];
|
||||
$: theme = THEMES[selected];
|
||||
|
||||
const options = {
|
||||
width: width,
|
||||
height: height,
|
||||
rightPriceScale: {
|
||||
visible: false,
|
||||
},
|
||||
timeScale: {
|
||||
borderColor: "#FFFFFF",
|
||||
textColor: "#FFFFFF",
|
||||
visible: false,
|
||||
fixLeftEdge: true,
|
||||
fixRightEdge: true,
|
||||
},
|
||||
handleScale: {
|
||||
mouseWheel: false,
|
||||
},
|
||||
handleScroll: {
|
||||
mouseWheel: false,
|
||||
horzTouchDrag: false,
|
||||
vertTouchDrag: false,
|
||||
pressedMouseMove: false,
|
||||
},
|
||||
};
|
||||
|
||||
function etfSelector() {
|
||||
if (title === "S&P500") {
|
||||
etfTicker.update((value) => "SPY");
|
||||
goto("/etf/SPY");
|
||||
} else if (title === "Nasdaq") {
|
||||
etfTicker.update((value) => "QQQ");
|
||||
goto("/etf/QQQ");
|
||||
} else if (title === "Dow") {
|
||||
etfTicker.update((value) => "DIA");
|
||||
goto("/etf/DIA");
|
||||
} else if (title === "Russel") {
|
||||
etfTicker.update((value) => "IWM");
|
||||
goto("/etf/IWM");
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if (chart && typeof window !== "undefined") {
|
||||
chart?.timeScale()?.fitContent();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<label
|
||||
on:click={etfSelector}
|
||||
class="sm:hover:border-[#3C74D4] duration-100 transition ease-in-out cursor-pointer flex flex-row items-center rounded-md shadow-lg border border-gray-600 bg-default"
|
||||
>
|
||||
<div class="flex flex-col items-center lg:mr-5">
|
||||
<span
|
||||
class="text-white font-semibold text-xs w-20 text-start pl-3 uppercase"
|
||||
>{title}</span
|
||||
>
|
||||
<div class="flex flex-row mt-1 items-center">
|
||||
{#if changesPercentage >= 0}
|
||||
<svg
|
||||
class="w-4 h-4 -mr-0.5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
><g id="evaArrowUpFill0"
|
||||
><g id="evaArrowUpFill1"
|
||||
><path
|
||||
id="evaArrowUpFill2"
|
||||
fill="#00FC50"
|
||||
d="M16.21 16H7.79a1.76 1.76 0 0 1-1.59-1a2.1 2.1 0 0 1 .26-2.21l4.21-5.1a1.76 1.76 0 0 1 2.66 0l4.21 5.1A2.1 2.1 0 0 1 17.8 15a1.76 1.76 0 0 1-1.59 1Z"
|
||||
/></g
|
||||
></g
|
||||
></svg
|
||||
>
|
||||
<span class="text-[#00FC50] text-xs"
|
||||
>+{changesPercentage?.toFixed(2)}%</span
|
||||
>
|
||||
{:else}
|
||||
<svg
|
||||
class="w-4 h-4 -mr-0.5 rotate-180"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
><g id="evaArrowUpFill0"
|
||||
><g id="evaArrowUpFill1"
|
||||
><path
|
||||
id="evaArrowUpFill2"
|
||||
fill="#FF2F1F"
|
||||
d="M16.21 16H7.79a1.76 1.76 0 0 1-1.59-1a2.1 2.1 0 0 1 .26-2.21l4.21-5.1a1.76 1.76 0 0 1 2.66 0l4.21 5.1A2.1 2.1 0 0 1 17.8 15a1.76 1.76 0 0 1-1.59 1Z"
|
||||
/></g
|
||||
></g
|
||||
></svg
|
||||
>
|
||||
<span class="text-[#FF2F1F] text-xs"
|
||||
>{changesPercentage?.toFixed(2)}%
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<Chart
|
||||
{...options}
|
||||
{...theme.chart}
|
||||
autoSize={true}
|
||||
ref={(ref) => (chart = ref)}
|
||||
>
|
||||
<BaselineSeries
|
||||
data={priceData}
|
||||
{...theme.series}
|
||||
{topLineColor}
|
||||
{topFillColor1}
|
||||
{bottomLineColor}
|
||||
{bottomFillColor1}
|
||||
>
|
||||
<PriceLine price={priceData?.at(0)?.value} lineWidth={1} color="#fff" />
|
||||
</BaselineSeries>
|
||||
</Chart>
|
||||
</label>
|
||||
@ -1,344 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { displayCompanyName, screenWidth } from "$lib/store";
|
||||
import InfoModal from "$lib/components/InfoModal.svelte";
|
||||
import { Chart } from "svelte-echarts";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import { init, use } from "echarts/core";
|
||||
import { LineChart } from "echarts/charts";
|
||||
import { GridComponent, TooltipComponent } from "echarts/components";
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
|
||||
use([LineChart, GridComponent, TooltipComponent, CanvasRenderer]);
|
||||
|
||||
export let data;
|
||||
let isLoaded = false;
|
||||
|
||||
let priceAnalysisDict = data?.getPriceAnalysis;
|
||||
let lastPrice = data?.getStockQuote?.price ?? "n/a";
|
||||
|
||||
const modalContent = `
|
||||
Our AI model, employing a Bayesian approach, predicts future prices by breaking down trends, seasonality, and holiday effects. It integrates uncertainty to offer forecasts with intervals.<br><br>
|
||||
<span class="font-semibold underline"><span class="italic">R</span><sup>2</sup> Score</span>: How well the regression model fits the data. A high score (close to 100%) is good, indicating a strong fit, while a low score (close to 0%) is bad, suggesting poor fit.
|
||||
<br><br>
|
||||
<span class="font-semibold underline">Mean Absolute Percentage Error (MAPE)</span>: Measures the average percentage difference between predicted and actual values. A lower MAPE indicates better accuracy, while a higher MAPE suggests less accurate predictions.
|
||||
`;
|
||||
|
||||
const monthNames = [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
];
|
||||
|
||||
let r2Score;
|
||||
let mape;
|
||||
let priceSentiment = "n/a";
|
||||
let oneYearPricePrediction = "n/a";
|
||||
let optionsData;
|
||||
|
||||
function getPlotOptions() {
|
||||
const predictionDate = priceAnalysisDict?.predictionDate;
|
||||
const upperBand = priceAnalysisDict?.upperBand;
|
||||
const lowerBand = priceAnalysisDict?.lowerBand?.map((value) =>
|
||||
value < 0 ? 0 : value,
|
||||
);
|
||||
const historicalPrice = priceAnalysisDict?.historicalPrice;
|
||||
|
||||
const option = {
|
||||
silent: true,
|
||||
animation: false,
|
||||
grid: {
|
||||
left: screenWidth < 640 ? "0%" : "2%",
|
||||
right: screenWidth < 640 ? "5%" : "2%",
|
||||
bottom: screenWidth < 640 ? "0%" : "5%",
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
hideDelay: 100,
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
boundaryGap: false,
|
||||
data: predictionDate,
|
||||
axisLabel: {
|
||||
color: "#fff",
|
||||
formatter(value) {
|
||||
const dateParts = value?.split("-");
|
||||
const year = dateParts[0]?.substring(2);
|
||||
const monthIndex = parseInt(dateParts[1], 10) - 1;
|
||||
return `${monthNames[monthIndex]} '${year}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
splitLine: { show: false },
|
||||
axisLabel: { show: false },
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "Stock Price",
|
||||
data: historicalPrice,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
type: "line",
|
||||
itemStyle: { color: "#fff" },
|
||||
},
|
||||
{
|
||||
name: "Upperband",
|
||||
data: upperBand,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
type: "line",
|
||||
areaStyle: { color: "rgba(60, 116, 212, 0.4)" },
|
||||
lineStyle: { color: "rgb(60, 116, 212)" },
|
||||
},
|
||||
{
|
||||
name: "Lowerband",
|
||||
data: lowerBand,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
type: "line",
|
||||
areaStyle: { color: "#09090B", opacity: 1 },
|
||||
lineStyle: { color: "rgb(60, 116, 212)" },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
oneYearPricePrediction = priceAnalysisDict?.meanResult?.slice(-1)?.at(0);
|
||||
mape = priceAnalysisDict?.mape;
|
||||
r2Score = priceAnalysisDict?.r2Score;
|
||||
priceSentiment = lastPrice < oneYearPricePrediction ? "Bullish" : "Bearish";
|
||||
optionsData = await getPlotOptions();
|
||||
isLoaded = true;
|
||||
});
|
||||
</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="priceAnalysisInfo"
|
||||
class="mr-1 cursor-pointer flex flex-row items-center text-white text-xl sm:text-3xl font-bold"
|
||||
>
|
||||
AI Price Analysis
|
||||
</label>
|
||||
<InfoModal
|
||||
title={"Price Analysis"}
|
||||
content={modalContent}
|
||||
id={"priceAnalysisInfo"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if isLoaded}
|
||||
{#if Object?.keys(priceAnalysisDict)?.length !== 0}
|
||||
<div class="w-full flex flex-col items-start">
|
||||
<div class="text-white text-[1rem] mt-1 sm:mt-3 mb-1 w-full">
|
||||
Our model predicts future prices by analyzing trends, seasonal
|
||||
variations, and holiday impacts. Here are the stats of the model for {$displayCompanyName}
|
||||
to ensure transparency and reliability.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full mt-5 mb-5 flex justify-start items-center">
|
||||
<div
|
||||
class="w-full grid grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 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-primary shadow-lg rounded-md h-20"
|
||||
>
|
||||
<div class="flex flex-col items-start">
|
||||
<span class=" text-gray-200 text-sm">Price Sentiment</span>
|
||||
<span
|
||||
class="text-start text-[1rem] sm:text-lg font-semibold {priceSentiment ===
|
||||
'Bullish'
|
||||
? 'text-[#37C97D]'
|
||||
: 'text-red-700 dark:text-[#FF2F1F]'}"
|
||||
>{priceSentiment}</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-primary shadow-lg rounded-md h-20"
|
||||
>
|
||||
<div class="flex flex-col items-start">
|
||||
<span class=" text-gray-200 text-sm"
|
||||
><span class="italic">R</span><sup>2</sup> Score</span
|
||||
>
|
||||
<span class="text-start text-sm sm:text-[1rem] text-white">
|
||||
{r2Score >= 65 ? "Good" : r2Score >= 50 ? "Moderate" : "Bad"}
|
||||
</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 {r2Score >= 65
|
||||
? 'text-green-700 dark:text-[#00FC50]'
|
||||
: r2Score >= 50
|
||||
? 'text-[#F8901E]'
|
||||
: 'text-red-700 dark:text-[#FF2F1F]'}"
|
||||
stroke-width="3"
|
||||
stroke-dasharray="100"
|
||||
stroke-dashoffset={100 - r2Score}
|
||||
></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">{r2Score}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Circular Progress -->
|
||||
</div>
|
||||
<!--End Put/Call-->
|
||||
|
||||
<!--Start mape-->
|
||||
<div
|
||||
class="flex flex-row items-center flex-wrap w-full px-3 sm:px-5 bg-primary shadow-lg rounded-md h-20"
|
||||
>
|
||||
<div class="flex flex-col items-start">
|
||||
<span class=" text-gray-200 text-sm">MAPE</span>
|
||||
<span class="text-start text-sm sm:text-[1rem] text-white">
|
||||
{mape <= 15 ? "Good" : mape <= 35 ? "Moderate" : "Bad"}
|
||||
</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 {mape <= 15
|
||||
? 'text-green-700 dark:text-[#00FC50]'
|
||||
: mape <= 35
|
||||
? 'text-[#F8901E]'
|
||||
: 'text-red-700 dark:text-[#FF2F1F]'}"
|
||||
stroke-width="3"
|
||||
stroke-dasharray="100"
|
||||
stroke-dashoffset={100 - mape > 0 ? 100 - mape : 1}
|
||||
></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">{mape}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Circular Progress -->
|
||||
</div>
|
||||
<!--End mape-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="app w-full h-[300px]">
|
||||
<Chart {init} options={optionsData} class="chart" />
|
||||
</div>
|
||||
|
||||
<div class="text-white text-[1rem] mt-4 sm:mt-7 ml-1">
|
||||
Over the next 12 months, the model predicts a
|
||||
<span
|
||||
class="font-semibold {priceSentiment === 'Bullish'
|
||||
? 'text-[#37C97D]'
|
||||
: 'text-red-700 dark:text-[#FF2F1F]'}">{priceSentiment}</span
|
||||
>
|
||||
trend, suggesting that the future price is expected to {priceSentiment ===
|
||||
"Bullish"
|
||||
? "surpass"
|
||||
: "to be less than"} the previous price of
|
||||
<span class="font-semibold">${lastPrice?.toFixed(2) ?? "n/a"}</span>,
|
||||
with a mean value of
|
||||
<span class="font-semibold">${oneYearPricePrediction}</span>.
|
||||
</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>
|
||||
@ -69,9 +69,9 @@
|
||||
animation: false,
|
||||
},
|
||||
title: {
|
||||
text: `<div class="text-muted dark:text-gray-200 mt-3 text-center font-normal text-2xl">Price Target: <span class="${changesPercentage >= 0 ? "text-green-700 dark:text-[#00FC50]" : "text-[#FF2F1F]"}">$${priceTarget}</span></div>
|
||||
text: `<div class="text-muted dark:text-gray-200 mt-3 text-center font-normal text-2xl">Price Target: <span class="${changesPercentage >= 0 ? "text-green-700 dark:text-[#00FC50]" : "text-red-700 dark:text-[#FF2F1F]"}">$${priceTarget}</span></div>
|
||||
<div class="text-muted dark:text-gray-200 mb-2 text-center font-normal text-xl">(${changesPercentage}% ${changesPercentage >= 0 ? "upside" : "downside"})</div>
|
||||
<div class="text-muted dark:text-gray-200 text-center font-normal text-xl flex justify-center items-center">Analyst Consensus: <span class="ml-1 ${consensusRating === "Buy" ? "text-green-700 dark:text-[#00FC50]" : consensusRating === "Sell" ? "text-red-500 dark:text-[#FF2F1F]" : consensusRating === "Hold" ? "text-orange-500 dark:text-[#D5AB31]" : "text-muted dark:text-white"}">${consensusRating ?? "n/a"}</span></div>`,
|
||||
<div class="text-muted dark:text-gray-200 text-center font-normal text-xl flex justify-center items-center">Analyst Consensus: <span class="ml-1 ${consensusRating === "Buy" ? "text-green-700 dark:text-[#00FC50]" : consensusRating === "Sell" ? "text-red-700 dark:text-[#FF2F1F]" : consensusRating === "Hold" ? "text-orange-500 dark:text-[#D5AB31]" : "text-muted dark:text-white"}">${consensusRating ?? "n/a"}</span></div>`,
|
||||
style: {
|
||||
color: "white",
|
||||
// Using inline CSS for margin-top and margin-bottom
|
||||
@ -299,7 +299,7 @@
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="mt-3 border border-gray-300 dark:border-gray-700 rounded"
|
||||
class="mt-3 border border-gray-300 dark:border-gray-800 rounded"
|
||||
use:highcharts={configAnalyst}
|
||||
></div>
|
||||
|
||||
|
||||
@ -1,253 +0,0 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
varComponent,
|
||||
displayCompanyName,
|
||||
stockTicker,
|
||||
etfTicker,
|
||||
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";
|
||||
|
||||
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;
|
||||
export let rawData = {};
|
||||
|
||||
use([LineChart, GridComponent, TooltipComponent, CanvasRenderer]);
|
||||
|
||||
let isLoaded = false;
|
||||
let rating: string | undefined;
|
||||
let outlook: string | undefined;
|
||||
let valueAtRisk: number | string | undefined;
|
||||
let optionsData: any;
|
||||
let monthlyVarAvg: string | undefined;
|
||||
|
||||
function getPlotOptions() {
|
||||
const dates: string[] = [];
|
||||
const varList: number[] = [];
|
||||
const priceList = [];
|
||||
|
||||
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);
|
||||
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 },
|
||||
},
|
||||
{
|
||||
type: "value",
|
||||
splitLine: { show: false },
|
||||
axisLabel: { show: false },
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "VaR",
|
||||
data: varList,
|
||||
type: "line",
|
||||
itemStyle: { color: "#E11D48" },
|
||||
showSymbol: false,
|
||||
},
|
||||
{
|
||||
name: "Stock Price",
|
||||
data: priceList,
|
||||
type: "line",
|
||||
yAxisIndex: 1,
|
||||
itemStyle: { color: "#fff" },
|
||||
showSymbol: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
$: {
|
||||
const ticker = $assetType === "stock" ? $stockTicker : "etf";
|
||||
|
||||
if (ticker) {
|
||||
isLoaded = false;
|
||||
|
||||
rating = rawData.rating;
|
||||
outlook = rawData.outlook;
|
||||
valueAtRisk = rawData.history?.slice(-1)?.at(0)?.var ?? "n/a";
|
||||
optionsData = getPlotOptions();
|
||||
}
|
||||
}
|
||||
</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-2xl 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(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"
|
||||
>
|
||||
<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-green-700 dark:text-[#00FC50]'
|
||||
: rating < 5
|
||||
? 'text-red-700 dark: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
|
||||
{$assetType === "stock" ? $stockTicker : $etfTicker}
|
||||
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 <span class="font-semibold">{monthlyVarAvg}%</span
|
||||
>.
|
||||
</div>
|
||||
|
||||
<div class="app w-full h-[300px] mt-5">
|
||||
<Chart {init} options={optionsData} class="chart" />
|
||||
</div>
|
||||
{:else}
|
||||
<Infobox text="No data available" />
|
||||
{/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,22 +0,0 @@
|
||||
export const load = async ({ locals, setHeaders }) => {
|
||||
const getEconomicIndicator = async () => {
|
||||
const { apiKey, apiURL } = locals;
|
||||
|
||||
const response = await fetch(apiURL + "/economic-indicator", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-API-KEY": apiKey,
|
||||
},
|
||||
});
|
||||
|
||||
const output = await response.json();
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
// Make sure to return a promise
|
||||
return {
|
||||
getEconomicIndicator: await getEconomicIndicator(),
|
||||
};
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,19 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { displayCompanyName, screenWidth, indexTicker } from "$lib/store";
|
||||
import { displayCompanyName, indexTicker } from "$lib/store";
|
||||
import DailyStats from "$lib/components/Options/DailyStats.svelte";
|
||||
import { Chart } from "svelte-echarts";
|
||||
import { abbreviateNumberWithColor, monthNames } from "$lib/utils";
|
||||
import { abbreviateNumber } from "$lib/utils";
|
||||
import { onMount } from "svelte";
|
||||
import UpgradeToPro from "$lib/components/UpgradeToPro.svelte";
|
||||
import { init, use } from "echarts/core";
|
||||
import { BarChart, LineChart } from "echarts/charts";
|
||||
import { GridComponent, TooltipComponent } from "echarts/components";
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
|
||||
import Infobox from "$lib/components/Infobox.svelte";
|
||||
import SEO from "$lib/components/SEO.svelte";
|
||||
|
||||
use([BarChart, LineChart, GridComponent, TooltipComponent, CanvasRenderer]);
|
||||
|
||||
export let data;
|
||||
let dailyStats = data?.getDailyStats;
|
||||
|
||||
@ -56,145 +50,6 @@
|
||||
displayData = event.target.value;
|
||||
}
|
||||
|
||||
function plotData(callData, putData, priceList) {
|
||||
const options = {
|
||||
animation: false,
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
hideDelay: 100,
|
||||
borderColor: "#969696", // Black border color
|
||||
borderWidth: 1, // Border width of 1px
|
||||
backgroundColor: "#313131", // Optional: Set background color for contrast
|
||||
textStyle: {
|
||||
color: "#fff", // Optional: Text color for better visibility
|
||||
},
|
||||
formatter: function (params) {
|
||||
// Get the timestamp from the first parameter
|
||||
const timestamp = params[0].axisValue;
|
||||
|
||||
// Initialize result with timestamp
|
||||
let result = timestamp + "<br/>";
|
||||
|
||||
// Sort params to ensure Vol appears last
|
||||
params.sort((a, b) => {
|
||||
if (a.seriesName === "Vol") return 1;
|
||||
if (b.seriesName === "Vol") return -1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Add each series data
|
||||
params?.forEach((param) => {
|
||||
const marker =
|
||||
'<span style="display:inline-block;margin-right:4px;' +
|
||||
"border-radius:10px;width:10px;height:10px;background-color:" +
|
||||
param.color +
|
||||
'"></span>';
|
||||
result +=
|
||||
marker +
|
||||
param.seriesName +
|
||||
": " +
|
||||
abbreviateNumberWithColor(param.value, false, true) +
|
||||
"<br/>";
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
color: "#fff",
|
||||
},
|
||||
},
|
||||
},
|
||||
silent: true,
|
||||
grid: {
|
||||
left: $screenWidth < 640 ? "5%" : "2%",
|
||||
right: $screenWidth < 640 ? "5%" : "2%",
|
||||
bottom: "10%",
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: "category",
|
||||
data: dateList,
|
||||
axisLabel: {
|
||||
color: "#fff",
|
||||
|
||||
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, // Disable x-axis grid lines
|
||||
},
|
||||
axisLabel: {
|
||||
show: false, // Hide y-axis labels
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "value",
|
||||
splitLine: {
|
||||
show: false, // Disable x-axis grid lines
|
||||
},
|
||||
position: "right",
|
||||
axisLabel: {
|
||||
show: false, // Hide y-axis labels
|
||||
},
|
||||
},
|
||||
],
|
||||
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'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Price", // Name for the line chart
|
||||
type: "line", // Type of the chart (line)
|
||||
yAxisIndex: 1, // Use the second y-axis on the right
|
||||
data: priceList, // iv60Data (assumed to be passed as priceList)
|
||||
itemStyle: {
|
||||
color: "#fff", // Choose a color for the line (gold in this case)
|
||||
},
|
||||
lineStyle: {
|
||||
width: 2, // Set the width of the line
|
||||
},
|
||||
smooth: true, // Optional: make the line smooth
|
||||
showSymbol: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
return options;
|
||||
}
|
||||
|
||||
function filterDate(filteredList, displayTimePeriod) {
|
||||
const now = Date.now();
|
||||
let cutoffDate;
|
||||
@ -244,11 +99,13 @@
|
||||
putOpenInterestList = filteredList?.map((item) => item?.put_open_interest);
|
||||
|
||||
// Determine the type of plot data to generate based on displayData
|
||||
/*
|
||||
if (displayData === "volume") {
|
||||
options = plotData(callVolumeList, putVolumeList, priceList);
|
||||
} else if (displayData === "openInterest") {
|
||||
options = plotData(callOpenInterestList, putOpenInterestList, priceList);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
async function handleScroll() {
|
||||
@ -290,7 +147,7 @@
|
||||
<div
|
||||
class="w-full relative flex justify-center items-center overflow-hidden"
|
||||
>
|
||||
<div class="sm:pl-7 sm:pb-7 sm:pt-7 w-full m-auto mt-2 sm:mt-0">
|
||||
<div class="sm:pl-5 sm:pb-7 sm:pt-7 w-full m-auto mt-2 sm:mt-0">
|
||||
{#if Object?.keys(dailyStats)?.length === 0 && rawData?.length === 0}
|
||||
<Infobox text="No Options data available" />
|
||||
{/if}
|
||||
@ -302,56 +159,6 @@
|
||||
{/if}
|
||||
|
||||
{#if rawData?.length > 0}
|
||||
<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-secondary"
|
||||
on:change={changeTimePeriod}
|
||||
>
|
||||
<option disabled>Choose a time period</option>
|
||||
<option value="oneWeek">1 Week</option>
|
||||
<option value="oneMonth">1 Month</option>
|
||||
<option value="threeMonths">3 Months</option>
|
||||
<option value="sixMonths">6 Months</option>
|
||||
<option value="oneYear" selected>1 Year</option>
|
||||
</select>
|
||||
-->
|
||||
|
||||
<select
|
||||
class=" w-40 select select-bordered select-sm p-0 pl-5 bg-secondary"
|
||||
on:change={changeVolumeOI}
|
||||
>
|
||||
<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-default">
|
||||
{#if filteredList?.length !== 0}
|
||||
<Chart {init} {options} class="chart" />
|
||||
{:else}
|
||||
<span
|
||||
class="text-xl text-white m-auto flex justify-center items-center h-full"
|
||||
>
|
||||
<div
|
||||
class="text-white text-sm sm:text-[1rem] sm:rounded-md h-auto border border-gray-600 p-4"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 inline-block sm:mr-2 shrink-0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 256 256"
|
||||
><path
|
||||
fill="#fff"
|
||||
d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24m-4 48a12 12 0 1 1-12 12a12 12 0 0 1 12-12m12 112a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 0 16"
|
||||
/></svg
|
||||
>
|
||||
No Options activity found
|
||||
</div>
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if optionList?.length !== 0}
|
||||
<h3 class="text-xl sm:text-2xl text-white font-bold text-start">
|
||||
Historical {$indexTicker} Data
|
||||
@ -359,92 +166,66 @@
|
||||
|
||||
<div class="flex justify-start items-center m-auto overflow-x-auto">
|
||||
<table
|
||||
class="w-full table table-sm table-compact bg-table border border-gray-800 rounded-none sm:rounded-md m-auto mt-4 overflow-x-auto"
|
||||
class="table table-sm table-compact no-scrollbar rounded-none sm:rounded-md w-full bg-white dark:bg-table border border-gray-300 dark:border-gray-800 m-auto mt-3"
|
||||
>
|
||||
<thead class="bg-default">
|
||||
<thead class="text-muted dark:text-white dark:bg-default">
|
||||
<tr class="">
|
||||
<td class="text-white font-semibold text-sm text-start"
|
||||
>Date</td
|
||||
>
|
||||
<td class="text-white font-semibold text-sm text-end"
|
||||
>% Change</td
|
||||
>
|
||||
<td class="text-white font-semibold text-sm text-end"
|
||||
>P/C</td
|
||||
>
|
||||
<td class="text-white font-semibold text-sm text-center"
|
||||
>Volume</td
|
||||
>
|
||||
<td class="text-white font-semibold text-sm text-center"
|
||||
>C Volume</td
|
||||
>
|
||||
<td class="text-white font-semibold text-sm text-center"
|
||||
>P Volume</td
|
||||
>
|
||||
<td class=" font-semibold text-sm text-start">Date</td>
|
||||
<td class=" font-semibold text-sm text-end">% Change</td>
|
||||
<td class=" font-semibold text-sm text-end">P/C</td>
|
||||
<td class=" font-semibold text-sm text-center">Volume</td>
|
||||
<td class=" font-semibold text-sm text-center">C Volume</td>
|
||||
<td class=" font-semibold text-sm text-center">P Volume</td>
|
||||
<!--
|
||||
<td class="text-white font-semibold text-sm text-end"
|
||||
<td class=" font-semibold text-sm text-end"
|
||||
>Vol/30D</td
|
||||
>
|
||||
-->
|
||||
<!--
|
||||
<td class="text-white font-semibold text-sm text-end"
|
||||
<td class=" font-semibold text-sm text-end"
|
||||
>🐻/🐂 Prem</td
|
||||
>
|
||||
-->
|
||||
<td class="text-white font-semibold text-sm text-end"
|
||||
>Total OI</td
|
||||
>
|
||||
<td class="text-white font-semibold text-sm text-end"
|
||||
>OI Change</td
|
||||
>
|
||||
<td class="text-white font-semibold text-sm text-end"
|
||||
>% OI Change</td
|
||||
>
|
||||
<td class="text-white font-semibold text-sm text-end"
|
||||
>C Prem</td
|
||||
>
|
||||
<td class="text-white font-semibold text-sm text-end"
|
||||
>P Prem</td
|
||||
>
|
||||
<td class=" font-semibold text-sm text-end">Total OI</td>
|
||||
<td class=" font-semibold text-sm text-end">OI Change</td>
|
||||
<td class=" font-semibold text-sm text-end">% OI Change</td>
|
||||
<td class=" font-semibold text-sm text-end">C Prem</td>
|
||||
<td class=" font-semibold text-sm text-end">P Prem</td>
|
||||
<!--
|
||||
<td class="text-white font-semibold text-sm text-end"
|
||||
<td class=" font-semibold text-sm text-end"
|
||||
>Net Prem</td
|
||||
>
|
||||
<td class="text-white font-semibold text-sm text-end"
|
||||
<td class=" font-semibold text-sm text-end"
|
||||
>Total Prem</td
|
||||
>
|
||||
-->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each ["Pro"]?.includes(data?.user?.tier) ? optionList : optionList?.slice(0, 3) as item, index}
|
||||
{#each data?.user?.tier === "Pro" ? optionList : optionList?.slice(0, 3) as item, index}
|
||||
<tr
|
||||
class="dark:sm:hover:bg-[#245073]/10 odd:bg-[#F6F7F8] dark:odd:bg-oddborder-b border-gray-800 {index +
|
||||
class="dark:sm:hover:bg-[#245073]/10 odd:bg-[#F6F7F8] dark:odd:bg-odd {index +
|
||||
1 ===
|
||||
optionList?.slice(0, 3)?.length &&
|
||||
!['Pro']?.includes(data?.user?.tier)
|
||||
? 'opacity-[0.1]'
|
||||
: ''}"
|
||||
>
|
||||
<td class="text-white text-sm sm:text-[1rem] text-start">
|
||||
<td class=" text-sm sm:text-[1rem] text-start">
|
||||
{formatDate(item?.date)}
|
||||
</td>
|
||||
|
||||
<td class="text-white text-sm sm:text-[1rem] text-end">
|
||||
<td class=" text-sm sm:text-[1rem] text-end">
|
||||
{#if item?.changesPercentage >= 0 && item?.changesPercentage !== null}
|
||||
<span class="text-green-700 dark:text-[#00FC50]"
|
||||
>+{item?.changesPercentage >= 1000
|
||||
? abbreviateNumberWithColor(
|
||||
item?.changesPercentage,
|
||||
)
|
||||
? abbreviateNumber(item?.changesPercentage)
|
||||
: item?.changesPercentage?.toFixed(2)}%</span
|
||||
>
|
||||
{:else if item?.changesPercentage < 0 && item?.changesPercentage !== null}
|
||||
<span class="text-red-700 dark:text-[#FF2F1F]"
|
||||
>{item?.changesPercentage <= -1000
|
||||
? abbreviateNumberWithColor(
|
||||
item?.changesPercentage,
|
||||
)
|
||||
? abbreviateNumber(item?.changesPercentage)
|
||||
: item?.changesPercentage?.toFixed(2)}%
|
||||
</span>
|
||||
{:else}
|
||||
@ -452,11 +233,11 @@
|
||||
{/if}
|
||||
</td>
|
||||
|
||||
<td class="text-sm sm:text-[1rem] text-end text-white">
|
||||
<td class="text-sm sm:text-[1rem] text-end">
|
||||
{item?.putCallRatio}
|
||||
</td>
|
||||
|
||||
<td class="text-sm sm:text-[1rem] text-end text-white">
|
||||
<td class="text-sm sm:text-[1rem] text-end">
|
||||
{item?.volume?.toLocaleString("en-US")}
|
||||
</td>
|
||||
|
||||
@ -472,7 +253,7 @@
|
||||
{item?.put_volume?.toLocaleString("en-US")}
|
||||
</td>
|
||||
<!--
|
||||
<td class="text-sm sm:text-[1rem] text-white text-end">
|
||||
<td class="text-sm sm:text-[1rem] text-end">
|
||||
{item?.avgVolumeRatio?.toFixed(2)}
|
||||
</td>
|
||||
-->
|
||||
@ -520,24 +301,24 @@
|
||||
>
|
||||
<div class="flex justify-between space-x-4">
|
||||
<div
|
||||
class="space-y-1 flex flex-col items-start text-white"
|
||||
class="space-y-1 flex flex-col items-start "
|
||||
>
|
||||
<div>
|
||||
Bearish: {@html abbreviateNumberWithColor(
|
||||
Bearish: {@html abbreviateNumber(
|
||||
item?.premium_ratio[0],
|
||||
false,
|
||||
true,
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
Neutral: {@html abbreviateNumberWithColor(
|
||||
Neutral: {@html abbreviateNumber(
|
||||
item?.premium_ratio[1],
|
||||
false,
|
||||
true,
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
Bullish: {@html abbreviateNumberWithColor(
|
||||
Bullish: {@html abbreviateNumber(
|
||||
item?.premium_ratio[2],
|
||||
false,
|
||||
true,
|
||||
@ -550,15 +331,15 @@
|
||||
</td>
|
||||
-->
|
||||
|
||||
<td class="text-sm sm:text-[1rem] text-end text-white">
|
||||
{@html abbreviateNumberWithColor(
|
||||
<td class="text-sm sm:text-[1rem] text-end">
|
||||
{@html abbreviateNumber(
|
||||
item?.total_open_interest,
|
||||
false,
|
||||
true,
|
||||
)}
|
||||
</td>
|
||||
|
||||
<td class="text-white text-sm sm:text-[1rem] text-end">
|
||||
<td class=" text-sm sm:text-[1rem] text-end">
|
||||
{#if item?.changeOI >= 0}
|
||||
<span class="text-green-700 dark:text-[#00FC50]"
|
||||
>+{item?.changeOI?.toLocaleString("en-US")}</span
|
||||
@ -568,58 +349,50 @@
|
||||
>{item?.changeOI?.toLocaleString("en-US")}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="text-white"> n/a </span>
|
||||
<span class=""> n/a </span>
|
||||
{/if}
|
||||
</td>
|
||||
|
||||
<td class="text-white text-sm sm:text-[1rem] text-end">
|
||||
<td class=" text-sm sm:text-[1rem] text-end">
|
||||
{#if item?.changesPercentageOI >= 0}
|
||||
<span class="text-green-700 dark:text-[#00FC50]"
|
||||
>+{item?.changesPercentageOI >= 1000
|
||||
? abbreviateNumberWithColor(
|
||||
item?.changesPercentageOI,
|
||||
)
|
||||
? abbreviateNumber(item?.changesPercentageOI)
|
||||
: item?.changesPercentageOI?.toFixed(2)}%</span
|
||||
>
|
||||
{:else if item?.changesPercentageOI < 0}
|
||||
<span class="text-red-700 dark:text-[#FF2F1F]"
|
||||
>{item?.changesPercentageOI <= -1000
|
||||
? abbreviateNumberWithColor(
|
||||
item?.changesPercentageOI,
|
||||
)
|
||||
? abbreviateNumber(item?.changesPercentageOI)
|
||||
: item?.changesPercentageOI?.toFixed(2)}%
|
||||
</span>
|
||||
{:else}
|
||||
<span class="text-white"> n/a </span>
|
||||
<span class=""> n/a </span>
|
||||
{/if}
|
||||
</td>
|
||||
|
||||
<td class="text-sm sm:text-[1rem] text-end text-white">
|
||||
{@html abbreviateNumberWithColor(
|
||||
<td class="text-sm sm:text-[1rem] text-end">
|
||||
{@html abbreviateNumber(
|
||||
item?.call_premium,
|
||||
false,
|
||||
true,
|
||||
)}
|
||||
</td>
|
||||
|
||||
<td class="text-sm sm:text-[1rem] text-end text-white">
|
||||
{@html abbreviateNumberWithColor(
|
||||
item?.put_premium,
|
||||
false,
|
||||
true,
|
||||
)}
|
||||
<td class="text-sm sm:text-[1rem] text-end">
|
||||
{@html abbreviateNumber(item?.put_premium, false, true)}
|
||||
</td>
|
||||
<!--
|
||||
<td class="text-sm sm:text-[1rem] text-end text-white">
|
||||
{@html abbreviateNumberWithColor(
|
||||
<td class="text-sm sm:text-[1rem] text-end ">
|
||||
{@html abbreviateNumber(
|
||||
item?.net_premium,
|
||||
false,
|
||||
true,
|
||||
)}
|
||||
</td>
|
||||
|
||||
<td class="text-sm sm:text-[1rem] text-end text-white">
|
||||
{@html abbreviateNumberWithColor(
|
||||
<td class="text-sm sm:text-[1rem] text-end ">
|
||||
{@html abbreviateNumber(
|
||||
item?.total_premium,
|
||||
false,
|
||||
true,
|
||||
|
||||
@ -1166,7 +1166,7 @@
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<h3 class="text-2xl md:text-3xl font-bold">Lifetime</h3>
|
||||
<div>
|
||||
<span class="text-3xl md:text-4xl font-bold">$599</span>
|
||||
<span class="text-3xl md:text-4xl font-bold">$799</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class=" md:text-lg mt-4 lg:mt-2">
|
||||
@ -1196,24 +1196,30 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative flex flex-col items-center">
|
||||
<div
|
||||
class="h-4 w-4 rounded-full bg-blue-500 relative z-10"
|
||||
></div>
|
||||
<div class="mt-2 text-center">
|
||||
<div class="text-lg sm:text-xl font-semibold">
|
||||
<span class="text-zinc-900 dark:text-white">$599</span>
|
||||
</div>
|
||||
<div class="text-xs sm:text-sm">First 100 users</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative flex flex-col items-center">
|
||||
<div
|
||||
class="h-4 w-4 rounded-full bg-zinc-500 dark:bg-gray-600 relative z-10"
|
||||
></div>
|
||||
<div class="mt-2 text-center">
|
||||
<div class="text-lg sm:text-xl font-semibold">
|
||||
<span class="text-zinc-500 dark:text-zinc-400">$799</span>
|
||||
<span class="text-gray-800 dark:text-gray-300 line-through"
|
||||
>$599</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="text-xs sm:text-sm text-gray-800 dark:text-gray-300"
|
||||
>
|
||||
First 100 users
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative flex flex-col items-center">
|
||||
<div
|
||||
class="h-4 w-4 rounded-full bg-blue-500 relative z-10"
|
||||
></div>
|
||||
<div class="mt-2 text-center">
|
||||
<div class="text-lg sm:text-xl font-semibold">
|
||||
<span class="">$799</span>
|
||||
</div>
|
||||
<div
|
||||
class="text-xs sm:text-sm text-gray-800 dark:text-gray-300"
|
||||
|
||||
@ -788,11 +788,16 @@
|
||||
class="flex flex-col border-b border-gray-300 dark:border-gray-800 py-1 sm:table-row sm:py-0"
|
||||
><td
|
||||
class="whitespace-nowrap px-0.5 py-[1px] xs:px-1 text-sm sm:text-[1rem]"
|
||||
>Revenue (ttm)</td
|
||||
><a
|
||||
href={`/stocks/${$stockTicker}/statistics/revenue`}
|
||||
class="sm:hover:text-muted dark:sm:hover:text-blue-400 underline underline-offset-4"
|
||||
>Revenue (ttm)</a
|
||||
></td
|
||||
>
|
||||
<td
|
||||
class="whitespace-nowrap px-0.5 py-[1px] text-left text-sm text-[1rem] font-semibold dark:font-normal xs:px-1 sm:text-right"
|
||||
>{@html stockDeck?.revenueTTM !== null &&
|
||||
>
|
||||
{@html stockDeck?.revenueTTM !== null &&
|
||||
stockDeck?.revenueTTM !== 0
|
||||
? abbreviateNumber(stockDeck?.revenueTTM, false, true)
|
||||
: "n/a"}</td
|
||||
|
||||
@ -237,7 +237,7 @@
|
||||
<span>Revenue (ttm)</span>
|
||||
</div>
|
||||
<div class="flex items-baseline">
|
||||
<span class="text-xl font-bold">
|
||||
<span class="text-xl font-semibold">
|
||||
{abbreviateNumber(rawData?.revenue, true)}</span
|
||||
>
|
||||
</div>
|
||||
@ -250,7 +250,7 @@
|
||||
<span>Revenue Growth</span>
|
||||
</div>
|
||||
<div class="flex items-baseline">
|
||||
<span class="text-xl font-bold"
|
||||
<span class="text-xl font-semibold"
|
||||
>{rawData?.growthRevenue}%</span
|
||||
>
|
||||
</div>
|
||||
@ -263,7 +263,7 @@
|
||||
<span>Price / Sales Ratio</span>
|
||||
</div>
|
||||
<div class="flex items-baseline">
|
||||
<span class="text-xl font-bold"
|
||||
<span class="text-xl font-semibold"
|
||||
>{rawData?.priceToSalesRatio}</span
|
||||
>
|
||||
</div>
|
||||
@ -276,7 +276,7 @@
|
||||
<span>Revenue / Employee </span>
|
||||
</div>
|
||||
<div class="flex items-baseline">
|
||||
<span class="text-xl font-bold"
|
||||
<span class="text-xl font-semibold"
|
||||
>{abbreviateNumber(
|
||||
rawData?.revenuePerEmployee,
|
||||
true,
|
||||
@ -292,7 +292,7 @@
|
||||
<span>Employees </span>
|
||||
</div>
|
||||
<div class="flex items-baseline">
|
||||
<span class="text-xl font-bold"
|
||||
<span class="text-xl font-semibold"
|
||||
>{rawData?.employees?.toLocaleString("en-US")}</span
|
||||
>
|
||||
</div>
|
||||
@ -304,7 +304,7 @@
|
||||
<span>Market Cap </span>
|
||||
</div>
|
||||
<div class="flex items-baseline">
|
||||
<span class="text-xl font-bold"
|
||||
<span class="text-xl font-semibold"
|
||||
>{abbreviateNumber(data?.getStockQuote?.marketCap)}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
@ -1377,9 +1377,9 @@
|
||||
on:input={handleInput}
|
||||
autocomplete="off"
|
||||
autofocus=""
|
||||
class="text-sm w-full bg-white dark:bg-default border-0 focus:border-gray-200 focus:ring-0 placeholder:text-gray-300 pr-8"
|
||||
class="text-sm w-full bg-white dark:bg-default border-0 focus:border-gray-200 focus:ring-0 focus:outline-none placeholder:text-gray-400 dark:placeholder:text-gray-600 pr-8"
|
||||
type="text"
|
||||
placeholder=""
|
||||
placeholder="Search indicators..."
|
||||
/>
|
||||
|
||||
<!-- Clear Button - Shown only when searchQuery has input -->
|
||||
|
||||
@ -5,7 +5,6 @@ module.exports = {
|
||||
safelist: ["dark"],
|
||||
content: [
|
||||
"./src/**/*.{html,js,svelte,ts}",
|
||||
"./node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}",
|
||||
],
|
||||
theme: {
|
||||
container: {
|
||||
@ -194,5 +193,4 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("flowbite/plugin")],
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user