Spaces:
Running
Running
Oviya
commited on
Commit
·
07cf142
1
Parent(s):
dd4142a
Serve app via redirect to dist/Pytrade/browser
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .editorconfig +17 -0
- CHANGELOG.md +16 -0
- Pytrade.esproj +10 -0
- Pytrade.sln +27 -0
- angular.json +110 -0
- index.html +7 -16
- karma.conf.js +44 -0
- obj/Debug/package.g.props +49 -0
- package-lock.json +0 -0
- package.json +54 -0
- public/44965755.jpg +3 -0
- public/bgimg.png +3 -0
- public/bgkey-opacity.png +3 -0
- public/bgkey.png +3 -0
- public/bgvideo.mp4 +3 -0
- public/chatbot.png +3 -0
- public/desktop_img.png +3 -0
- public/favicon.ico +3 -0
- public/featureimg.jpg +3 -0
- public/filter-img.png +3 -0
- public/final.png +3 -0
- public/logo.png +3 -0
- public/marketselect.jpg +3 -0
- public/marketselect.mp4 +3 -0
- public/yfinance.png +3 -0
- src/app/analysispage/analysispage.html +291 -0
- src/app/analysispage/analysispage.scss +156 -0
- src/app/analysispage/analysispage.spec.ts +23 -0
- src/app/analysispage/analysispage.ts +226 -0
- src/app/analysispage/chart.service.ts +558 -0
- src/app/app-module.ts +49 -0
- src/app/app-routing-module.ts +28 -0
- src/app/app.html +19 -0
- src/app/app.scss +52 -0
- src/app/app.spec.ts +29 -0
- src/app/app.ts +11 -0
- src/app/chatbot/chatbot.html +10 -0
- src/app/chatbot/chatbot.scss +133 -0
- src/app/chatbot/chatbot.spec.ts +23 -0
- src/app/chatbot/chatbot.ts +49 -0
- src/app/homepage/homepage.html +149 -0
- src/app/homepage/homepage.scss +217 -0
- src/app/homepage/homepage.spec.ts +23 -0
- src/app/homepage/homepage.ts +34 -0
- src/app/marketselect/market.service.ts +24 -0
- src/app/marketselect/marketselect.html +78 -0
- src/app/marketselect/marketselect.scss +201 -0
- src/app/marketselect/marketselect.spec.ts +23 -0
- src/app/marketselect/marketselect.ts +173 -0
- src/app/screenerpage/screenerpage.css +83 -0
.editorconfig
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Editor configuration, see https://editorconfig.org
|
| 2 |
+
root = true
|
| 3 |
+
|
| 4 |
+
[*]
|
| 5 |
+
charset = utf-8
|
| 6 |
+
indent_style = space
|
| 7 |
+
indent_size = 2
|
| 8 |
+
insert_final_newline = true
|
| 9 |
+
trim_trailing_whitespace = true
|
| 10 |
+
|
| 11 |
+
[*.ts]
|
| 12 |
+
quote_type = single
|
| 13 |
+
ij_typescript_use_double_quotes = false
|
| 14 |
+
|
| 15 |
+
[*.md]
|
| 16 |
+
max_line_length = off
|
| 17 |
+
trim_trailing_whitespace = false
|
CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
This file explains how Visual Studio created the project.
|
| 2 |
+
|
| 3 |
+
The following tools were used to generate this project:
|
| 4 |
+
- Angular CLI (ng)
|
| 5 |
+
|
| 6 |
+
The following steps were used to generate this project:
|
| 7 |
+
- Create Angular project with ng: `ng new Pytrade --defaults --skip-install --skip-git --no-standalone `.
|
| 8 |
+
- Update angular.json with port.
|
| 9 |
+
- Create project file (`Pytrade.esproj`).
|
| 10 |
+
- Create `launch.json` to enable debugging.
|
| 11 |
+
- Update package.json to add `jest-editor-support`.
|
| 12 |
+
- Update `start` script in `package.json` to specify host.
|
| 13 |
+
- Add `karma.conf.js` for unit tests.
|
| 14 |
+
- Update `angular.json` to point to `karma.conf.js`.
|
| 15 |
+
- Add project to solution.
|
| 16 |
+
- Write this file.
|
Pytrade.esproj
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<Project Sdk="Microsoft.VisualStudio.JavaScript.Sdk/1.0.2752196">
|
| 2 |
+
<PropertyGroup>
|
| 3 |
+
<StartupCommand>npm start</StartupCommand>
|
| 4 |
+
<JavaScriptTestFramework>Jasmine</JavaScriptTestFramework>
|
| 5 |
+
<!-- Allows the build (or compile) script located on package.json to run on Build -->
|
| 6 |
+
<ShouldRunBuildScript>false</ShouldRunBuildScript>
|
| 7 |
+
<!-- Folder where production build objects will be placed -->
|
| 8 |
+
<BuildOutputFolder>$(MSBuildProjectDirectory)\dist\Pytrade\browser\</BuildOutputFolder>
|
| 9 |
+
</PropertyGroup>
|
| 10 |
+
</Project>
|
Pytrade.sln
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Microsoft Visual Studio Solution File, Format Version 12.00
|
| 3 |
+
# Visual Studio Version 17
|
| 4 |
+
VisualStudioVersion = 17.14.36301.6 d17.14
|
| 5 |
+
MinimumVisualStudioVersion = 10.0.40219.1
|
| 6 |
+
Project("{54A90642-561A-4BB1-A94E-469ADEE60C69}") = "Pytrade", "Pytrade.esproj", "{54209CD0-4BE7-905C-7951-7A10E456A6D9}"
|
| 7 |
+
EndProject
|
| 8 |
+
Global
|
| 9 |
+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
| 10 |
+
Debug|Any CPU = Debug|Any CPU
|
| 11 |
+
Release|Any CPU = Release|Any CPU
|
| 12 |
+
EndGlobalSection
|
| 13 |
+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
| 14 |
+
{54209CD0-4BE7-905C-7951-7A10E456A6D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
| 15 |
+
{54209CD0-4BE7-905C-7951-7A10E456A6D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
| 16 |
+
{54209CD0-4BE7-905C-7951-7A10E456A6D9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
| 17 |
+
{54209CD0-4BE7-905C-7951-7A10E456A6D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
| 18 |
+
{54209CD0-4BE7-905C-7951-7A10E456A6D9}.Release|Any CPU.Build.0 = Release|Any CPU
|
| 19 |
+
{54209CD0-4BE7-905C-7951-7A10E456A6D9}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
| 20 |
+
EndGlobalSection
|
| 21 |
+
GlobalSection(SolutionProperties) = preSolution
|
| 22 |
+
HideSolutionNode = FALSE
|
| 23 |
+
EndGlobalSection
|
| 24 |
+
GlobalSection(ExtensibilityGlobals) = postSolution
|
| 25 |
+
SolutionGuid = {9D215C41-589A-46E9-ABAA-BB49FAEA1A1F}
|
| 26 |
+
EndGlobalSection
|
| 27 |
+
EndGlobal
|
angular.json
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
| 3 |
+
"version": 1,
|
| 4 |
+
"newProjectRoot": "projects",
|
| 5 |
+
"projects": {
|
| 6 |
+
"Pytrade": {
|
| 7 |
+
"projectType": "application",
|
| 8 |
+
"schematics": {
|
| 9 |
+
"@schematics/angular:component": {
|
| 10 |
+
"standalone": false
|
| 11 |
+
},
|
| 12 |
+
"@schematics/angular:directive": {
|
| 13 |
+
"standalone": false
|
| 14 |
+
},
|
| 15 |
+
"@schematics/angular:pipe": {
|
| 16 |
+
"standalone": false
|
| 17 |
+
}
|
| 18 |
+
},
|
| 19 |
+
"root": "",
|
| 20 |
+
"sourceRoot": "src",
|
| 21 |
+
"prefix": "app",
|
| 22 |
+
"architect": {
|
| 23 |
+
"build": {
|
| 24 |
+
"builder": "@angular/build:application",
|
| 25 |
+
"options": {
|
| 26 |
+
"browser": "src/main.ts",
|
| 27 |
+
"polyfills": [
|
| 28 |
+
"zone.js"
|
| 29 |
+
],
|
| 30 |
+
"tsConfig": "tsconfig.app.json",
|
| 31 |
+
"assets": [
|
| 32 |
+
{
|
| 33 |
+
"glob": "**/*",
|
| 34 |
+
"input": "public"
|
| 35 |
+
}
|
| 36 |
+
],
|
| 37 |
+
"styles": [
|
| 38 |
+
"src/custom-theme.scss",
|
| 39 |
+
"src/styles.css"
|
| 40 |
+
]
|
| 41 |
+
},
|
| 42 |
+
"configurations": {
|
| 43 |
+
"production": {
|
| 44 |
+
"budgets": [
|
| 45 |
+
{
|
| 46 |
+
"type": "initial",
|
| 47 |
+
"maximumWarning": "500kB",
|
| 48 |
+
"maximumError": "1MB"
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"type": "anyComponentStyle",
|
| 52 |
+
"maximumWarning": "4kB",
|
| 53 |
+
"maximumError": "8kB"
|
| 54 |
+
}
|
| 55 |
+
],
|
| 56 |
+
"outputHashing": "all"
|
| 57 |
+
},
|
| 58 |
+
"development": {
|
| 59 |
+
"optimization": false,
|
| 60 |
+
"extractLicenses": false,
|
| 61 |
+
"sourceMap": true
|
| 62 |
+
}
|
| 63 |
+
},
|
| 64 |
+
"defaultConfiguration": "production"
|
| 65 |
+
},
|
| 66 |
+
"serve": {
|
| 67 |
+
"builder": "@angular/build:dev-server",
|
| 68 |
+
"configurations": {
|
| 69 |
+
"production": {
|
| 70 |
+
"buildTarget": "Pytrade:build:production"
|
| 71 |
+
},
|
| 72 |
+
"development": {
|
| 73 |
+
"buildTarget": "Pytrade:build:development"
|
| 74 |
+
}
|
| 75 |
+
},
|
| 76 |
+
"defaultConfiguration": "development",
|
| 77 |
+
"options": {
|
| 78 |
+
"port": 57205
|
| 79 |
+
}
|
| 80 |
+
},
|
| 81 |
+
"extract-i18n": {
|
| 82 |
+
"builder": "@angular/build:extract-i18n"
|
| 83 |
+
},
|
| 84 |
+
"test": {
|
| 85 |
+
"builder": "@angular/build:karma",
|
| 86 |
+
"options": {
|
| 87 |
+
"polyfills": [
|
| 88 |
+
"zone.js",
|
| 89 |
+
"zone.js/testing"
|
| 90 |
+
],
|
| 91 |
+
"tsConfig": "tsconfig.spec.json",
|
| 92 |
+
"assets": [
|
| 93 |
+
{
|
| 94 |
+
"glob": "**/*",
|
| 95 |
+
"input": "public"
|
| 96 |
+
}
|
| 97 |
+
],
|
| 98 |
+
"styles": [
|
| 99 |
+
"src/styles.css"
|
| 100 |
+
],
|
| 101 |
+
"karmaConfig": "karma.conf.js"
|
| 102 |
+
}
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
},
|
| 107 |
+
"cli": {
|
| 108 |
+
"analytics": "eab27a52-ee79-4cb0-aa82-d323da3c6b06"
|
| 109 |
+
}
|
| 110 |
+
}
|
index.html
CHANGED
|
@@ -1,19 +1,10 @@
|
|
| 1 |
<!doctype html>
|
| 2 |
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
<div class="card">
|
| 11 |
-
<h1>Welcome to your static Space!</h1>
|
| 12 |
-
<p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
|
| 13 |
-
<p>
|
| 14 |
-
Also don't forget to check the
|
| 15 |
-
<a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
|
| 16 |
-
</p>
|
| 17 |
-
</div>
|
| 18 |
-
</body>
|
| 19 |
</html>
|
|
|
|
| 1 |
<!doctype html>
|
| 2 |
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<title>Pytrade</title>
|
| 6 |
+
<meta http-equiv="refresh" content="0; url=./dist/Pytrade/browser/">
|
| 7 |
+
<script>window.location.replace('./dist/Pytrade/browser/');</script>
|
| 8 |
+
</head>
|
| 9 |
+
<body></body>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
</html>
|
karma.conf.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
module.exports = function (config) {
|
| 2 |
+
config.set({
|
| 3 |
+
basePath: '',
|
| 4 |
+
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
| 5 |
+
plugins: [
|
| 6 |
+
require('karma-jasmine'),
|
| 7 |
+
require('karma-chrome-launcher'),
|
| 8 |
+
require('karma-jasmine-html-reporter'),
|
| 9 |
+
require('karma-coverage'),
|
| 10 |
+
require('@angular-devkit/build-angular/plugins/karma')
|
| 11 |
+
],
|
| 12 |
+
client: {
|
| 13 |
+
jasmine: {
|
| 14 |
+
// you can add configuration options for Jasmine here
|
| 15 |
+
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
| 16 |
+
// for example, you can disable the random execution with `random: false`
|
| 17 |
+
// or set a specific seed with `seed: 4321`
|
| 18 |
+
},
|
| 19 |
+
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
| 20 |
+
},
|
| 21 |
+
jasmineHtmlReporter: {
|
| 22 |
+
suppressAll: true // removes the duplicated traces
|
| 23 |
+
},
|
| 24 |
+
coverageReporter: {
|
| 25 |
+
dir: require('path').join(__dirname, './coverage/'),
|
| 26 |
+
subdir: '.',
|
| 27 |
+
reporters: [
|
| 28 |
+
{ type: 'html' },
|
| 29 |
+
{ type: 'text-summary' }
|
| 30 |
+
]
|
| 31 |
+
},
|
| 32 |
+
reporters: ['progress', 'kjhtml'],
|
| 33 |
+
port: 9876,
|
| 34 |
+
colors: true,
|
| 35 |
+
logLevel: config.LOG_INFO,
|
| 36 |
+
autoWatch: true,
|
| 37 |
+
browsers: ['Chrome'],
|
| 38 |
+
singleRun: false,
|
| 39 |
+
restartOnFileChange: true,
|
| 40 |
+
listenAddress: 'localhost',
|
| 41 |
+
hostname: 'localhost'
|
| 42 |
+
});
|
| 43 |
+
};
|
| 44 |
+
|
obj/Debug/package.g.props
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="utf-8"?>
|
| 2 |
+
<Project>
|
| 3 |
+
<PropertyGroup>
|
| 4 |
+
<PackageJsonName Condition="$(PackageJsonName) == ''">pytrade</PackageJsonName>
|
| 5 |
+
<PackageJsonVersion Condition="$(PackageJsonVersion) == ''">0.0.0</PackageJsonVersion>
|
| 6 |
+
<PackageJsonScriptsNg Condition="$(PackageJsonScriptsNg) == ''">ng</PackageJsonScriptsNg>
|
| 7 |
+
<PackageJsonScriptsStart Condition="$(PackageJsonScriptsStart) == ''">ng serve --host=127.0.0.1</PackageJsonScriptsStart>
|
| 8 |
+
<PackageJsonScriptsBuild Condition="$(PackageJsonScriptsBuild) == ''">ng build</PackageJsonScriptsBuild>
|
| 9 |
+
<PackageJsonScriptsWatch Condition="$(PackageJsonScriptsWatch) == ''">ng build --watch --configuration development</PackageJsonScriptsWatch>
|
| 10 |
+
<PackageJsonScriptsTest Condition="$(PackageJsonScriptsTest) == ''">ng test</PackageJsonScriptsTest>
|
| 11 |
+
<PackageJsonPrettierPrintwidth Condition="$(PackageJsonPrettierPrintwidth) == ''">100</PackageJsonPrettierPrintwidth>
|
| 12 |
+
<PackageJsonPrettierSinglequote Condition="$(PackageJsonPrettierSinglequote) == ''">true</PackageJsonPrettierSinglequote>
|
| 13 |
+
<PackageJsonPrettierOverrides Condition="$(PackageJsonPrettierOverrides) == ''">[
|
| 14 |
+
{
|
| 15 |
+
"files": "*.html",
|
| 16 |
+
"options": {
|
| 17 |
+
"parser": "angular"
|
| 18 |
+
}
|
| 19 |
+
}
|
| 20 |
+
]</PackageJsonPrettierOverrides>
|
| 21 |
+
<PackageJsonPrivate Condition="$(PackageJsonPrivate) == ''">true</PackageJsonPrivate>
|
| 22 |
+
<PackageJsonDependenciesAngularCdk Condition="$(PackageJsonDependenciesAngularCdk) == ''">^20.2.1</PackageJsonDependenciesAngularCdk>
|
| 23 |
+
<PackageJsonDependenciesAngularCommon Condition="$(PackageJsonDependenciesAngularCommon) == ''">^20.2.0</PackageJsonDependenciesAngularCommon>
|
| 24 |
+
<PackageJsonDependenciesAngularCompiler Condition="$(PackageJsonDependenciesAngularCompiler) == ''">^20.2.0</PackageJsonDependenciesAngularCompiler>
|
| 25 |
+
<PackageJsonDependenciesAngularCore Condition="$(PackageJsonDependenciesAngularCore) == ''">^20.2.0</PackageJsonDependenciesAngularCore>
|
| 26 |
+
<PackageJsonDependenciesAngularForms Condition="$(PackageJsonDependenciesAngularForms) == ''">^20.2.0</PackageJsonDependenciesAngularForms>
|
| 27 |
+
<PackageJsonDependenciesAngularMaterial Condition="$(PackageJsonDependenciesAngularMaterial) == ''">^20.2.1</PackageJsonDependenciesAngularMaterial>
|
| 28 |
+
<PackageJsonDependenciesAngularPlatformBrowser Condition="$(PackageJsonDependenciesAngularPlatformBrowser) == ''">^20.2.0</PackageJsonDependenciesAngularPlatformBrowser>
|
| 29 |
+
<PackageJsonDependenciesAngularRouter Condition="$(PackageJsonDependenciesAngularRouter) == ''">^20.2.0</PackageJsonDependenciesAngularRouter>
|
| 30 |
+
<PackageJsonDependenciesApexcharts Condition="$(PackageJsonDependenciesApexcharts) == ''">^5.3.4</PackageJsonDependenciesApexcharts>
|
| 31 |
+
<PackageJsonDependenciesAxios Condition="$(PackageJsonDependenciesAxios) == ''">^1.11.0</PackageJsonDependenciesAxios>
|
| 32 |
+
<PackageJsonDependenciesJestEditorSupport Condition="$(PackageJsonDependenciesJestEditorSupport) == ''">*</PackageJsonDependenciesJestEditorSupport>
|
| 33 |
+
<PackageJsonDependenciesNgApexcharts Condition="$(PackageJsonDependenciesNgApexcharts) == ''">^2.0.1</PackageJsonDependenciesNgApexcharts>
|
| 34 |
+
<PackageJsonDependenciesRxjs Condition="$(PackageJsonDependenciesRxjs) == ''">~7.8.0</PackageJsonDependenciesRxjs>
|
| 35 |
+
<PackageJsonDependenciesTslib Condition="$(PackageJsonDependenciesTslib) == ''">^2.3.0</PackageJsonDependenciesTslib>
|
| 36 |
+
<PackageJsonDependenciesZoneJs Condition="$(PackageJsonDependenciesZoneJs) == ''">~0.15.0</PackageJsonDependenciesZoneJs>
|
| 37 |
+
<PackageJsonDevdependenciesAngularBuild Condition="$(PackageJsonDevdependenciesAngularBuild) == ''">^20.2.0</PackageJsonDevdependenciesAngularBuild>
|
| 38 |
+
<PackageJsonDevdependenciesAngularCli Condition="$(PackageJsonDevdependenciesAngularCli) == ''">^20.2.0</PackageJsonDevdependenciesAngularCli>
|
| 39 |
+
<PackageJsonDevdependenciesAngularCompilerCli Condition="$(PackageJsonDevdependenciesAngularCompilerCli) == ''">^20.2.0</PackageJsonDevdependenciesAngularCompilerCli>
|
| 40 |
+
<PackageJsonDevdependenciesTypesJasmine Condition="$(PackageJsonDevdependenciesTypesJasmine) == ''">~5.1.0</PackageJsonDevdependenciesTypesJasmine>
|
| 41 |
+
<PackageJsonDevdependenciesJasmineCore Condition="$(PackageJsonDevdependenciesJasmineCore) == ''">~5.9.0</PackageJsonDevdependenciesJasmineCore>
|
| 42 |
+
<PackageJsonDevdependenciesKarma Condition="$(PackageJsonDevdependenciesKarma) == ''">~6.4.0</PackageJsonDevdependenciesKarma>
|
| 43 |
+
<PackageJsonDevdependenciesKarmaChromeLauncher Condition="$(PackageJsonDevdependenciesKarmaChromeLauncher) == ''">~3.2.0</PackageJsonDevdependenciesKarmaChromeLauncher>
|
| 44 |
+
<PackageJsonDevdependenciesKarmaCoverage Condition="$(PackageJsonDevdependenciesKarmaCoverage) == ''">~2.2.0</PackageJsonDevdependenciesKarmaCoverage>
|
| 45 |
+
<PackageJsonDevdependenciesKarmaJasmine Condition="$(PackageJsonDevdependenciesKarmaJasmine) == ''">~5.1.0</PackageJsonDevdependenciesKarmaJasmine>
|
| 46 |
+
<PackageJsonDevdependenciesKarmaJasmineHtmlReporter Condition="$(PackageJsonDevdependenciesKarmaJasmineHtmlReporter) == ''">~2.1.0</PackageJsonDevdependenciesKarmaJasmineHtmlReporter>
|
| 47 |
+
<PackageJsonDevdependenciesTypescript Condition="$(PackageJsonDevdependenciesTypescript) == ''">~5.9.2</PackageJsonDevdependenciesTypescript>
|
| 48 |
+
</PropertyGroup>
|
| 49 |
+
</Project>
|
package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "pytrade",
|
| 3 |
+
"version": "0.0.0",
|
| 4 |
+
"scripts": {
|
| 5 |
+
"ng": "ng",
|
| 6 |
+
"start": "ng serve --host=127.0.0.1",
|
| 7 |
+
"build": "ng build",
|
| 8 |
+
"watch": "ng build --watch --configuration development",
|
| 9 |
+
"test": "ng test"
|
| 10 |
+
},
|
| 11 |
+
"prettier": {
|
| 12 |
+
"printWidth": 100,
|
| 13 |
+
"singleQuote": true,
|
| 14 |
+
"overrides": [
|
| 15 |
+
{
|
| 16 |
+
"files": "*.html",
|
| 17 |
+
"options": {
|
| 18 |
+
"parser": "angular"
|
| 19 |
+
}
|
| 20 |
+
}
|
| 21 |
+
]
|
| 22 |
+
},
|
| 23 |
+
"private": true,
|
| 24 |
+
"dependencies": {
|
| 25 |
+
"@angular/cdk": "^20.2.1",
|
| 26 |
+
"@angular/common": "^20.2.0",
|
| 27 |
+
"@angular/compiler": "^20.2.0",
|
| 28 |
+
"@angular/core": "^20.2.0",
|
| 29 |
+
"@angular/forms": "^20.2.0",
|
| 30 |
+
"@angular/material": "^20.2.1",
|
| 31 |
+
"@angular/platform-browser": "^20.2.0",
|
| 32 |
+
"@angular/router": "^20.2.0",
|
| 33 |
+
"apexcharts": "^5.3.4",
|
| 34 |
+
"axios": "^1.11.0",
|
| 35 |
+
"jest-editor-support": "*",
|
| 36 |
+
"ng-apexcharts": "^2.0.1",
|
| 37 |
+
"rxjs": "~7.8.0",
|
| 38 |
+
"tslib": "^2.3.0",
|
| 39 |
+
"zone.js": "~0.15.0"
|
| 40 |
+
},
|
| 41 |
+
"devDependencies": {
|
| 42 |
+
"@angular/build": "^20.2.0",
|
| 43 |
+
"@angular/cli": "^20.2.0",
|
| 44 |
+
"@angular/compiler-cli": "^20.2.0",
|
| 45 |
+
"@types/jasmine": "~5.1.0",
|
| 46 |
+
"jasmine-core": "~5.9.0",
|
| 47 |
+
"karma": "~6.4.0",
|
| 48 |
+
"karma-chrome-launcher": "~3.2.0",
|
| 49 |
+
"karma-coverage": "~2.2.0",
|
| 50 |
+
"karma-jasmine": "~5.1.0",
|
| 51 |
+
"karma-jasmine-html-reporter": "~2.1.0",
|
| 52 |
+
"typescript": "~5.9.2"
|
| 53 |
+
}
|
| 54 |
+
}
|
public/44965755.jpg
ADDED
|
Git LFS Details
|
public/bgimg.png
ADDED
|
Git LFS Details
|
public/bgkey-opacity.png
ADDED
|
Git LFS Details
|
public/bgkey.png
ADDED
|
Git LFS Details
|
public/bgvideo.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:61d87371c7239259e64e17998d41f2fe1bb52fd5d9dcaa419c887f9e51cae818
|
| 3 |
+
size 2263652
|
public/chatbot.png
ADDED
|
Git LFS Details
|
public/desktop_img.png
ADDED
|
Git LFS Details
|
public/favicon.ico
ADDED
|
|
Git LFS Details
|
public/featureimg.jpg
ADDED
|
Git LFS Details
|
public/filter-img.png
ADDED
|
Git LFS Details
|
public/final.png
ADDED
|
Git LFS Details
|
public/logo.png
ADDED
|
Git LFS Details
|
public/marketselect.jpg
ADDED
|
Git LFS Details
|
public/marketselect.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5e761f128092c0b54cc74bb95529c01593d9423e21e9b16771fe0da458b23188
|
| 3 |
+
size 2022987
|
public/yfinance.png
ADDED
|
Git LFS Details
|
src/app/analysispage/analysispage.html
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<div class="analyse-container">
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
<mat-card>
|
| 5 |
+
<h1>Overall Score Board</h1>
|
| 6 |
+
|
| 7 |
+
<table>
|
| 8 |
+
<thead>
|
| 9 |
+
<tr>
|
| 10 |
+
<th>Company Name</th>
|
| 11 |
+
<th>Ticker</th>
|
| 12 |
+
<th>Overall Signal</th>
|
| 13 |
+
<th>Overall Score</th>
|
| 14 |
+
<th>Technical Analysis Score (80%)</th>
|
| 15 |
+
<th>Fundamental Analysis Score (15%)</th>
|
| 16 |
+
<th>News Score (5%)</th>
|
| 17 |
+
</tr>
|
| 18 |
+
</thead>
|
| 19 |
+
<tbody id="resultsTable">
|
| 20 |
+
<tr *ngFor="let scoreboard of result;let i = index">
|
| 21 |
+
<td style="cursor:pointer"
|
| 22 |
+
(click)="selectCompany(i)"
|
| 23 |
+
[ngClass]="{ 'active-td': activeCompany === i }"
|
| 24 |
+
>{{scoreboard.company_name}}</td>
|
| 25 |
+
<td>{{scoreboard.ticker}}</td>
|
| 26 |
+
<td>
|
| 27 |
+
<div class="dot"
|
| 28 |
+
[ngClass]="getClass(scoreboard.combined_overall_signal)"></div>
|
| 29 |
+
</td>
|
| 30 |
+
<td>{{scoreboard.combined_overall_score}}</td>
|
| 31 |
+
<td>{{scoreboard.overall_ta_score}}</td>
|
| 32 |
+
<td>{{scoreboard.fundamental_analysis.overall_fa_score}}</td>
|
| 33 |
+
<td>{{scoreboard.news_overall_score}}</td>
|
| 34 |
+
</tr>
|
| 35 |
+
</tbody>
|
| 36 |
+
</table>
|
| 37 |
+
|
| 38 |
+
</mat-card>
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
<mat-card>
|
| 42 |
+
<h1>Close Price Chart</h1>
|
| 43 |
+
<div class="chart-container">
|
| 44 |
+
<div>
|
| 45 |
+
<apx-chart [series]="chartOptions.series"
|
| 46 |
+
[chart]="chartOptions.chart"
|
| 47 |
+
[xaxis]="chartOptions.xaxis"
|
| 48 |
+
[yaxis]="chartOptions.yaxis"></apx-chart>
|
| 49 |
+
</div>
|
| 50 |
+
<div>
|
| 51 |
+
<!-- Include ApexCharts in your HTML file -->
|
| 52 |
+
<apx-chart [series]="overallChart.series"
|
| 53 |
+
[chart]="overallChart.chart"
|
| 54 |
+
[labels]="overallChart.labels"
|
| 55 |
+
[plotOptions]="overallChart.plotOptions"
|
| 56 |
+
[fill]="overallChart.fill"
|
| 57 |
+
[tooltip]="overallChart.tooltip"
|
| 58 |
+
[legend]="overallChart.legend">
|
| 59 |
+
</apx-chart>
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
</div>
|
| 63 |
+
</div>
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
</mat-card>
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
<mat-card>
|
| 70 |
+
<h1>Trade Recommendations</h1>
|
| 71 |
+
<ng-container>
|
| 72 |
+
<table>
|
| 73 |
+
<thead>
|
| 74 |
+
<tr>
|
| 75 |
+
<th>Current Price</th>
|
| 76 |
+
<th>Entry Point</th>
|
| 77 |
+
<th>Stop Loss</th>
|
| 78 |
+
<th>Target Price</th>
|
| 79 |
+
</tr>
|
| 80 |
+
</thead>
|
| 81 |
+
<tbody>
|
| 82 |
+
<tr>
|
| 83 |
+
<td>
|
| 84 |
+
<p style="font-size:2vw;">{{result[activeCompany].live_price}}</p>
|
| 85 |
+
<p [ngStyle]="{'color': result[activeCompany].price_change > 0 ? 'green' : (result[activeCompany].price_change < 0 ? 'red' : 'green')}">{{result[activeCompany].price_change}} ({{result[activeCompany].percentage_change}}%)</p>
|
| 86 |
+
</td>
|
| 87 |
+
<td>{{result[activeCompany].tradingInfo.entry_point}}</td>
|
| 88 |
+
<td>{{result[activeCompany].tradingInfo.stop_loss}}</td>
|
| 89 |
+
<td>{{result[activeCompany].tradingInfo.target_price}}</td>
|
| 90 |
+
|
| 91 |
+
</tr>
|
| 92 |
+
</tbody>
|
| 93 |
+
</table>
|
| 94 |
+
|
| 95 |
+
<table>
|
| 96 |
+
<thead>
|
| 97 |
+
<tr>
|
| 98 |
+
<th>Pivot Points</th>
|
| 99 |
+
<th>Support</th>
|
| 100 |
+
<th>Resistance</th>
|
| 101 |
+
<th>Remarks</th>
|
| 102 |
+
</tr>
|
| 103 |
+
</thead>
|
| 104 |
+
<tbody>
|
| 105 |
+
<tr>
|
| 106 |
+
<td>
|
| 107 |
+
<div>
|
| 108 |
+
{{result[activeCompany].tradingInfo.pivot_point}} ({{result[activeCompany].tradingInfo.p1_pect}}%)
|
| 109 |
+
</div>
|
| 110 |
+
</td>
|
| 111 |
+
<td>
|
| 112 |
+
<div>
|
| 113 |
+
<span color="#ec2932!important"><b>S1:</b></span> {{result[activeCompany].tradingInfo.support1}} ({{result[activeCompany].tradingInfo.s1_pect}}%)
|
| 114 |
+
</div>
|
| 115 |
+
<div>
|
| 116 |
+
<span color="#ec2932!important"><b>S2:</b></span> {{result[activeCompany].tradingInfo.support2}} ({{result[activeCompany].tradingInfo.s2_pect}}%)
|
| 117 |
+
</div>
|
| 118 |
+
<div>
|
| 119 |
+
<span color="#ec2932!important"><b>S3:</b></span> {{result[activeCompany].tradingInfo.support3}} ({{result[activeCompany].tradingInfo.s3_pect}}%)
|
| 120 |
+
</div>
|
| 121 |
+
</td>
|
| 122 |
+
<td>
|
| 123 |
+
<div>
|
| 124 |
+
<span color="#ec2932!important"><b>R1:</b></span> {{result[activeCompany].tradingInfo.resistance1}} ({{result[activeCompany].tradingInfo.r1_pect}}%)
|
| 125 |
+
</div>
|
| 126 |
+
<div>
|
| 127 |
+
<span color="#ec2932!important"><b>R2:</b></span> {{result[activeCompany].tradingInfo.resistance2}} ({{result[activeCompany].tradingInfo.r2_pect}}%)
|
| 128 |
+
</div>
|
| 129 |
+
<div>
|
| 130 |
+
<span color="#ec2932!important"><b>R3:</b></span> {{result[activeCompany].tradingInfo.resistance3}} ({{result[activeCompany].tradingInfo.r3_pect}}%)
|
| 131 |
+
</div>
|
| 132 |
+
</td>
|
| 133 |
+
|
| 134 |
+
<td>{{result[activeCompany].tradingInfo.remarks}}</td>
|
| 135 |
+
</tr>
|
| 136 |
+
</tbody>
|
| 137 |
+
</table>
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
</ng-container>
|
| 141 |
+
</mat-card>
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
<mat-card>
|
| 147 |
+
<h1>Technical Analysis</h1>
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
<table>
|
| 151 |
+
<thead>
|
| 152 |
+
<tr>
|
| 153 |
+
<th style="cursor:pointer;"
|
| 154 |
+
*ngFor="let kv of result[activeCompany].final_trade_signal | keyvalue"
|
| 155 |
+
(click)="showStrategies(kv.key)"
|
| 156 |
+
[ngClass]="{ 'active-td': selectedIndicator === kv.key }">
|
| 157 |
+
{{ kv.key }}
|
| 158 |
+
</th>
|
| 159 |
+
</tr>
|
| 160 |
+
</thead>
|
| 161 |
+
<tbody>
|
| 162 |
+
<tr>
|
| 163 |
+
<td *ngFor="let kv of result[activeCompany].final_trade_signal | keyvalue">
|
| 164 |
+
<div class="dot"
|
| 165 |
+
[ngClass]="getClass(kv.value)"></div>
|
| 166 |
+
</td>
|
| 167 |
+
</tr>
|
| 168 |
+
</tbody>
|
| 169 |
+
</table>
|
| 170 |
+
|
| 171 |
+
<div class="strategy-chart-container">
|
| 172 |
+
|
| 173 |
+
<div class="strategy-container">
|
| 174 |
+
|
| 175 |
+
<ng-container *ngFor="let kv of result[activeCompany][selectedIndicator] | keyvalue">
|
| 176 |
+
<div class="strategy">
|
| 177 |
+
<div class="content" style="cursor:pointer;"
|
| 178 |
+
(click)="loadStrategiesChart(kv.key)"
|
| 179 |
+
[ngClass]="{ 'active-td': selectedStrategy === kv.key }">{{kv.key}}</div>
|
| 180 |
+
<!--<div class="dot"
|
| 181 |
+
[ngClass]="getClass(kv.value)"></div>-->
|
| 182 |
+
|
| 183 |
+
<div class="dot" [ngClass]="getClass(kv.value)"></div>
|
| 184 |
+
<span *ngIf="getClass(kv.value) === 'strategyvalue'">{{ kv.value }}</span>
|
| 185 |
+
</div>
|
| 186 |
+
|
| 187 |
+
</ng-container>
|
| 188 |
+
|
| 189 |
+
</div>
|
| 190 |
+
|
| 191 |
+
<div class="strategy-chart" *ngFor="let strategyCharts of strategyChart">
|
| 192 |
+
<apx-chart [series]="strategyCharts.series"
|
| 193 |
+
[chart]="strategyCharts.chart"
|
| 194 |
+
[xaxis]="strategyCharts.xaxis"
|
| 195 |
+
[yaxis]="strategyCharts.yaxis"
|
| 196 |
+
[stroke]="strategyCharts.stroke"
|
| 197 |
+
[tooltip]="strategyCharts.tooltip"
|
| 198 |
+
[annotations]="strategyCharts.annotations">
|
| 199 |
+
</apx-chart>
|
| 200 |
+
</div>
|
| 201 |
+
</div>
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
</mat-card>
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
<mat-card>
|
| 208 |
+
<h1>Fundamental Analysis</h1>
|
| 209 |
+
|
| 210 |
+
<table>
|
| 211 |
+
<thead>
|
| 212 |
+
<tr>
|
| 213 |
+
<th *ngFor="let kv of result[activeCompany].fundamental_analysis.fa_strategy | keyvalue">
|
| 214 |
+
{{ kv.key }}
|
| 215 |
+
</th>
|
| 216 |
+
</tr>
|
| 217 |
+
</thead>
|
| 218 |
+
<tbody>
|
| 219 |
+
<tr>
|
| 220 |
+
<td *ngFor="let kv of result[activeCompany].fundamental_analysis.fa_strategy | keyvalue">
|
| 221 |
+
<div class="dot"
|
| 222 |
+
[ngClass]="getClass(kv.value)"></div>
|
| 223 |
+
</td>
|
| 224 |
+
</tr>
|
| 225 |
+
</tbody>
|
| 226 |
+
</table>
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
<div class="fa-metrics">
|
| 230 |
+
<ng-container *ngFor="let kv of result[activeCompany].fundamental_analysis | keyvalue">
|
| 231 |
+
<div class="strategy-container" *ngIf="kv.key !== 'overall_fa_score'&& kv.key !== 'fa_strategy'">
|
| 232 |
+
|
| 233 |
+
<div class="fa-title">{{ kv.key }}</div>
|
| 234 |
+
|
| 235 |
+
<div class="fa-content" *ngIf="kv.value && typeof kv.value === 'object'">
|
| 236 |
+
<div class="metrics" *ngFor="let kvalue of kv.value | keyvalue">
|
| 237 |
+
<div>{{ kvalue.key }}</div>
|
| 238 |
+
<div>{{ kvalue.value }}</div>
|
| 239 |
+
</div>
|
| 240 |
+
</div>
|
| 241 |
+
|
| 242 |
+
</div>
|
| 243 |
+
</ng-container>
|
| 244 |
+
</div>
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
</mat-card>
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
<mat-card>
|
| 255 |
+
<h1>Candlestick Chart</h1>
|
| 256 |
+
<apx-chart [series]="candlestickChartOptions.series"
|
| 257 |
+
[chart]="candlestickChartOptions.chart"
|
| 258 |
+
[xaxis]="candlestickChartOptions.xaxis"
|
| 259 |
+
[yaxis]="candlestickChartOptions.yaxis"></apx-chart>
|
| 260 |
+
|
| 261 |
+
</mat-card>
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
<mat-card>
|
| 265 |
+
<h1>Predicted Close Price for next 15 days</h1>
|
| 266 |
+
<div class="strategy-container">
|
| 267 |
+
<p>Maximum Highest Price Prediction: {{result[activeCompany].ai_predicted_highest_price}}</p>
|
| 268 |
+
<p>Maximum Lowest Price Prediction: {{result[activeCompany].ai_predicted_lowest_price}}</p>
|
| 269 |
+
</div>
|
| 270 |
+
<apx-chart [series]="predictedChart.series"
|
| 271 |
+
[chart]="predictedChart.chart"
|
| 272 |
+
[xaxis]="predictedChart.xaxis">
|
| 273 |
+
</apx-chart>
|
| 274 |
+
</mat-card>
|
| 275 |
+
|
| 276 |
+
<mat-card>
|
| 277 |
+
<h1>Latest News</h1>
|
| 278 |
+
<div class="news-container">
|
| 279 |
+
<ng-container *ngFor="let news of result[activeCompany].news">
|
| 280 |
+
|
| 281 |
+
<div class="strategy-container">
|
| 282 |
+
<div class="dot"
|
| 283 |
+
[ngClass]="getClass(news.sentiment)"></div>
|
| 284 |
+
<a [href]="news.url">{{news.title}}</a>
|
| 285 |
+
<span>{{news.published}}</span>
|
| 286 |
+
</div>
|
| 287 |
+
|
| 288 |
+
</ng-container>
|
| 289 |
+
</div>
|
| 290 |
+
</mat-card>
|
| 291 |
+
</div>
|
src/app/analysispage/analysispage.scss
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.analyse-container{
|
| 2 |
+
width:94%;
|
| 3 |
+
padding: 3vw;
|
| 4 |
+
display:flex;
|
| 5 |
+
flex-direction:column;
|
| 6 |
+
gap:2vw;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
mat-card {
|
| 10 |
+
background: #142133;
|
| 11 |
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.55), 0 2px 6px rgba(0, 0, 0, 0.35), 0 0 0 1px rgba(255, 255, 255, 0.04) inset;
|
| 12 |
+
padding: 2vw;
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
h1 {
|
| 16 |
+
color: #38bdf8 !important;
|
| 17 |
+
font-size: 1.5vw;
|
| 18 |
+
font-family: arial;
|
| 19 |
+
}
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
/* ---- Theme ---- */
|
| 23 |
+
:host {
|
| 24 |
+
--bg: #0f172a;
|
| 25 |
+
--card: #121a27;
|
| 26 |
+
--line: #243044;
|
| 27 |
+
--ink: #e2e8f0;
|
| 28 |
+
--dim: #94a3b8;
|
| 29 |
+
--head: #2a3a52;
|
| 30 |
+
--good: #22c55e;
|
| 31 |
+
--warn: #eab308;
|
| 32 |
+
--bad: #ef4444;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
/* ---- Table base ---- */
|
| 37 |
+
table {
|
| 38 |
+
width: 100%;
|
| 39 |
+
border-collapse: collapse;
|
| 40 |
+
color: var(--ink);
|
| 41 |
+
background: var(--card);
|
| 42 |
+
/*border: 1px solid var(--line);
|
| 43 |
+
border-radius: 14px;*/ /* visible if you do not use .table-wrap */
|
| 44 |
+
overflow: hidden; /* keeps rounded corners on header */
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
thead th {
|
| 48 |
+
background: var(--head);
|
| 49 |
+
color: #d9e7ff;
|
| 50 |
+
font-weight: 700;
|
| 51 |
+
letter-spacing: .3px;
|
| 52 |
+
padding: 12px 14px;
|
| 53 |
+
text-align: left;
|
| 54 |
+
border-bottom: 1px solid var(--line);
|
| 55 |
+
font-family: arial;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
tbody td {
|
| 59 |
+
padding: 12px 14px;
|
| 60 |
+
border-top: 1px solid var(--line);
|
| 61 |
+
vertical-align: middle;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
.dot {
|
| 67 |
+
width: 1vw;
|
| 68 |
+
height: 1vw;
|
| 69 |
+
border-radius: 50%;
|
| 70 |
+
display: inline-block;
|
| 71 |
+
margin-right: 8px;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
.green {
|
| 75 |
+
background: var(--good);
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.yellow {
|
| 79 |
+
background: var(--warn);
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
.red {
|
| 83 |
+
background: var(--bad);
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.strategy-container {
|
| 87 |
+
background-color: #334155;
|
| 88 |
+
/* border: 1px solid #334155;*/
|
| 89 |
+
padding: 15px;
|
| 90 |
+
border-radius: 10px;
|
| 91 |
+
color: #FFFFFF;
|
| 92 |
+
display: flex;
|
| 93 |
+
flex-direction: column;
|
| 94 |
+
gap: 2vw;
|
| 95 |
+
font-family: Arial;
|
| 96 |
+
width: 20vw;
|
| 97 |
+
/*margin-top: 2vw;*/
|
| 98 |
+
|
| 99 |
+
.strategy {
|
| 100 |
+
display: flex;
|
| 101 |
+
font-family: Arial;
|
| 102 |
+
justify-content: space-between;
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
.fa-metrics {
|
| 107 |
+
display: grid;
|
| 108 |
+
grid-template-columns: repeat(3, 1fr);
|
| 109 |
+
gap: 40px;
|
| 110 |
+
max-width: 75vw;
|
| 111 |
+
margin: auto;
|
| 112 |
+
}
|
| 113 |
+
.fa-title {
|
| 114 |
+
color: #38bdf8 !important;
|
| 115 |
+
}
|
| 116 |
+
.fa-content {
|
| 117 |
+
display: flex;
|
| 118 |
+
flex-direction:column;
|
| 119 |
+
gap:1vw;
|
| 120 |
+
}
|
| 121 |
+
.metrics {
|
| 122 |
+
display: flex;
|
| 123 |
+
justify-content: space-between;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
.news-container {
|
| 127 |
+
display: grid;
|
| 128 |
+
grid-template-columns: repeat(4, 1fr);
|
| 129 |
+
gap: 1vw;
|
| 130 |
+
|
| 131 |
+
a {
|
| 132 |
+
color: inherit;
|
| 133 |
+
text-decoration: none;
|
| 134 |
+
font-family: Arial;
|
| 135 |
+
}
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
.chart-container {
|
| 139 |
+
display: flex;
|
| 140 |
+
background-color: #1e293b;
|
| 141 |
+
}
|
| 142 |
+
.strategy-chart-container {
|
| 143 |
+
display: flex;
|
| 144 |
+
align-items: center;
|
| 145 |
+
gap: 2vw;
|
| 146 |
+
|
| 147 |
+
.strategy-chart {
|
| 148 |
+
background-color: #FFFFFF;
|
| 149 |
+
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
| 150 |
+
}
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
.active-td {
|
| 154 |
+
color: #38bdf8 !important;
|
| 155 |
+
font-weight:bold;
|
| 156 |
+
}
|
src/app/analysispage/analysispage.spec.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
| 2 |
+
|
| 3 |
+
import { Analysispage } from './analysispage';
|
| 4 |
+
|
| 5 |
+
describe('Analysispage', () => {
|
| 6 |
+
let component: Analysispage;
|
| 7 |
+
let fixture: ComponentFixture<Analysispage>;
|
| 8 |
+
|
| 9 |
+
beforeEach(async () => {
|
| 10 |
+
await TestBed.configureTestingModule({
|
| 11 |
+
imports: [Analysispage]
|
| 12 |
+
})
|
| 13 |
+
.compileComponents();
|
| 14 |
+
|
| 15 |
+
fixture = TestBed.createComponent(Analysispage);
|
| 16 |
+
component = fixture.componentInstance;
|
| 17 |
+
fixture.detectChanges();
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
it('should create', () => {
|
| 21 |
+
expect(component).toBeTruthy();
|
| 22 |
+
});
|
| 23 |
+
});
|
src/app/analysispage/analysispage.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Component, OnInit, inject } from '@angular/core';
|
| 2 |
+
import { CommonModule } from '@angular/common';
|
| 3 |
+
import { MatCardModule } from '@angular/material/card';
|
| 4 |
+
import { Location } from '@angular/common';
|
| 5 |
+
import { NgApexchartsModule } from 'ng-apexcharts';
|
| 6 |
+
import { ChartService } from './chart.service';
|
| 7 |
+
|
| 8 |
+
@Component({
|
| 9 |
+
selector: 'app-analysispage',
|
| 10 |
+
standalone: true,
|
| 11 |
+
imports: [CommonModule, MatCardModule, NgApexchartsModule],
|
| 12 |
+
templateUrl: './analysispage.html',
|
| 13 |
+
styleUrls: ['./analysispage.scss']
|
| 14 |
+
})
|
| 15 |
+
export class Analysispage implements OnInit {
|
| 16 |
+
private location = inject(Location);
|
| 17 |
+
result: any;
|
| 18 |
+
chartOptions: any;
|
| 19 |
+
candlestickChartOptions: any;
|
| 20 |
+
overallChart: any;
|
| 21 |
+
strategyChart: any;
|
| 22 |
+
predictedChart: any;
|
| 23 |
+
selectedIndicator:any = 'ADX';
|
| 24 |
+
selectedStrategy: any = 'ADX';
|
| 25 |
+
activeCompany: number = 0;
|
| 26 |
+
constructor(private chartService: ChartService) { }
|
| 27 |
+
|
| 28 |
+
ngOnInit(): void {
|
| 29 |
+
const s = this.location.getState() as { result?: unknown } | null;
|
| 30 |
+
this.result = s?.result ?? null;
|
| 31 |
+
console.log('Analysis result:', this.result);
|
| 32 |
+
this.loadCharts();
|
| 33 |
+
this.loadStrategiesChart('ADX');
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
showStrategies(selectedIndicator:any) {
|
| 37 |
+
this.selectedIndicator = selectedIndicator;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
loadStrategiesChart(strategyName: any) {
|
| 41 |
+
this.selectedStrategy = strategyName;
|
| 42 |
+
|
| 43 |
+
this.strategyChart = this.chartService.getChartOptions(strategyName, this.result[this.activeCompany]);
|
| 44 |
+
console.log(this.strategyChart);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
getClass(signal: any) {
|
| 48 |
+
if (typeof signal === 'string') {
|
| 49 |
+
switch (signal.toLowerCase()) {
|
| 50 |
+
case 'buy':
|
| 51 |
+
case 'good':
|
| 52 |
+
case 'positive':
|
| 53 |
+
case 'bullish':
|
| 54 |
+
return 'green';
|
| 55 |
+
case 'dbuy':
|
| 56 |
+
case 'bad':
|
| 57 |
+
case 'negative':
|
| 58 |
+
case 'bearish':
|
| 59 |
+
return 'red';
|
| 60 |
+
case 'neutral':
|
| 61 |
+
case 'none':
|
| 62 |
+
return 'yellow';
|
| 63 |
+
default:
|
| 64 |
+
return '';
|
| 65 |
+
}
|
| 66 |
+
} else {
|
| 67 |
+
return 'strategyvalue'
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
loadCharts() {
|
| 72 |
+
let closePrices = this.result[this.activeCompany].ohlc_data.map((data:any) => ({
|
| 73 |
+
x: new Date(data.x).getTime(),
|
| 74 |
+
y: data.y[3]
|
| 75 |
+
}));
|
| 76 |
+
this.chartOptions = {
|
| 77 |
+
series: [{
|
| 78 |
+
name: "Close Price",
|
| 79 |
+
data: closePrices
|
| 80 |
+
}],
|
| 81 |
+
chart: {
|
| 82 |
+
type: 'area',
|
| 83 |
+
height: 350,
|
| 84 |
+
width: 1200
|
| 85 |
+
},
|
| 86 |
+
xaxis: {
|
| 87 |
+
type: 'datetime',
|
| 88 |
+
labels: {
|
| 89 |
+
style: {
|
| 90 |
+
colors: '#ffffff'
|
| 91 |
+
},
|
| 92 |
+
},
|
| 93 |
+
},
|
| 94 |
+
yaxis: {
|
| 95 |
+
title: {
|
| 96 |
+
text: 'Close Price'
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
};
|
| 100 |
+
|
| 101 |
+
this.candlestickChartOptions = {
|
| 102 |
+
chart: {
|
| 103 |
+
type: 'candlestick',
|
| 104 |
+
height: 550,
|
| 105 |
+
width: 1700
|
| 106 |
+
},
|
| 107 |
+
series: [{
|
| 108 |
+
data: this.result[this.activeCompany].ohlc_data
|
| 109 |
+
}],
|
| 110 |
+
xaxis: {
|
| 111 |
+
type: 'datetime',
|
| 112 |
+
labels: {
|
| 113 |
+
style: {
|
| 114 |
+
colors: '#ffffff'
|
| 115 |
+
},
|
| 116 |
+
},
|
| 117 |
+
},
|
| 118 |
+
yaxis: {
|
| 119 |
+
opposite: true,
|
| 120 |
+
labels: {
|
| 121 |
+
style: {
|
| 122 |
+
colors: '#ffffff'
|
| 123 |
+
},
|
| 124 |
+
},
|
| 125 |
+
}
|
| 126 |
+
};
|
| 127 |
+
|
| 128 |
+
this.overallChart = {
|
| 129 |
+
series: [
|
| 130 |
+
this.result[this.activeCompany].overall_ta_score,
|
| 131 |
+
this.result[this.activeCompany].overall_fa_score,
|
| 132 |
+
this.result[this.activeCompany].news_overall_score
|
| 133 |
+
], // Three values for the donut chart
|
| 134 |
+
chart: {
|
| 135 |
+
type: 'donut',
|
| 136 |
+
width: 500
|
| 137 |
+
},
|
| 138 |
+
labels: ['TA', 'FA', 'News'], // Set the labels to show on the chart
|
| 139 |
+
plotOptions: {
|
| 140 |
+
pie: {
|
| 141 |
+
donut: {
|
| 142 |
+
size: '60%', // Control the thickness of the donut
|
| 143 |
+
labels: {
|
| 144 |
+
show: true,
|
| 145 |
+
name: {
|
| 146 |
+
show: false
|
| 147 |
+
},
|
| 148 |
+
value: {
|
| 149 |
+
show: true, // Show values inside the donut chart
|
| 150 |
+
fontSize: '16px', // Font size for values
|
| 151 |
+
color: 'white' // White color for the values
|
| 152 |
+
},
|
| 153 |
+
total: {
|
| 154 |
+
show: true,
|
| 155 |
+
label: 'Total', // Label at the center
|
| 156 |
+
formatter: (w: any) => {
|
| 157 |
+
// Display the total value (sum of all slices)
|
| 158 |
+
return w.globals.seriesTotals.reduce((a: any, b: any) => a + b, 0) + '%';
|
| 159 |
+
}
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
}
|
| 164 |
+
},
|
| 165 |
+
fill: {
|
| 166 |
+
type: 'solid',
|
| 167 |
+
colors: ['#ff5733', '#33ff57', '#ffcc00'] // Colors for TA, FA, and News
|
| 168 |
+
},
|
| 169 |
+
tooltip: {
|
| 170 |
+
enabled: true, // Enable tooltips for the chart
|
| 171 |
+
fillSeriesColor: false,
|
| 172 |
+
theme: 'dark',
|
| 173 |
+
style: {
|
| 174 |
+
fontSize: '14px',
|
| 175 |
+
color: 'white' // White text color for tooltips
|
| 176 |
+
}
|
| 177 |
+
},
|
| 178 |
+
legend: {
|
| 179 |
+
show: true,
|
| 180 |
+
position: 'bottom', // Position the legend at the bottom
|
| 181 |
+
labels: {
|
| 182 |
+
useSeriesColors: true, // Use the series colors for legend
|
| 183 |
+
colors: ['white'] // White text color for legend
|
| 184 |
+
}
|
| 185 |
+
}
|
| 186 |
+
};
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
const prediction_prices = this.result[this.activeCompany]['prediction_prices']
|
| 191 |
+
const prediction_dates = this.result[this.activeCompany]['prediction_dates']
|
| 192 |
+
|
| 193 |
+
this.predictedChart = {
|
| 194 |
+
series: [
|
| 195 |
+
{
|
| 196 |
+
name: "Predicted Price",
|
| 197 |
+
data: prediction_prices.map((value: any) => Number(value.toFixed(2))),
|
| 198 |
+
color: '#28a745'
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
],
|
| 202 |
+
chart: {
|
| 203 |
+
type: "area",
|
| 204 |
+
height: 460,
|
| 205 |
+
width: 1650
|
| 206 |
+
},
|
| 207 |
+
xaxis: {
|
| 208 |
+
categories: prediction_dates,
|
| 209 |
+
labels: {
|
| 210 |
+
style: {
|
| 211 |
+
colors: '#ffffff'
|
| 212 |
+
},
|
| 213 |
+
},
|
| 214 |
+
}
|
| 215 |
+
};
|
| 216 |
+
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
selectCompany(index: number) {
|
| 221 |
+
this.activeCompany = index;
|
| 222 |
+
this.loadCharts();
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
}
|
src/app/analysispage/chart.service.ts
ADDED
|
@@ -0,0 +1,558 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Injectable } from '@angular/core';
|
| 2 |
+
|
| 3 |
+
@Injectable({
|
| 4 |
+
providedIn: 'root'
|
| 5 |
+
})
|
| 6 |
+
export class ChartService {
|
| 7 |
+
|
| 8 |
+
constructor() { }
|
| 9 |
+
|
| 10 |
+
getChartOptions(strategyName: any, fetchData: any): any[] {
|
| 11 |
+
const strategyConfig: any = {
|
| 12 |
+
|
| 13 |
+
'RSI 14': {
|
| 14 |
+
panels: [
|
| 15 |
+
{
|
| 16 |
+
indicators: ['RSI 14'],
|
| 17 |
+
showAnnotations: false,
|
| 18 |
+
levels: [],
|
| 19 |
+
yaxisconfig: {
|
| 20 |
+
min: 0,
|
| 21 |
+
max: 100
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
]
|
| 25 |
+
},
|
| 26 |
+
'Overbought/Oversold': {
|
| 27 |
+
panels: [
|
| 28 |
+
{
|
| 29 |
+
indicators: ['RSI 14'],
|
| 30 |
+
showAnnotations: true,
|
| 31 |
+
levels: [
|
| 32 |
+
{ value: 70, color: '#FF0000', label: 'Overbought (70)' },
|
| 33 |
+
{ value: 30, color: '#008000', label: 'Oversold (30)' }
|
| 34 |
+
],
|
| 35 |
+
yaxisconfig: {
|
| 36 |
+
min: 0,
|
| 37 |
+
max: 100
|
| 38 |
+
}
|
| 39 |
+
}
|
| 40 |
+
]
|
| 41 |
+
},
|
| 42 |
+
'RSI Swing Rejection': {
|
| 43 |
+
panels: [
|
| 44 |
+
{
|
| 45 |
+
indicators: ['RSI 14'],
|
| 46 |
+
showAnnotations: false,
|
| 47 |
+
levels: [],
|
| 48 |
+
yaxisconfig: {
|
| 49 |
+
min: 0,
|
| 50 |
+
max: 100
|
| 51 |
+
}
|
| 52 |
+
}
|
| 53 |
+
]
|
| 54 |
+
},
|
| 55 |
+
'RSI Divergence': {
|
| 56 |
+
panels: [
|
| 57 |
+
{
|
| 58 |
+
indicators: ['RSI 14'],
|
| 59 |
+
showAnnotations: false,
|
| 60 |
+
levels: [],
|
| 61 |
+
yaxisconfig: {
|
| 62 |
+
min: 0,
|
| 63 |
+
max: 100
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
]
|
| 67 |
+
},
|
| 68 |
+
'RSI 5/14 Crossover': {
|
| 69 |
+
panels: [
|
| 70 |
+
{
|
| 71 |
+
indicators: ['RSI 14', 'RSI 5'],
|
| 72 |
+
showAnnotations: false,
|
| 73 |
+
levels: [],
|
| 74 |
+
yaxisconfig: {
|
| 75 |
+
min: 0,
|
| 76 |
+
max: 100
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
]
|
| 80 |
+
},
|
| 81 |
+
'RSI Trend 50 Confirmation': {
|
| 82 |
+
panels: [
|
| 83 |
+
{
|
| 84 |
+
indicators: ['RSI 14'],
|
| 85 |
+
showAnnotations: true,
|
| 86 |
+
levels: [
|
| 87 |
+
{ value: 70, color: '#FF0000', label: 'Overbought (70)' },
|
| 88 |
+
{ value: 50, color: '#775DD0', label: 'RSI (50)' },
|
| 89 |
+
{ value: 30, color: '#008000', label: 'Oversold (30)' }
|
| 90 |
+
],
|
| 91 |
+
yaxisconfig: {
|
| 92 |
+
min: 0,
|
| 93 |
+
max: 100
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
]
|
| 97 |
+
},
|
| 98 |
+
'Mean Reversion': {
|
| 99 |
+
panels: [
|
| 100 |
+
{
|
| 101 |
+
indicators: ['RSI 14'],
|
| 102 |
+
showAnnotations: true,
|
| 103 |
+
levels: [
|
| 104 |
+
{ value: 80, color: '#FF0000', label: 'Overbought (80)' },
|
| 105 |
+
{ value: 20, color: '#008000', label: 'Oversold (20)' }
|
| 106 |
+
],
|
| 107 |
+
yaxisconfig: {
|
| 108 |
+
min: 0,
|
| 109 |
+
max: 100
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
]
|
| 113 |
+
},
|
| 114 |
+
'RSI_Bollinger Band': {
|
| 115 |
+
panels: [
|
| 116 |
+
{
|
| 117 |
+
indicators: ['RSI 14'],
|
| 118 |
+
showAnnotations: true,
|
| 119 |
+
levels: [
|
| 120 |
+
{ value: 70, color: '#FF0000', label: 'Overbought (70)' },
|
| 121 |
+
{ value: 30, color: '#008000', label: 'Oversold (30)' }
|
| 122 |
+
],
|
| 123 |
+
yaxisconfig: {
|
| 124 |
+
min: 0,
|
| 125 |
+
max: 100
|
| 126 |
+
}
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
indicators: ['Close', 'UpperBB', 'LowerBB'],
|
| 130 |
+
showAnnotations: false,
|
| 131 |
+
levels: [],
|
| 132 |
+
yaxisconfig: {} // auto-scaling
|
| 133 |
+
}
|
| 134 |
+
]
|
| 135 |
+
},
|
| 136 |
+
'RSI_MA': {
|
| 137 |
+
panels: [
|
| 138 |
+
{
|
| 139 |
+
indicators: ['RSI 14'],
|
| 140 |
+
showAnnotations: true,
|
| 141 |
+
levels: [
|
| 142 |
+
{ value: 70, color: '#FF0000', label: 'Overbought (70)' },
|
| 143 |
+
{ value: 30, color: '#008000', label: 'Oversold (30)' }
|
| 144 |
+
],
|
| 145 |
+
yaxisconfig: {
|
| 146 |
+
min: 0,
|
| 147 |
+
max: 100
|
| 148 |
+
}
|
| 149 |
+
},
|
| 150 |
+
{
|
| 151 |
+
indicators: ['Close', 'MA_20'],
|
| 152 |
+
showAnnotations: false,
|
| 153 |
+
levels: [],
|
| 154 |
+
yaxisconfig: {} // auto-scaling
|
| 155 |
+
}
|
| 156 |
+
]
|
| 157 |
+
},
|
| 158 |
+
'MACD': {
|
| 159 |
+
panels: [
|
| 160 |
+
{
|
| 161 |
+
indicators: ['MACDLine'],
|
| 162 |
+
showAnnotations: false,
|
| 163 |
+
levels: [],
|
| 164 |
+
yaxisconfig: {}
|
| 165 |
+
}
|
| 166 |
+
]
|
| 167 |
+
},
|
| 168 |
+
'MACD Line Crossover': {
|
| 169 |
+
panels: [
|
| 170 |
+
{
|
| 171 |
+
indicators: ['MACDLine', 'MACDSignalLine', 'MACDHistogram'],
|
| 172 |
+
showAnnotations: false,
|
| 173 |
+
levels: [],
|
| 174 |
+
yaxisconfig: {}
|
| 175 |
+
}
|
| 176 |
+
]
|
| 177 |
+
},
|
| 178 |
+
'MACD Zero-Line Crossover': {
|
| 179 |
+
panels: [
|
| 180 |
+
{
|
| 181 |
+
indicators: ['MACDLine'],
|
| 182 |
+
showAnnotations: true,
|
| 183 |
+
levels: [{ value: 0, color: '#FF0000', label: 'MACD ZeroLine' },],
|
| 184 |
+
yaxisconfig: {}
|
| 185 |
+
}
|
| 186 |
+
]
|
| 187 |
+
},
|
| 188 |
+
'MACD Momentum': {
|
| 189 |
+
panels: [
|
| 190 |
+
{
|
| 191 |
+
indicators: ['MACDLine', 'MACDSignalLine', 'MACDHistogram'],
|
| 192 |
+
showAnnotations: false,
|
| 193 |
+
levels: [{ value: 0, color: '#FF0000', label: 'MACD ZeroLine' },],
|
| 194 |
+
yaxisconfig: {}
|
| 195 |
+
}
|
| 196 |
+
]
|
| 197 |
+
},
|
| 198 |
+
'ATR': {
|
| 199 |
+
panels: [
|
| 200 |
+
{
|
| 201 |
+
indicators: ['ATRValue'],
|
| 202 |
+
showAnnotations: false,
|
| 203 |
+
levels: [],
|
| 204 |
+
yaxisconfig: {} // auto-scaling
|
| 205 |
+
}
|
| 206 |
+
]
|
| 207 |
+
},
|
| 208 |
+
'ATR Breakout': {
|
| 209 |
+
panels: [
|
| 210 |
+
{
|
| 211 |
+
indicators: ['ATRValue'],
|
| 212 |
+
showAnnotations: false,
|
| 213 |
+
levels: [],
|
| 214 |
+
yaxisconfig: {} // auto-scaling
|
| 215 |
+
},
|
| 216 |
+
{
|
| 217 |
+
indicators: ['Close'],
|
| 218 |
+
showAnnotations: false,
|
| 219 |
+
levels: [],
|
| 220 |
+
yaxisconfig: {} // auto-scaling for Close Price
|
| 221 |
+
}
|
| 222 |
+
]
|
| 223 |
+
},
|
| 224 |
+
'ATR Expansion': {
|
| 225 |
+
panels: [
|
| 226 |
+
{
|
| 227 |
+
indicators: ['ATRValue'],
|
| 228 |
+
showAnnotations: false,
|
| 229 |
+
levels: [],
|
| 230 |
+
yaxisconfig: {} // auto-scaling
|
| 231 |
+
}
|
| 232 |
+
]
|
| 233 |
+
},
|
| 234 |
+
'ATR Squeeze': {
|
| 235 |
+
panels: [
|
| 236 |
+
{
|
| 237 |
+
indicators: ['ATRValue'],
|
| 238 |
+
showAnnotations: false,
|
| 239 |
+
levels: [],
|
| 240 |
+
yaxisconfig: {} // auto-scaling
|
| 241 |
+
}
|
| 242 |
+
]
|
| 243 |
+
},
|
| 244 |
+
'ATR Trend Reversal': {
|
| 245 |
+
panels: [
|
| 246 |
+
{
|
| 247 |
+
indicators: ['ATRValue'],
|
| 248 |
+
showAnnotations: false,
|
| 249 |
+
levels: [],
|
| 250 |
+
yaxisconfig: {} // auto-scaling
|
| 251 |
+
}
|
| 252 |
+
]
|
| 253 |
+
},
|
| 254 |
+
'EMA 20': {
|
| 255 |
+
panels: [
|
| 256 |
+
{
|
| 257 |
+
indicators: ['EMA 20'],
|
| 258 |
+
showAnnotations: false,
|
| 259 |
+
levels: [],
|
| 260 |
+
yaxisconfig: {} // auto-scaling
|
| 261 |
+
}
|
| 262 |
+
]
|
| 263 |
+
},
|
| 264 |
+
|
| 265 |
+
'EMA 50': {
|
| 266 |
+
panels: [
|
| 267 |
+
{
|
| 268 |
+
indicators: ['EMA 50'],
|
| 269 |
+
showAnnotations: false,
|
| 270 |
+
levels: [],
|
| 271 |
+
yaxisconfig: {} // auto-scaling
|
| 272 |
+
}
|
| 273 |
+
]
|
| 274 |
+
},
|
| 275 |
+
|
| 276 |
+
'EMA Crossover': {
|
| 277 |
+
panels: [
|
| 278 |
+
{
|
| 279 |
+
indicators: ['EMA 20', 'EMA 50'],
|
| 280 |
+
showAnnotations: false,
|
| 281 |
+
levels: [],
|
| 282 |
+
yaxisconfig: {} // auto-scaling
|
| 283 |
+
}
|
| 284 |
+
]
|
| 285 |
+
},
|
| 286 |
+
'EMA Price Crossover': {
|
| 287 |
+
panels: [
|
| 288 |
+
{
|
| 289 |
+
indicators: ['EMA 20', 'Close'],
|
| 290 |
+
showAnnotations: false,
|
| 291 |
+
levels: [],
|
| 292 |
+
yaxisconfig: {} // auto-scaling
|
| 293 |
+
}
|
| 294 |
+
]
|
| 295 |
+
},
|
| 296 |
+
'Triple EMA': {
|
| 297 |
+
panels: [
|
| 298 |
+
{
|
| 299 |
+
indicators: ['EMA 5', 'EMA 20', 'EMA 50'],
|
| 300 |
+
showAnnotations: false,
|
| 301 |
+
levels: [],
|
| 302 |
+
yaxisconfig: {} // auto-scaling
|
| 303 |
+
}
|
| 304 |
+
]
|
| 305 |
+
},
|
| 306 |
+
'ADX': {
|
| 307 |
+
panels: [
|
| 308 |
+
{
|
| 309 |
+
indicators: ['ADX_Indicator'],
|
| 310 |
+
showAnnotations: false,
|
| 311 |
+
levels: [],
|
| 312 |
+
yaxisconfig: {
|
| 313 |
+
min: 0,
|
| 314 |
+
max: 100
|
| 315 |
+
}
|
| 316 |
+
}
|
| 317 |
+
]
|
| 318 |
+
},
|
| 319 |
+
|
| 320 |
+
'ADX + DI Crossover': {
|
| 321 |
+
panels: [
|
| 322 |
+
{
|
| 323 |
+
indicators: ['ADX_Indicator', 'PLUS_DI', 'MINUS_DI'],
|
| 324 |
+
showAnnotations: false,
|
| 325 |
+
levels: [],
|
| 326 |
+
yaxisconfig: {
|
| 327 |
+
min: 0,
|
| 328 |
+
max: 100
|
| 329 |
+
}
|
| 330 |
+
}
|
| 331 |
+
]
|
| 332 |
+
},
|
| 333 |
+
'ADX Divergence': {
|
| 334 |
+
panels: [
|
| 335 |
+
{
|
| 336 |
+
indicators: ['ADX_Indicator'],
|
| 337 |
+
showAnnotations: false,
|
| 338 |
+
levels: [],
|
| 339 |
+
yaxisconfig: {
|
| 340 |
+
min: 0,
|
| 341 |
+
max: 100
|
| 342 |
+
}
|
| 343 |
+
},
|
| 344 |
+
{
|
| 345 |
+
indicators: ['Close'],
|
| 346 |
+
showAnnotations: false,
|
| 347 |
+
levels: [],
|
| 348 |
+
yaxisconfig: {} // auto-scale for price
|
| 349 |
+
}
|
| 350 |
+
]
|
| 351 |
+
},
|
| 352 |
+
'ADX Slope': {
|
| 353 |
+
panels: [
|
| 354 |
+
{
|
| 355 |
+
indicators: ['ADX_Indicator'],
|
| 356 |
+
showAnnotations: false,
|
| 357 |
+
levels: [],
|
| 358 |
+
yaxisconfig: {
|
| 359 |
+
min: 0,
|
| 360 |
+
max: 100
|
| 361 |
+
}
|
| 362 |
+
}
|
| 363 |
+
]
|
| 364 |
+
},
|
| 365 |
+
'ADX Breakout': {
|
| 366 |
+
panels: [
|
| 367 |
+
{
|
| 368 |
+
indicators: ['ADX_Indicator'],
|
| 369 |
+
showAnnotations: false,
|
| 370 |
+
levels: [],
|
| 371 |
+
yaxisconfig: {
|
| 372 |
+
min: 0,
|
| 373 |
+
max: 100
|
| 374 |
+
}
|
| 375 |
+
},
|
| 376 |
+
{
|
| 377 |
+
indicators: ['Close'],
|
| 378 |
+
showAnnotations: false,
|
| 379 |
+
levels: [],
|
| 380 |
+
yaxisconfig: {} // auto-scale for price
|
| 381 |
+
}
|
| 382 |
+
]
|
| 383 |
+
},
|
| 384 |
+
'Fibonacci Retracement Bounce': {
|
| 385 |
+
panels: [
|
| 386 |
+
{
|
| 387 |
+
indicators: ['Candlestick'],
|
| 388 |
+
showAnnotations: true,
|
| 389 |
+
levels: [
|
| 390 |
+
{ value: 0.382, color: '#00008B', label: '38.2%' },
|
| 391 |
+
{ value: 0.5, color: '#FF0000', label: '50%' },
|
| 392 |
+
{ value: 0.618, color: '#FF5722', label: '61.8%' }
|
| 393 |
+
],
|
| 394 |
+
yaxisconfig: {} // auto-scaling
|
| 395 |
+
}
|
| 396 |
+
]
|
| 397 |
+
},
|
| 398 |
+
|
| 399 |
+
'Fibonacci Breakout': {
|
| 400 |
+
panels: [
|
| 401 |
+
{
|
| 402 |
+
indicators: ['Candlestick'],
|
| 403 |
+
showAnnotations: true,
|
| 404 |
+
levels: [],
|
| 405 |
+
yaxisconfig: {} // auto-scaling
|
| 406 |
+
}
|
| 407 |
+
]
|
| 408 |
+
},
|
| 409 |
+
|
| 410 |
+
'Fibonacci Confluence': {
|
| 411 |
+
panels: [
|
| 412 |
+
{
|
| 413 |
+
indicators: ['Close', 'EMA9'],
|
| 414 |
+
showAnnotations: true,
|
| 415 |
+
levels: [], // dynamically filled
|
| 416 |
+
yaxisconfig: {} // auto-scaling
|
| 417 |
+
}
|
| 418 |
+
]
|
| 419 |
+
},
|
| 420 |
+
|
| 421 |
+
'Golden Pocket Reversal': {
|
| 422 |
+
panels: [
|
| 423 |
+
{
|
| 424 |
+
indicators: ['Candlestick'],
|
| 425 |
+
showAnnotations: true,
|
| 426 |
+
levels: [
|
| 427 |
+
{ value: fetchData.FIB_61_8, color: '#FF0000', label: '61.8%' },
|
| 428 |
+
{ value: fetchData.FIB_65, color: '#FFC107', label: '65%' }
|
| 429 |
+
],
|
| 430 |
+
yaxisconfig: {} // auto-scaling
|
| 431 |
+
}
|
| 432 |
+
]
|
| 433 |
+
},
|
| 434 |
+
|
| 435 |
+
'BB Squeeze': {
|
| 436 |
+
panels: [
|
| 437 |
+
{
|
| 438 |
+
indicators: ['Close', 'UpperBB', 'LowerBB'],
|
| 439 |
+
showAnnotations: false,
|
| 440 |
+
levels: [],
|
| 441 |
+
yaxisconfig: {} // auto-scaling
|
| 442 |
+
}
|
| 443 |
+
]
|
| 444 |
+
},
|
| 445 |
+
|
| 446 |
+
'BB Breakout': {
|
| 447 |
+
panels: [
|
| 448 |
+
{
|
| 449 |
+
indicators: ['Close', 'UpperBB', 'LowerBB'],
|
| 450 |
+
showAnnotations: false,
|
| 451 |
+
levels: [],
|
| 452 |
+
yaxisconfig: {} // auto-scaling
|
| 453 |
+
}
|
| 454 |
+
]
|
| 455 |
+
},
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
'BB Reversal': {
|
| 459 |
+
panels: [
|
| 460 |
+
{
|
| 461 |
+
indicators: ['Close', 'UpperBB', 'LowerBB'],
|
| 462 |
+
showAnnotations: false,
|
| 463 |
+
levels: [],
|
| 464 |
+
yaxisconfig: {} // auto-scaling
|
| 465 |
+
}
|
| 466 |
+
]
|
| 467 |
+
},
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
|
| 471 |
+
};
|
| 472 |
+
|
| 473 |
+
const config = strategyConfig[strategyName];
|
| 474 |
+
|
| 475 |
+
if (strategyName === 'Fibonacci Breakout') {
|
| 476 |
+
config.panels[0].levels = [
|
| 477 |
+
{ value: fetchData.FIB_61_8, color: '#FF5722', label: '61.8% Breakout' }
|
| 478 |
+
];
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
// For Fibonacci Retracement Bounce, convert levels to price-based annotations
|
| 482 |
+
if (strategyName === 'Fibonacci Retracement Bounce') {
|
| 483 |
+
config.panels[0].levels = [
|
| 484 |
+
{ value: fetchData.FIB_38_2, color: '#00008B', label: '38.2%' },
|
| 485 |
+
{ value: fetchData.FIB_50, color: '#FF0000', label: '50%' },
|
| 486 |
+
{ value: fetchData.FIB_61_8, color: '#FF5722', label: '61.8%' }
|
| 487 |
+
];
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
if (strategyName === 'Fibonacci Confluence') {
|
| 491 |
+
config.panels[0].levels = [
|
| 492 |
+
{ value: fetchData.FIB_61_8, color: '#FF5722', label: '61.8% Confluence' }
|
| 493 |
+
];
|
| 494 |
+
}
|
| 495 |
+
|
| 496 |
+
|
| 497 |
+
const chartOptionsArray = config.panels.map((panel: any) => {
|
| 498 |
+
const referenceData = fetchData[panel.indicators[0]];
|
| 499 |
+
const categories = Object.keys(referenceData);
|
| 500 |
+
|
| 501 |
+
const series = panel.indicators.map((indicator: string) => {
|
| 502 |
+
const indicatorData = fetchData[indicator];
|
| 503 |
+
|
| 504 |
+
if (indicator === 'Candlestick') {
|
| 505 |
+
return {
|
| 506 |
+
name: 'Candlestick',
|
| 507 |
+
type: 'candlestick',
|
| 508 |
+
data: indicatorData // already in [{x, y: [o,h,l,c]}] format
|
| 509 |
+
};
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
return {
|
| 513 |
+
name: indicator,
|
| 514 |
+
type: indicator === 'MACDHistogram' ? 'bar' : 'line',
|
| 515 |
+
data: indicatorData ? Object.entries(indicatorData).map(([x, y]) => ({ x, y })) : []
|
| 516 |
+
};
|
| 517 |
+
});
|
| 518 |
+
|
| 519 |
+
const annotations = panel.showAnnotations ? {
|
| 520 |
+
yaxis: panel.levels.map((level: any) => ({
|
| 521 |
+
y: level.value,
|
| 522 |
+
borderColor: level.color,
|
| 523 |
+
label: {
|
| 524 |
+
borderColor: level.color,
|
| 525 |
+
style: {
|
| 526 |
+
color: '#fff',
|
| 527 |
+
background: level.color
|
| 528 |
+
},
|
| 529 |
+
text: level.label
|
| 530 |
+
}
|
| 531 |
+
}))
|
| 532 |
+
} : {};
|
| 533 |
+
|
| 534 |
+
return {
|
| 535 |
+
series: series,
|
| 536 |
+
chart: {
|
| 537 |
+
type: 'line',
|
| 538 |
+
height: 500,
|
| 539 |
+
width: 1300
|
| 540 |
+
},
|
| 541 |
+
xaxis: {
|
| 542 |
+
categories: categories
|
| 543 |
+
},
|
| 544 |
+
yaxis: panel.yaxisconfig || {},
|
| 545 |
+
stroke: {
|
| 546 |
+
curve: 'smooth',
|
| 547 |
+
width: 2
|
| 548 |
+
},
|
| 549 |
+
annotations: annotations
|
| 550 |
+
};
|
| 551 |
+
});
|
| 552 |
+
|
| 553 |
+
return chartOptionsArray;
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
|
| 557 |
+
|
| 558 |
+
}
|
src/app/app-module.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';
|
| 2 |
+
import { BrowserModule } from '@angular/platform-browser';
|
| 3 |
+
|
| 4 |
+
import { AppRoutingModule } from './app-routing-module';
|
| 5 |
+
import { App } from './app';
|
| 6 |
+
import { Homepage } from './homepage/homepage';
|
| 7 |
+
import { RouterModule, Routes } from '@angular/router';
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
import { CommonModule } from '@angular/common';
|
| 11 |
+
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
| 12 |
+
import { HttpClientModule } from '@angular/common/http';
|
| 13 |
+
|
| 14 |
+
import { FormsModule } from '@angular/forms';
|
| 15 |
+
import { MatFormFieldModule } from '@angular/material/form-field'; // Import mat-form-field module
|
| 16 |
+
import { MatInputModule } from '@angular/material/input';
|
| 17 |
+
// Import mat-input module
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@NgModule({
|
| 21 |
+
declarations: [
|
| 22 |
+
App,
|
| 23 |
+
Homepage,
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
],
|
| 28 |
+
imports: [
|
| 29 |
+
BrowserModule,
|
| 30 |
+
AppRoutingModule,
|
| 31 |
+
CommonModule,
|
| 32 |
+
ReactiveFormsModule,
|
| 33 |
+
HttpClientModule,
|
| 34 |
+
FormsModule,
|
| 35 |
+
MatFormFieldModule,
|
| 36 |
+
MatInputModule
|
| 37 |
+
|
| 38 |
+
//RouterModule.forRoot(routes, {
|
| 39 |
+
// scrollPositionRestoration: 'enabled',
|
| 40 |
+
// anchorScrolling: 'enabled',
|
| 41 |
+
// bindToComponentInputs: true
|
| 42 |
+
//})
|
| 43 |
+
],
|
| 44 |
+
providers: [
|
| 45 |
+
provideBrowserGlobalErrorListeners()
|
| 46 |
+
],
|
| 47 |
+
bootstrap: [App]
|
| 48 |
+
})
|
| 49 |
+
export class AppModule { }
|
src/app/app-routing-module.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NgModule } from '@angular/core';
|
| 2 |
+
import { RouterModule, Routes } from '@angular/router';
|
| 3 |
+
import { MarketSelectComponent } from './marketselect/marketselect';
|
| 4 |
+
import { Homepage } from './homepage/homepage';
|
| 5 |
+
import { Analysispage } from './analysispage/analysispage';
|
| 6 |
+
import { Chatbot } from './chatbot/chatbot';
|
| 7 |
+
import { ToolspageComponent } from './toolspage/toolspage.component';
|
| 8 |
+
import { Screenerpage } from './screenerpage/screenerpage';
|
| 9 |
+
|
| 10 |
+
const routes: Routes = [
|
| 11 |
+
{ path: '', component: Homepage },
|
| 12 |
+
{ path: 'home', component: Homepage },
|
| 13 |
+
{ path: 'tools', component: MarketSelectComponent },
|
| 14 |
+
{ path: 'analysepage', component: Analysispage },
|
| 15 |
+
{ path: 'chatbot', component: Chatbot },
|
| 16 |
+
{ path: 'screener', component: Screenerpage },
|
| 17 |
+
{ path: 'marketselect', component: ToolspageComponent },
|
| 18 |
+
{ path: '**', redirectTo: '' }
|
| 19 |
+
];
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@NgModule({
|
| 25 |
+
imports: [RouterModule.forRoot(routes)],
|
| 26 |
+
exports: [RouterModule]
|
| 27 |
+
})
|
| 28 |
+
export class AppRoutingModule { }
|
src/app/app.html
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<div class="navigation-container">
|
| 2 |
+
<div class="nav-content">
|
| 3 |
+
<img src="logo.png" alt="PyTrade Logo" class="logo-img">
|
| 4 |
+
<h1 class="logo-title">PY-TRADE</h1>
|
| 5 |
+
<div style="display: flex; gap: 2vw; margin-left: 47vw;align-self:flex-start;">
|
| 6 |
+
<p class="menu-item">Markets</p>
|
| 7 |
+
<p class="menu-item">Community</p>
|
| 8 |
+
<p class="menu-item">AI Assistant</p>
|
| 9 |
+
|
| 10 |
+
</div>
|
| 11 |
+
|
| 12 |
+
<div class="user-avatar">
|
| 13 |
+
<img src="https://cdn-icons-png.flaticon.com/512/847/847969.png" alt="User Image">
|
| 14 |
+
</div>
|
| 15 |
+
</div>
|
| 16 |
+
|
| 17 |
+
</div>
|
| 18 |
+
|
| 19 |
+
<router-outlet></router-outlet>
|
src/app/app.scss
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Navigation Styles
|
| 2 |
+
.navigation-container {
|
| 3 |
+
background: linear-gradient(180deg, #0f0f0f, #0f0f0f00);
|
| 4 |
+
position: fixed;
|
| 5 |
+
width: 98%;
|
| 6 |
+
height: 10vw;
|
| 7 |
+
padding: 1vw;
|
| 8 |
+
.nav-content
|
| 9 |
+
|
| 10 |
+
{
|
| 11 |
+
display: flex;
|
| 12 |
+
gap: 2vw;
|
| 13 |
+
align-items: center;
|
| 14 |
+
justify-content: space-between;
|
| 15 |
+
.logo-img
|
| 16 |
+
|
| 17 |
+
{
|
| 18 |
+
width: 6vw;
|
| 19 |
+
height: 6vw;
|
| 20 |
+
border-radius: 50%;
|
| 21 |
+
box-shadow: 0 0 15px rgba(255, 255, 255, 0.8);
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
.logo-title {
|
| 25 |
+
font-size: 3vw;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
.menu-item {
|
| 29 |
+
font-size: 1vw;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
.user-avatar {
|
| 33 |
+
width: 2.5vw; /* Size of the avatar */
|
| 34 |
+
height: 2.5vw;
|
| 35 |
+
border-radius: 50%; /* Makes it circular */
|
| 36 |
+
overflow: hidden; /* Ensures image stays within circle */
|
| 37 |
+
display: inline-block;
|
| 38 |
+
position: relative;
|
| 39 |
+
background: linear-gradient(135deg, #38bdf8, #712d74); /* Gradient border */
|
| 40 |
+
padding: 3px; /* Space for gradient border */
|
| 41 |
+
align-self: flex-start
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
.user-avatar img {
|
| 45 |
+
width: 100%;
|
| 46 |
+
height: 100%;
|
| 47 |
+
border-radius: 50%; /* Keep image circular */
|
| 48 |
+
object-fit: cover; /* Cover the container without distortion */
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
}
|
| 52 |
+
}
|
src/app/app.spec.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { TestBed } from '@angular/core/testing';
|
| 2 |
+
import { RouterModule } from '@angular/router';
|
| 3 |
+
import { App } from './app';
|
| 4 |
+
|
| 5 |
+
describe('App', () => {
|
| 6 |
+
beforeEach(async () => {
|
| 7 |
+
await TestBed.configureTestingModule({
|
| 8 |
+
imports: [
|
| 9 |
+
RouterModule.forRoot([])
|
| 10 |
+
],
|
| 11 |
+
declarations: [
|
| 12 |
+
App
|
| 13 |
+
],
|
| 14 |
+
}).compileComponents();
|
| 15 |
+
});
|
| 16 |
+
|
| 17 |
+
it('should create the app', () => {
|
| 18 |
+
const fixture = TestBed.createComponent(App);
|
| 19 |
+
const app = fixture.componentInstance;
|
| 20 |
+
expect(app).toBeTruthy();
|
| 21 |
+
});
|
| 22 |
+
|
| 23 |
+
it('should render title', () => {
|
| 24 |
+
const fixture = TestBed.createComponent(App);
|
| 25 |
+
fixture.detectChanges();
|
| 26 |
+
const compiled = fixture.nativeElement as HTMLElement;
|
| 27 |
+
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, Pytrade');
|
| 28 |
+
});
|
| 29 |
+
});
|
src/app/app.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Component, signal } from '@angular/core';
|
| 2 |
+
|
| 3 |
+
@Component({
|
| 4 |
+
selector: 'app-root',
|
| 5 |
+
templateUrl: './app.html',
|
| 6 |
+
standalone: false,
|
| 7 |
+
styleUrl: './app.scss'
|
| 8 |
+
})
|
| 9 |
+
export class App {
|
| 10 |
+
protected readonly title = signal('Pytrade');
|
| 11 |
+
}
|
src/app/chatbot/chatbot.html
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<div class="chatbot-container">
|
| 2 |
+
<div class="chatbox">
|
| 3 |
+
<div *ngFor="let message of messages" class="message">
|
| 4 |
+
<div class="user-message"><strong>User:</strong> {{ message.user }}</div>
|
| 5 |
+
<div class="bot-response"><strong>Bot:</strong> {{ message.bot }}</div>
|
| 6 |
+
</div>
|
| 7 |
+
</div>
|
| 8 |
+
<input [(ngModel)]="userQuestion" placeholder="Ask a question..." />
|
| 9 |
+
<button (click)="getChatbotResponse()">Ask</button>
|
| 10 |
+
</div>
|
src/app/chatbot/chatbot.scss
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* General Container */
|
| 2 |
+
.chatbot-container {
|
| 3 |
+
max-width: 400px;
|
| 4 |
+
margin: 0 auto;
|
| 5 |
+
padding: 20px;
|
| 6 |
+
border-radius: 15px;
|
| 7 |
+
background-color: #f4f7fc;
|
| 8 |
+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
| 9 |
+
overflow: hidden;
|
| 10 |
+
font-family: 'Arial', sans-serif;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
/* Chatbox Container */
|
| 14 |
+
.chatbox {
|
| 15 |
+
max-height: 300px;
|
| 16 |
+
overflow-y: auto;
|
| 17 |
+
margin-bottom: 20px;
|
| 18 |
+
padding-right: 10px;
|
| 19 |
+
border-bottom: 2px solid #dcdfe6;
|
| 20 |
+
padding-left: 15px;
|
| 21 |
+
background: #fff;
|
| 22 |
+
border-radius: 10px;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
/* Individual Message Styling */
|
| 26 |
+
.message {
|
| 27 |
+
margin-bottom: 15px;
|
| 28 |
+
display: flex;
|
| 29 |
+
flex-direction: column;
|
| 30 |
+
align-items: flex-start;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
.user-message {
|
| 34 |
+
background-color: #e0f7fa;
|
| 35 |
+
padding: 8px 12px;
|
| 36 |
+
border-radius: 10px;
|
| 37 |
+
margin-bottom: 5px;
|
| 38 |
+
max-width: 80%;
|
| 39 |
+
align-self: flex-start;
|
| 40 |
+
font-size: 14px;
|
| 41 |
+
color: #00796b;
|
| 42 |
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
| 43 |
+
animation: fadeIn 0.5s ease;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.bot-response {
|
| 47 |
+
background-color: #e8f5e9;
|
| 48 |
+
padding: 8px 12px;
|
| 49 |
+
border-radius: 10px;
|
| 50 |
+
max-width: 80%;
|
| 51 |
+
align-self: flex-end;
|
| 52 |
+
font-size: 14px;
|
| 53 |
+
color: #388e3c;
|
| 54 |
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
| 55 |
+
animation: fadeIn 0.5s ease;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
/* Input Box Styling */
|
| 59 |
+
input {
|
| 60 |
+
width: 80%;
|
| 61 |
+
padding: 12px;
|
| 62 |
+
font-size: 14px;
|
| 63 |
+
border-radius: 25px;
|
| 64 |
+
border: 1px solid #ccc;
|
| 65 |
+
margin-right: 10px;
|
| 66 |
+
background-color: #ffffff;
|
| 67 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
button {
|
| 71 |
+
padding: 12px;
|
| 72 |
+
font-size: 14px;
|
| 73 |
+
background-color: #00796b;
|
| 74 |
+
color: white;
|
| 75 |
+
border: none;
|
| 76 |
+
border-radius: 25px;
|
| 77 |
+
cursor: pointer;
|
| 78 |
+
transition: all 0.3s;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
button:hover {
|
| 82 |
+
background-color: #004d40;
|
| 83 |
+
transform: scale(1.05);
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
button:focus {
|
| 87 |
+
outline: none;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
/* Scrollbar Styling */
|
| 91 |
+
.chatbox::-webkit-scrollbar {
|
| 92 |
+
width: 8px;
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
.chatbox::-webkit-scrollbar-thumb {
|
| 96 |
+
background-color: #00796b;
|
| 97 |
+
border-radius: 10px;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
.chatbox::-webkit-scrollbar-track {
|
| 101 |
+
background-color: #f4f7fc;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
/* Loading Spinner */
|
| 105 |
+
.loader {
|
| 106 |
+
border: 4px solid #f3f3f3;
|
| 107 |
+
border-top: 4px solid #00796b;
|
| 108 |
+
border-radius: 50%;
|
| 109 |
+
width: 24px;
|
| 110 |
+
height: 24px;
|
| 111 |
+
animation: spin 1s linear infinite;
|
| 112 |
+
margin: 0 auto;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
@keyframes spin {
|
| 116 |
+
0% {
|
| 117 |
+
transform: rotate(0deg);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
100% {
|
| 121 |
+
transform: rotate(360deg);
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
@keyframes fadeIn {
|
| 126 |
+
0% {
|
| 127 |
+
opacity: 0;
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
100% {
|
| 131 |
+
opacity: 1;
|
| 132 |
+
}
|
| 133 |
+
}
|
src/app/chatbot/chatbot.spec.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
| 2 |
+
|
| 3 |
+
import { Chatbot } from './chatbot';
|
| 4 |
+
|
| 5 |
+
describe('Chatbot', () => {
|
| 6 |
+
let component: Chatbot;
|
| 7 |
+
let fixture: ComponentFixture<Chatbot>;
|
| 8 |
+
|
| 9 |
+
beforeEach(async () => {
|
| 10 |
+
await TestBed.configureTestingModule({
|
| 11 |
+
declarations: [Chatbot]
|
| 12 |
+
})
|
| 13 |
+
.compileComponents();
|
| 14 |
+
|
| 15 |
+
fixture = TestBed.createComponent(Chatbot);
|
| 16 |
+
component = fixture.componentInstance;
|
| 17 |
+
fixture.detectChanges();
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
it('should create', () => {
|
| 21 |
+
expect(component).toBeTruthy();
|
| 22 |
+
});
|
| 23 |
+
});
|
src/app/chatbot/chatbot.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { CommonModule } from '@angular/common';
|
| 2 |
+
import { Component } from '@angular/core';
|
| 3 |
+
import { FormsModule } from '@angular/forms';
|
| 4 |
+
import axios from 'axios';
|
| 5 |
+
|
| 6 |
+
@Component({
|
| 7 |
+
selector: 'app-chatbot',
|
| 8 |
+
standalone: true,
|
| 9 |
+
imports: [FormsModule],
|
| 10 |
+
templateUrl: './chatbot.html',
|
| 11 |
+
styleUrl: './chatbot.scss'
|
| 12 |
+
})
|
| 13 |
+
export class Chatbot {
|
| 14 |
+
userQuestion: string = ''; // Holds the user's question
|
| 15 |
+
chatbotAnswer: string = ''; // Holds the chatbot's answer
|
| 16 |
+
|
| 17 |
+
// Define messages array to store conversation
|
| 18 |
+
messages: { user: string, bot: string }[] = []; // This is the missing property
|
| 19 |
+
|
| 20 |
+
// Handle user input and make API call to backend
|
| 21 |
+
async getChatbotResponse() {
|
| 22 |
+
if (this.userQuestion.trim()) {
|
| 23 |
+
// Add user's question to the messages array
|
| 24 |
+
this.messages.push({ user: this.userQuestion, bot: '...' });
|
| 25 |
+
|
| 26 |
+
try {
|
| 27 |
+
// Make API call to backend for the answer
|
| 28 |
+
const response = await axios.post('http://127.0.0.1:5000/ask', {
|
| 29 |
+
question: this.userQuestion
|
| 30 |
+
});
|
| 31 |
+
|
| 32 |
+
// Get the answer from the response and update the chatbot response
|
| 33 |
+
this.chatbotAnswer = response.data.answer;
|
| 34 |
+
|
| 35 |
+
// Add the chatbot's answer to the messages array
|
| 36 |
+
this.messages[this.messages.length - 1].bot = this.chatbotAnswer;
|
| 37 |
+
|
| 38 |
+
// Clear the user question input
|
| 39 |
+
this.userQuestion = '';
|
| 40 |
+
|
| 41 |
+
} catch (error) {
|
| 42 |
+
console.error('Error while getting response from chatbot API:', error);
|
| 43 |
+
// Handle error gracefully by adding an error message in the conversation
|
| 44 |
+
this.messages.push({ user: this.userQuestion, bot: 'Sorry, I am unable to get an answer right now.' });
|
| 45 |
+
this.userQuestion = '';
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
}
|
src/app/homepage/homepage.html
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!--Fixed Navigation-->
|
| 2 |
+
|
| 3 |
+
<!--<div class="navigation-container">
|
| 4 |
+
<div class="nav-content">
|
| 5 |
+
<img src="logo.png" alt="PyTrade Logo" class="logo-img">
|
| 6 |
+
<h1 class="logo-title">PY-TRADE</h1>
|
| 7 |
+
<div style="display: flex; gap: 2vw; margin-left: 47vw;align-self:flex-start;">
|
| 8 |
+
<p class="menu-item">Markets</p>
|
| 9 |
+
<p class="menu-item">Community</p>
|
| 10 |
+
<p class="menu-item">AI Assistant</p>
|
| 11 |
+
|
| 12 |
+
</div>
|
| 13 |
+
|
| 14 |
+
<div class="user-avatar">
|
| 15 |
+
<img src="https://cdn-icons-png.flaticon.com/512/847/847969.png" alt="User Image">
|
| 16 |
+
</div>
|
| 17 |
+
</div>
|
| 18 |
+
|
| 19 |
+
</div>-->
|
| 20 |
+
|
| 21 |
+
<!--Landing Page-->
|
| 22 |
+
<div class="homepage-container">
|
| 23 |
+
|
| 24 |
+
<h1 class="py-title">Unlock the Future of Trading</h1>
|
| 25 |
+
<p class="py-quote">Empower your decisions with data, and turn insights into profitable action.</p>
|
| 26 |
+
<button class="get-started-btn" routerLink="/marketselect">
|
| 27 |
+
Get Started
|
| 28 |
+
</button>
|
| 29 |
+
</div>
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
<!--Features container-->
|
| 33 |
+
<div class="key-feature-container">
|
| 34 |
+
<h1>Key Features</h1>
|
| 35 |
+
|
| 36 |
+
<div class="features">
|
| 37 |
+
<div class="feature-card">
|
| 38 |
+
<span>📊</span>
|
| 39 |
+
<div class="feature-title">Technical Analysis</div>
|
| 40 |
+
<div class="feature-detail">
|
| 41 |
+
Technical analysis helps you make informed decisions by analyzing historical market data like price and volume.
|
| 42 |
+
It includes charts, indicators, and price patterns to forecast market movements.
|
| 43 |
+
</div>
|
| 44 |
+
</div>
|
| 45 |
+
|
| 46 |
+
<div class="feature-card">
|
| 47 |
+
<span>💹</span>
|
| 48 |
+
<div class="feature-title">Fundamental Analysis</div>
|
| 49 |
+
<div class="feature-detail">
|
| 50 |
+
Fundamental analysis helps evaluate the financial health of a company by analyzing financial statements,
|
| 51 |
+
earnings, revenue, and market conditions. It assists in determining the intrinsic value of stocks for long-term investments.
|
| 52 |
+
</div>
|
| 53 |
+
</div>
|
| 54 |
+
|
| 55 |
+
<div class="feature-card">
|
| 56 |
+
<span>📰</span>
|
| 57 |
+
<div class="feature-title">Market News Sentiment Analysis</div>
|
| 58 |
+
<div class="feature-detail">
|
| 59 |
+
Market news sentiment analysis evaluates the mood and sentiment of market participants by analyzing news,
|
| 60 |
+
social media, and other relevant information. It helps predict potential market reactions to news events.
|
| 61 |
+
</div>
|
| 62 |
+
</div>
|
| 63 |
+
|
| 64 |
+
<div class="feature-card">
|
| 65 |
+
<span>🔮</span>
|
| 66 |
+
<div class="feature-title">Predictions with LSTM</div>
|
| 67 |
+
<div class="feature-detail">
|
| 68 |
+
Long Short-Term Memory (LSTM) models predict future price movements using historical market data.
|
| 69 |
+
These models are highly effective in capturing patterns and trends for more accurate predictions in volatile markets.
|
| 70 |
+
</div>
|
| 71 |
+
</div>
|
| 72 |
+
|
| 73 |
+
<div class="feature-card">
|
| 74 |
+
<span>⚡</span>
|
| 75 |
+
<div class="feature-title">Live Data</div>
|
| 76 |
+
<div class="feature-detail">
|
| 77 |
+
Access to live market data ensures you stay updated with the latest prices, volumes, and other important metrics
|
| 78 |
+
in real-time, enabling you to make quick, informed decisions.
|
| 79 |
+
</div>
|
| 80 |
+
</div>
|
| 81 |
+
|
| 82 |
+
<div class="feature-card">
|
| 83 |
+
<span>💡</span>
|
| 84 |
+
<div class="feature-title">Trade Recommendations</div>
|
| 85 |
+
<div class="feature-detail">
|
| 86 |
+
Get personalized trade recommendations based on your trading strategy and market analysis.
|
| 87 |
+
These recommendations are powered by advanced algorithms that analyze historical and real-time data.
|
| 88 |
+
</div>
|
| 89 |
+
</div>
|
| 90 |
+
|
| 91 |
+
<div class="feature-card">
|
| 92 |
+
<span>📉</span>
|
| 93 |
+
<div class="feature-title">Chart Analysis</div>
|
| 94 |
+
<div class="feature-detail">
|
| 95 |
+
Analyze various types of charts like candlestick, line, and bar charts to identify trends, support, resistance levels,
|
| 96 |
+
and chart patterns. This helps in making better trading decisions.
|
| 97 |
+
</div>
|
| 98 |
+
</div>
|
| 99 |
+
|
| 100 |
+
<div class="feature-card">
|
| 101 |
+
<span>🔔</span>
|
| 102 |
+
<div class="feature-title">Smart Alerts</div>
|
| 103 |
+
<div class="feature-detail">
|
| 104 |
+
Set smart alerts to get notified about market events, price movements, or specific conditions,
|
| 105 |
+
so you never miss important trading opportunities.
|
| 106 |
+
</div>
|
| 107 |
+
</div>
|
| 108 |
+
|
| 109 |
+
<div class="feature-card">
|
| 110 |
+
<span>🤖</span>
|
| 111 |
+
<div class="feature-title">AI Assistant</div>
|
| 112 |
+
<div class="feature-detail">
|
| 113 |
+
The AI Assistant provides insights, analyzes patterns, and helps you make data-driven decisions with the power of artificial intelligence,
|
| 114 |
+
making trading more efficient and less stressful.
|
| 115 |
+
</div>
|
| 116 |
+
</div>
|
| 117 |
+
</div>
|
| 118 |
+
|
| 119 |
+
</div>
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
<footer class="footer">
|
| 124 |
+
<div class="footer-content">
|
| 125 |
+
<div class="footer-left">
|
| 126 |
+
<p>© 2025 PyTrade. All rights reserved.</p>
|
| 127 |
+
</div>
|
| 128 |
+
<div class="footer-center">
|
| 129 |
+
<ul class="footer-links">
|
| 130 |
+
<li><a href="/about">About Us</a></li>
|
| 131 |
+
<li><a href="/contact">Contact</a></li>
|
| 132 |
+
<li><a href="/privacy">Privacy Policy</a></li>
|
| 133 |
+
<li><a href="/terms">Terms & Conditions</a></li>
|
| 134 |
+
</ul>
|
| 135 |
+
</div>
|
| 136 |
+
<div class="footer-right">
|
| 137 |
+
<div class="social-media">
|
| 138 |
+
<a href="https://facebook.com" target="_blank" aria-label="Facebook"><i class="fab fa-facebook"></i></a>
|
| 139 |
+
<a href="https://twitter.com" target="_blank" aria-label="Twitter"><i class="fab fa-twitter"></i></a>
|
| 140 |
+
<a href="https://linkedin.com" target="_blank" aria-label="LinkedIn"><i class="fab fa-linkedin"></i></a>
|
| 141 |
+
<a href="https://instagram.com" target="_blank" aria-label="Instagram"><i class="fab fa-instagram"></i></a>
|
| 142 |
+
</div>
|
| 143 |
+
</div>
|
| 144 |
+
</div>
|
| 145 |
+
</footer>
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
|
src/app/homepage/homepage.scss
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Navigation Styles
|
| 2 |
+
.navigation-container {
|
| 3 |
+
background: linear-gradient(180deg, #0f0f0f, #0f0f0f00);
|
| 4 |
+
position: fixed;
|
| 5 |
+
width: 98%;
|
| 6 |
+
height: 10vw;
|
| 7 |
+
padding: 1vw;
|
| 8 |
+
|
| 9 |
+
.nav-content {
|
| 10 |
+
display: flex;
|
| 11 |
+
gap: 2vw;
|
| 12 |
+
align-items: center;
|
| 13 |
+
justify-content: space-between;
|
| 14 |
+
|
| 15 |
+
.logo-img {
|
| 16 |
+
width: 6vw;
|
| 17 |
+
height: 6vw;
|
| 18 |
+
border-radius: 50%;
|
| 19 |
+
box-shadow: 0 0 15px rgba(255, 255, 255, 0.8);
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
.logo-title {
|
| 23 |
+
font-size: 3vw;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
.menu-item {
|
| 27 |
+
font-size: 1vw;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
.user-avatar {
|
| 31 |
+
width: 2.5vw; /* Size of the avatar */
|
| 32 |
+
height: 2.5vw;
|
| 33 |
+
border-radius: 50%; /* Makes it circular */
|
| 34 |
+
overflow: hidden; /* Ensures image stays within circle */
|
| 35 |
+
display: inline-block;
|
| 36 |
+
position: relative;
|
| 37 |
+
background: linear-gradient(135deg, #38bdf8, #712d74); /* Gradient border */
|
| 38 |
+
padding: 3px; /* Space for gradient border */
|
| 39 |
+
align-self:flex-start
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
.user-avatar img {
|
| 43 |
+
width: 100%;
|
| 44 |
+
height: 100%;
|
| 45 |
+
border-radius: 50%; /* Keep image circular */
|
| 46 |
+
object-fit: cover; /* Cover the container without distortion */
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
// Homepage Styles
|
| 52 |
+
.homepage-container {
|
| 53 |
+
height: 48vw;
|
| 54 |
+
background: url('../../../public/final.png') no-repeat;
|
| 55 |
+
display: flex;
|
| 56 |
+
flex-direction: column;
|
| 57 |
+
justify-content: center;
|
| 58 |
+
align-items: center;
|
| 59 |
+
|
| 60 |
+
.py-title {
|
| 61 |
+
font-size: 4vw;
|
| 62 |
+
color: #fff;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
.py-quote {
|
| 66 |
+
font-size: 1.5vw;
|
| 67 |
+
color: #fff;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
.get-started-btn {
|
| 71 |
+
background: linear-gradient(90deg, #5af4fb, #712d74, #f134d0);
|
| 72 |
+
color: #fff;
|
| 73 |
+
padding: 10px 18px;
|
| 74 |
+
border-radius: 6px;
|
| 75 |
+
cursor: pointer;
|
| 76 |
+
font-weight: bold;
|
| 77 |
+
font-size: 1vw;
|
| 78 |
+
width: 8vw;
|
| 79 |
+
height: 3vw;
|
| 80 |
+
transition: all 0.3s ease-in-out;
|
| 81 |
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
| 82 |
+
|
| 83 |
+
&:hover {
|
| 84 |
+
transform: scale(1.05);
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
// Key Features Section
|
| 90 |
+
.key-feature-container {
|
| 91 |
+
background: url('../../../public/bgkey.png') no-repeat;
|
| 92 |
+
background-size: cover;
|
| 93 |
+
padding-left: 1vw;
|
| 94 |
+
text-align: center;
|
| 95 |
+
margin-top: -2vw;
|
| 96 |
+
padding-top: 2vw;
|
| 97 |
+
padding-bottom: 2vw;
|
| 98 |
+
|
| 99 |
+
h1 {
|
| 100 |
+
color: #fff;
|
| 101 |
+
font-size: 2vw;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
.features {
|
| 105 |
+
display: grid;
|
| 106 |
+
grid-template-columns: repeat(3, 1fr);
|
| 107 |
+
gap: 40px;
|
| 108 |
+
max-width: 75vw;
|
| 109 |
+
margin: auto;
|
| 110 |
+
|
| 111 |
+
.feature-card {
|
| 112 |
+
background: #334155;
|
| 113 |
+
border-radius: 12px;
|
| 114 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
| 115 |
+
transition: transform 0.2s ease-in-out;
|
| 116 |
+
cursor: pointer;
|
| 117 |
+
color: #fff;
|
| 118 |
+
font-weight: bold;
|
| 119 |
+
font-size: 1vw;
|
| 120 |
+
display: flex;
|
| 121 |
+
flex-direction: column;
|
| 122 |
+
justify-content: center;
|
| 123 |
+
align-items: center;
|
| 124 |
+
gap: 1vw;
|
| 125 |
+
height: 13vw;
|
| 126 |
+
|
| 127 |
+
&:hover {
|
| 128 |
+
transform: scale(1.1);
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
span {
|
| 132 |
+
font-size: 2rem;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
.feature-title {
|
| 136 |
+
color: #38bdf8;
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
.feature-detail {
|
| 140 |
+
padding: 1vw;
|
| 141 |
+
font-size: 0.8vw;
|
| 142 |
+
}
|
| 143 |
+
}
|
| 144 |
+
}
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
// Footer Styles
|
| 148 |
+
.footer {
|
| 149 |
+
background: linear-gradient(90deg, #38bdf8, #712d74); /* Gradient from blue to purple */
|
| 150 |
+
color: white;
|
| 151 |
+
padding: 20px 0;
|
| 152 |
+
text-align: center;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
.footer-content {
|
| 156 |
+
display: flex;
|
| 157 |
+
justify-content: space-between;
|
| 158 |
+
align-items: center;
|
| 159 |
+
max-width: 1200px;
|
| 160 |
+
margin: 0 auto;
|
| 161 |
+
padding: 0 20px;
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
.footer-left p {
|
| 165 |
+
font-size: 1rem;
|
| 166 |
+
margin: 0;
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
.footer-center .footer-links {
|
| 170 |
+
list-style: none;
|
| 171 |
+
padding: 0;
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
.footer-center .footer-links li {
|
| 175 |
+
display: inline-block;
|
| 176 |
+
margin: 0 15px;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
.footer-center .footer-links li a {
|
| 180 |
+
color: white;
|
| 181 |
+
text-decoration: none;
|
| 182 |
+
font-size: 1rem;
|
| 183 |
+
transition: color 0.3s ease;
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
.footer-center .footer-links li a:hover {
|
| 187 |
+
color: #f134d0; /* Accent color on hover */
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
.footer-right .social-media a {
|
| 191 |
+
color: white;
|
| 192 |
+
font-size: 1.5rem;
|
| 193 |
+
margin: 0 10px;
|
| 194 |
+
transition: color 0.3s ease;
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
.footer-right .social-media a:hover {
|
| 198 |
+
color: #f134d0; /* Accent color on hover */
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
@media screen and (max-width: 768px) {
|
| 202 |
+
.footer-content {
|
| 203 |
+
flex-direction: column;
|
| 204 |
+
text-align: center;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
.footer-center {
|
| 208 |
+
margin-top: 20px;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
.footer-left,
|
| 212 |
+
.footer-right {
|
| 213 |
+
margin-bottom: 20px;
|
| 214 |
+
}
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
|
src/app/homepage/homepage.spec.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
| 2 |
+
|
| 3 |
+
import { Homepage } from './homepage';
|
| 4 |
+
|
| 5 |
+
describe('Homepage', () => {
|
| 6 |
+
let component: Homepage;
|
| 7 |
+
let fixture: ComponentFixture<Homepage>;
|
| 8 |
+
|
| 9 |
+
beforeEach(async () => {
|
| 10 |
+
await TestBed.configureTestingModule({
|
| 11 |
+
declarations: [Homepage]
|
| 12 |
+
})
|
| 13 |
+
.compileComponents();
|
| 14 |
+
|
| 15 |
+
fixture = TestBed.createComponent(Homepage);
|
| 16 |
+
component = fixture.componentInstance;
|
| 17 |
+
fixture.detectChanges();
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
it('should create', () => {
|
| 21 |
+
expect(component).toBeTruthy();
|
| 22 |
+
});
|
| 23 |
+
});
|
src/app/homepage/homepage.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Component, ElementRef } from '@angular/core';
|
| 2 |
+
|
| 3 |
+
@Component({
|
| 4 |
+
selector: 'app-homepage',
|
| 5 |
+
standalone: false,
|
| 6 |
+
templateUrl: './homepage.html',
|
| 7 |
+
styleUrl: './homepage.scss'
|
| 8 |
+
})
|
| 9 |
+
export class Homepage {
|
| 10 |
+
|
| 11 |
+
constructor(private el: ElementRef) { }
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
ngAfterViewInit(): void {
|
| 15 |
+
const blocks: NodeListOf<HTMLElement> = this.el.nativeElement.querySelectorAll('.reveal');
|
| 16 |
+
|
| 17 |
+
if (!('IntersectionObserver' in window)) {
|
| 18 |
+
blocks.forEach(b => b.classList.add('in-view'));
|
| 19 |
+
return;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
const io = new IntersectionObserver((entries) => {
|
| 23 |
+
entries.forEach(entry => {
|
| 24 |
+
if (entry.isIntersecting) {
|
| 25 |
+
entry.target.classList.remove('in-view');
|
| 26 |
+
void (entry.target as HTMLElement).offsetWidth; // force reflow
|
| 27 |
+
entry.target.classList.add('in-view');
|
| 28 |
+
}
|
| 29 |
+
});
|
| 30 |
+
}, { threshold: 0.3 });
|
| 31 |
+
|
| 32 |
+
blocks.forEach(b => io.observe(b));
|
| 33 |
+
}
|
| 34 |
+
}
|
src/app/marketselect/market.service.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { HttpClient } from '@angular/common/http';
|
| 2 |
+
import { Injectable } from '@angular/core';
|
| 3 |
+
import { Observable } from 'rxjs';
|
| 4 |
+
import { of } from 'rxjs';
|
| 5 |
+
@Injectable({
|
| 6 |
+
providedIn: 'root'
|
| 7 |
+
})
|
| 8 |
+
export class MarketService {
|
| 9 |
+
|
| 10 |
+
private pyCompaniesApiUrl = 'http://127.0.0.1:5000/getcompanies';
|
| 11 |
+
private pyAnalyseApiUrl = 'http://127.0.0.1:5000/analysestock';
|
| 12 |
+
constructor(private http: HttpClient) { }
|
| 13 |
+
|
| 14 |
+
getCompanies(): Observable<any> {
|
| 15 |
+
return this.http.get<any>(this.pyCompaniesApiUrl);
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
getAnalyseResult(ticker: string[]): Observable<any> {
|
| 19 |
+
console.log(ticker)
|
| 20 |
+
return this.http.post<any>(this.pyAnalyseApiUrl, { ticker });
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
}
|
src/app/marketselect/marketselect.html
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<div class="page">
|
| 2 |
+
<header class="header">
|
| 3 |
+
<div class="title">Market Selection</div>
|
| 4 |
+
<div class="subtitle">Choose Country → Exchange → Market, then pick Companies</div>
|
| 5 |
+
</header>
|
| 6 |
+
|
| 7 |
+
<section class="grid">
|
| 8 |
+
<!-- Filters -->
|
| 9 |
+
<div class="card">
|
| 10 |
+
<h3>Filters</h3>
|
| 11 |
+
|
| 12 |
+
<form [formGroup]="form" class="form">
|
| 13 |
+
<label>🌍 Country</label>
|
| 14 |
+
<select class="select" formControlName="country">
|
| 15 |
+
<option value="">-- Select Country --</option>
|
| 16 |
+
<option value="USA">USA</option>
|
| 17 |
+
<option value="India">India</option>
|
| 18 |
+
<option value="USA">Sweden</option>
|
| 19 |
+
<option value="USA">Germany</option>
|
| 20 |
+
</select>
|
| 21 |
+
|
| 22 |
+
<label>🏛️ Exchange</label>
|
| 23 |
+
<select class="select" formControlName="exchange" [disabled]="!exchanges.length">
|
| 24 |
+
<option value="">-- Select Exchange --</option>
|
| 25 |
+
<option *ngFor="let ex of exchanges" [value]="ex">{{ ex }}</option>
|
| 26 |
+
</select>
|
| 27 |
+
|
| 28 |
+
<label>📊 Market</label>
|
| 29 |
+
<select class="select" formControlName="market" [disabled]="!markets.length">
|
| 30 |
+
<option value="">-- Select Market --</option>
|
| 31 |
+
<option *ngFor="let m of markets" [value]="m">{{ m }}</option>
|
| 32 |
+
</select>
|
| 33 |
+
|
| 34 |
+
<button type="button" class="btn-primary" (click)="submit()">Run Analysis</button>
|
| 35 |
+
</form>
|
| 36 |
+
</div>
|
| 37 |
+
|
| 38 |
+
<!-- Companies -->
|
| 39 |
+
<div class="card">
|
| 40 |
+
<div class="companies-head">
|
| 41 |
+
<h3>Companies</h3>
|
| 42 |
+
<div class="badge">{{ (form.value.companies || []).length }}</div>
|
| 43 |
+
</div>
|
| 44 |
+
|
| 45 |
+
<div class="hint" *ngIf="!form.value.market">
|
| 46 |
+
Select a market to view the companies.
|
| 47 |
+
</div>
|
| 48 |
+
|
| 49 |
+
<div class="toolbar" *ngIf="form.value.market && companies.length">
|
| 50 |
+
<button class="btn-ghost" (click)="selectAll()">Select All</button>
|
| 51 |
+
<button class="btn-ghost" (click)="clearAll()" [disabled]="!(form.value.companies || []).length">Clear</button>
|
| 52 |
+
</div>
|
| 53 |
+
|
| 54 |
+
<div class="company-list" *ngIf="form.value.market && companies.length">
|
| 55 |
+
<label class="company"
|
| 56 |
+
*ngFor="let c of companies; trackBy: trackByName"
|
| 57 |
+
(click)="toggleCompany(c)"
|
| 58 |
+
[class.active]="isSelected(c)">
|
| 59 |
+
<span class="tick" aria-hidden="true">✔</span>
|
| 60 |
+
<span class="name">{{ c }}</span>
|
| 61 |
+
</label>
|
| 62 |
+
</div>
|
| 63 |
+
|
| 64 |
+
<div class="empty" *ngIf="form.value.market && !companies.length">
|
| 65 |
+
No companies available for this market.
|
| 66 |
+
</div>
|
| 67 |
+
</div>
|
| 68 |
+
</section>
|
| 69 |
+
</div>
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
<div class="overlay" *ngIf="loading">
|
| 73 |
+
<mat-spinner diameter="50"></mat-spinner>
|
| 74 |
+
<div class="hint">Processing…</div>
|
| 75 |
+
</div>
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
|
src/app/marketselect/marketselect.scss
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:host {
|
| 2 |
+
display: block;
|
| 3 |
+
}
|
| 4 |
+
|
| 5 |
+
.page {
|
| 6 |
+
min-height: 100vh;
|
| 7 |
+
padding: 32px 24px 48px;
|
| 8 |
+
background: linear-gradient(135deg, #0f172a, #1e293b);
|
| 9 |
+
color: #e2e8f0;
|
| 10 |
+
font-family: Arial, sans-serif;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
.header {
|
| 14 |
+
max-width: 1200px;
|
| 15 |
+
margin: 0 auto 24px;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
.title {
|
| 19 |
+
font-size: 28px;
|
| 20 |
+
font-weight: 700;
|
| 21 |
+
color: #38bdf8;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
.subtitle {
|
| 25 |
+
color: #94a3b8;
|
| 26 |
+
margin-top: 4px;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
.grid {
|
| 30 |
+
display: grid;
|
| 31 |
+
grid-template-columns: 1fr 1.3fr;
|
| 32 |
+
gap: 28px;
|
| 33 |
+
max-width: 1200px;
|
| 34 |
+
margin: 0 auto;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
.card {
|
| 38 |
+
background: #1e293b;
|
| 39 |
+
border-radius: 14px;
|
| 40 |
+
box-shadow: 0 8px 20px rgba(0,0,0,.35);
|
| 41 |
+
padding: 22px;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
h3 {
|
| 45 |
+
margin: 0 0 12px;
|
| 46 |
+
color: #e2e8f0;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.form label {
|
| 50 |
+
display: block;
|
| 51 |
+
margin: 14px 0 6px;
|
| 52 |
+
font-weight: 600;
|
| 53 |
+
color: #cbd5e1;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
.select {
|
| 57 |
+
width: 100%;
|
| 58 |
+
background: #0f172a;
|
| 59 |
+
color: #e2e8f0;
|
| 60 |
+
border: 1px solid #334155;
|
| 61 |
+
border-radius: 8px;
|
| 62 |
+
padding: 10px 12px;
|
| 63 |
+
font-size: 14px;
|
| 64 |
+
outline: none;
|
| 65 |
+
transition: border-color .2s;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
.select:focus {
|
| 69 |
+
border-color: #38bdf8;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.btn-primary {
|
| 73 |
+
margin-top: 18px;
|
| 74 |
+
width: 100%;
|
| 75 |
+
padding: 12px 14px;
|
| 76 |
+
font-size: 15px;
|
| 77 |
+
font-weight: 700;
|
| 78 |
+
background: linear-gradient(90deg, #4f46e5, #9333ea, #22c55e);
|
| 79 |
+
color: #fff;
|
| 80 |
+
border: 0;
|
| 81 |
+
border-radius: 10px;
|
| 82 |
+
cursor: pointer;
|
| 83 |
+
transition: opacity .15s;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.btn-primary:hover {
|
| 87 |
+
opacity: .9;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
.companies-head {
|
| 91 |
+
display: flex;
|
| 92 |
+
align-items: center;
|
| 93 |
+
justify-content: space-between;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
.badge {
|
| 97 |
+
background: #0ea5e9;
|
| 98 |
+
color: #0b1220;
|
| 99 |
+
border-radius: 999px;
|
| 100 |
+
padding: 4px 10px;
|
| 101 |
+
font-weight: 800;
|
| 102 |
+
font-size: 12px;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
.hint {
|
| 106 |
+
margin-top: 8px;
|
| 107 |
+
color: #94a3b8;
|
| 108 |
+
font-size: 13px;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.toolbar {
|
| 112 |
+
display: flex;
|
| 113 |
+
gap: 8px;
|
| 114 |
+
margin: 10px 0 8px;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
.btn-ghost {
|
| 118 |
+
background: #0f172a;
|
| 119 |
+
color: #e2e8f0;
|
| 120 |
+
border: 1px solid #334155;
|
| 121 |
+
border-radius: 8px;
|
| 122 |
+
padding: 8px 10px;
|
| 123 |
+
font-size: 13px;
|
| 124 |
+
cursor: pointer;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.btn-ghost:hover {
|
| 128 |
+
border-color: #38bdf8;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
.company-list {
|
| 132 |
+
margin-top: 8px;
|
| 133 |
+
max-height: 320px;
|
| 134 |
+
overflow: auto;
|
| 135 |
+
border: 1px solid #334155;
|
| 136 |
+
border-radius: 10px;
|
| 137 |
+
padding: 8px;
|
| 138 |
+
background: #0f172a;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
.company {
|
| 142 |
+
display: flex;
|
| 143 |
+
align-items: center;
|
| 144 |
+
gap: 10px;
|
| 145 |
+
padding: 10px 8px;
|
| 146 |
+
border-radius: 8px;
|
| 147 |
+
cursor: pointer;
|
| 148 |
+
border: 1px solid transparent;
|
| 149 |
+
transition: background .15s, border-color .15s;
|
| 150 |
+
user-select: none;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
.company:hover {
|
| 154 |
+
background: #0b1220;
|
| 155 |
+
border-color: #1f2a44;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
.company.active {
|
| 159 |
+
background: #0b1220;
|
| 160 |
+
border-color: #22c55e;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
.company .tick {
|
| 164 |
+
opacity: 0;
|
| 165 |
+
font-size: 14px;
|
| 166 |
+
width: 16px;
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
.company.active .tick {
|
| 170 |
+
opacity: 1;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
.company .name {
|
| 174 |
+
flex: 1;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
.empty {
|
| 178 |
+
padding: 14px;
|
| 179 |
+
color: #94a3b8;
|
| 180 |
+
text-align: center;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
@media (max-width: 960px) {
|
| 184 |
+
.grid {
|
| 185 |
+
grid-template-columns: 1fr;
|
| 186 |
+
}
|
| 187 |
+
}
|
| 188 |
+
.overlay {
|
| 189 |
+
position: fixed;
|
| 190 |
+
inset: 0;
|
| 191 |
+
display: grid;
|
| 192 |
+
place-items: center;
|
| 193 |
+
background: rgba(0,0,0,.45);
|
| 194 |
+
z-index: 1000;
|
| 195 |
+
color: #fff;
|
| 196 |
+
text-align: center;
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
.hint {
|
| 200 |
+
margin-top: 12px;
|
| 201 |
+
}
|
src/app/marketselect/marketselect.spec.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
| 2 |
+
|
| 3 |
+
import { Marketselect } from './marketselect';
|
| 4 |
+
|
| 5 |
+
describe('Marketselect', () => {
|
| 6 |
+
let component: Marketselect;
|
| 7 |
+
let fixture: ComponentFixture<Marketselect>;
|
| 8 |
+
|
| 9 |
+
beforeEach(async () => {
|
| 10 |
+
await TestBed.configureTestingModule({
|
| 11 |
+
declarations: [Marketselect]
|
| 12 |
+
})
|
| 13 |
+
.compileComponents();
|
| 14 |
+
|
| 15 |
+
fixture = TestBed.createComponent(Marketselect);
|
| 16 |
+
component = fixture.componentInstance;
|
| 17 |
+
fixture.detectChanges();
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
it('should create', () => {
|
| 21 |
+
expect(component).toBeTruthy();
|
| 22 |
+
});
|
| 23 |
+
});
|
src/app/marketselect/marketselect.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Component, OnInit } from '@angular/core';
|
| 2 |
+
import { CommonModule } from '@angular/common';
|
| 3 |
+
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
| 4 |
+
import { MarketService } from './market.service';
|
| 5 |
+
import { Router } from '@angular/router';
|
| 6 |
+
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
| 7 |
+
import { finalize } from 'rxjs';
|
| 8 |
+
|
| 9 |
+
type CountryKey = 'India' | 'USA';
|
| 10 |
+
type ExchangeKey = 'NSE' | 'BSE' | 'NYSE' | 'NASDAQ';
|
| 11 |
+
|
| 12 |
+
interface ExchangeData {
|
| 13 |
+
markets: string[];
|
| 14 |
+
marketCompanies: Record<string, string[]>; // market -> companies
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
const DATA: Record<CountryKey, Partial<Record<ExchangeKey, ExchangeData>>> = {
|
| 18 |
+
India: {
|
| 19 |
+
NSE: {
|
| 20 |
+
markets: ['Nifty50', 'Nifty100', 'Midcap', 'BankNifty'],
|
| 21 |
+
marketCompanies: {
|
| 22 |
+
Nifty50: ['Reliance Industries', 'TCS', 'Infosys', 'HDFC Bank', 'ICICI Bank', 'ITC', 'Bharti Airtel'],
|
| 23 |
+
Nifty100: ['L&T', 'Axis Bank', 'Kotak Mahindra Bank', 'Hindustan Unilever', 'Bajaj Finance', 'Wipro'],
|
| 24 |
+
Midcap: ['Crompton Greaves', 'Dixon Technologies', 'Deepak Nitrite', 'Astral', 'AU Small Finance Bank'],
|
| 25 |
+
BankNifty: ['HDFC Bank', 'ICICI Bank', 'Axis Bank', 'Kotak Mahindra Bank', 'SBI']
|
| 26 |
+
}
|
| 27 |
+
},
|
| 28 |
+
BSE: {
|
| 29 |
+
markets: ['Sensex', 'BSE 100', 'BSE 200', 'Midcap'],
|
| 30 |
+
marketCompanies: {
|
| 31 |
+
Sensex: ['Reliance Industries', 'TCS', 'Infosys', 'HDFC Bank', 'HCLTech', 'Tata Motors', 'NTPC'],
|
| 32 |
+
'BSE 100': ['Mahindra & Mahindra', 'UltraTech Cement', 'Power Grid', 'Sun Pharma', 'Maruti Suzuki'],
|
| 33 |
+
'BSE 200': ['Pidilite', 'Dabur', 'Divi’s Labs', 'Britannia', 'Grasim'],
|
| 34 |
+
Midcap: ['Voltas', 'BEL', 'Tata Power', 'Tata Elxsi', 'UBL']
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
USA: {
|
| 39 |
+
NYSE: {
|
| 40 |
+
markets: ['S&P 500', 'Dow Jones', 'Midcap', 'Sector ETFs'],
|
| 41 |
+
marketCompanies: {
|
| 42 |
+
'S&P 500': ['Apple', 'Microsoft', 'NVIDIA', 'Amazon', 'Meta Platforms', 'Alphabet (A)'],
|
| 43 |
+
'Dow Jones': ['Coca-Cola', 'Johnson & Johnson', 'Walmart', 'Chevron', 'Visa'],
|
| 44 |
+
Midcap: ['Ball Corp', 'Keysight', 'Fortinet', 'CrowdStrike', 'AMD'],
|
| 45 |
+
'Sector ETFs': ['XLE', 'XLK', 'XLF', 'XLV', 'XLY']
|
| 46 |
+
}
|
| 47 |
+
},
|
| 48 |
+
NASDAQ: {
|
| 49 |
+
markets: ['NASDAQ 100', 'Growth', 'Midcap'],
|
| 50 |
+
marketCompanies: {
|
| 51 |
+
'NASDAQ 100': ['Apple', 'Microsoft', 'NVIDIA', 'Amazon', 'Meta Platforms', 'Tesla', 'Adobe', 'Netflix'],
|
| 52 |
+
Growth: ['Snowflake', 'Datadog', 'ServiceNow', 'MongoDB', 'Cloudflare'],
|
| 53 |
+
Midcap: ['Zscaler', 'Okta', 'Trade Desk', 'Marvell Technology', 'On Semiconductor']
|
| 54 |
+
}
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
};
|
| 58 |
+
|
| 59 |
+
@Component({
|
| 60 |
+
selector: 'app-marketselect',
|
| 61 |
+
standalone: true,
|
| 62 |
+
imports: [CommonModule, ReactiveFormsModule, MatProgressSpinnerModule],
|
| 63 |
+
templateUrl: './marketselect.html',
|
| 64 |
+
styleUrls: ['./marketselect.scss']
|
| 65 |
+
})
|
| 66 |
+
export class MarketSelectComponent implements OnInit {
|
| 67 |
+
form: FormGroup;
|
| 68 |
+
loading = false;
|
| 69 |
+
exchanges: ExchangeKey[] = [];
|
| 70 |
+
markets: string[] = [];
|
| 71 |
+
companies: string[] = []; // populated only after market selection
|
| 72 |
+
selectedCompanies: string[] = [];
|
| 73 |
+
constructor(private fb: FormBuilder, private marketService: MarketService, private router:Router) {
|
| 74 |
+
this.form = this.fb.group({
|
| 75 |
+
country: new FormControl<CountryKey | ''>(''),
|
| 76 |
+
exchange: new FormControl<ExchangeKey | ''>(''),
|
| 77 |
+
market: new FormControl<string | ''>(''),
|
| 78 |
+
companies: this.fb.control<string[]>([])
|
| 79 |
+
});
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
ngOnInit(): void {
|
| 83 |
+
|
| 84 |
+
this.marketService.getCompanies()
|
| 85 |
+
.subscribe({
|
| 86 |
+
next: res => {
|
| 87 |
+
console.log(res);
|
| 88 |
+
},
|
| 89 |
+
error: err => {
|
| 90 |
+
console.error('API error:', err);
|
| 91 |
+
}
|
| 92 |
+
});
|
| 93 |
+
|
| 94 |
+
// Country change: reset and load exchanges
|
| 95 |
+
this.form.get('country')!.valueChanges.subscribe((country) => {
|
| 96 |
+
this.form.patchValue({ exchange: '', market: '', companies: [] }, { emitEvent: false });
|
| 97 |
+
this.exchanges = this.getExchanges(country as CountryKey | '');
|
| 98 |
+
this.markets = [];
|
| 99 |
+
this.companies = [];
|
| 100 |
+
});
|
| 101 |
+
|
| 102 |
+
// Exchange change: reset and load markets
|
| 103 |
+
this.form.get('exchange')!.valueChanges.subscribe((exchange) => {
|
| 104 |
+
const country = this.form.value.country as CountryKey | '';
|
| 105 |
+
this.form.patchValue({ market: '', companies: [] }, { emitEvent: false });
|
| 106 |
+
this.markets = this.getMarkets(country, exchange as ExchangeKey | '');
|
| 107 |
+
this.companies = [];
|
| 108 |
+
});
|
| 109 |
+
|
| 110 |
+
// Market change: load companies for that market
|
| 111 |
+
this.form.get('market')!.valueChanges.subscribe((market) => {
|
| 112 |
+
const country = this.form.value.country as CountryKey | '';
|
| 113 |
+
const exchange = this.form.value.exchange as ExchangeKey | '';
|
| 114 |
+
this.form.patchValue({ companies: [] }, { emitEvent: false });
|
| 115 |
+
this.companies = this.getCompanies(country, exchange, (market || '') as string);
|
| 116 |
+
});
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
// Helpers
|
| 120 |
+
private getExchanges(country: CountryKey | ''): ExchangeKey[] {
|
| 121 |
+
if (!country) return [];
|
| 122 |
+
return Object.keys(DATA[country] || {}) as ExchangeKey[];
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
private getMarkets(country: CountryKey | '', exchange: ExchangeKey | ''): string[] {
|
| 126 |
+
if (!country || !exchange) return [];
|
| 127 |
+
return DATA[country]?.[exchange]?.markets ?? [];
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
private getCompanies(country: CountryKey | '', exchange: ExchangeKey | '', market: string): string[] {
|
| 131 |
+
if (!country || !exchange || !market) return [];
|
| 132 |
+
return DATA[country]?.[exchange]?.marketCompanies?.[market] ?? [];
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
// Company selection
|
| 136 |
+
isSelected(name: string): boolean {
|
| 137 |
+
const selected = this.form.value.companies ?? [];
|
| 138 |
+
return selected.includes(name);
|
| 139 |
+
}
|
| 140 |
+
toggleCompany(name: string): void {
|
| 141 |
+
const set = new Set(this.form.value.companies ?? []);
|
| 142 |
+
set.has(name) ? set.delete(name) : set.add(name);
|
| 143 |
+
this.form.patchValue({ companies: Array.from(set) });
|
| 144 |
+
}
|
| 145 |
+
selectAll(): void {
|
| 146 |
+
this.form.patchValue({ companies: this.companies.slice() });
|
| 147 |
+
}
|
| 148 |
+
clearAll(): void {
|
| 149 |
+
this.form.patchValue({ companies: [] });
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
submit(): void {
|
| 153 |
+
this.loading = true;
|
| 154 |
+
this.selectedCompanies = ['TCS.NS', 'ICICIBANK.NS']
|
| 155 |
+
this.marketService.getAnalyseResult(this.selectedCompanies)
|
| 156 |
+
.pipe(finalize(() => this.loading = false))
|
| 157 |
+
.subscribe({
|
| 158 |
+
next: res => {
|
| 159 |
+
// navigate AFTER data is returned; pass the value
|
| 160 |
+
console.log(res);
|
| 161 |
+
this.router.navigate(['/analysepage'], { state: { result: res } });
|
| 162 |
+
},
|
| 163 |
+
error: err => {
|
| 164 |
+
console.error('API error:', err);
|
| 165 |
+
// you can add MatSnackBar here if needed
|
| 166 |
+
}
|
| 167 |
+
});
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
trackByName = (_: number, name: string) => name;
|
| 173 |
+
}
|
src/app/screenerpage/screenerpage.css
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
body {
|
| 2 |
+
background-color: #0f172a;
|
| 3 |
+
color: #e2e8f0;
|
| 4 |
+
font-family: Arial, sans-serif;
|
| 5 |
+
margin: 0;
|
| 6 |
+
padding: 20px;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
h1, h2 {
|
| 10 |
+
text-align: center;
|
| 11 |
+
color: #38bdf8;
|
| 12 |
+
margin-bottom: 20px;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
.dashboard {
|
| 16 |
+
display: grid;
|
| 17 |
+
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
| 18 |
+
gap: 25px;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
.card {
|
| 22 |
+
background: #1e293b;
|
| 23 |
+
border-radius: 12px;
|
| 24 |
+
padding: 20px;
|
| 25 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
.card h2 {
|
| 29 |
+
margin-bottom: 15px;
|
| 30 |
+
font-size: 1.2rem;
|
| 31 |
+
color: #38bdf8;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
table {
|
| 35 |
+
width: 100%;
|
| 36 |
+
border-collapse: collapse;
|
| 37 |
+
margin-top: 10px;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
table th, table td {
|
| 41 |
+
border-bottom: 1px solid #334155;
|
| 42 |
+
padding: 8px;
|
| 43 |
+
text-align: left;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
table th {
|
| 47 |
+
color: #38bdf8;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
.big-table {
|
| 51 |
+
margin-top: 40px;
|
| 52 |
+
background: #1e293b;
|
| 53 |
+
padding: 20px;
|
| 54 |
+
border-radius: 12px;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.main {
|
| 58 |
+
flex: 1;
|
| 59 |
+
display: flex;
|
| 60 |
+
flex-direction: column;
|
| 61 |
+
padding: 20px;
|
| 62 |
+
gap: 20px;
|
| 63 |
+
overflow-y: auto;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.breadcrumb {
|
| 67 |
+
display: flex;
|
| 68 |
+
align-items: center;
|
| 69 |
+
font-size: 0.95em;
|
| 70 |
+
color: #94a3b8;
|
| 71 |
+
margin-bottom: -10px;
|
| 72 |
+
font-size: 16px;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.breadcrumb a {
|
| 76 |
+
color: #38bdf8;
|
| 77 |
+
text-decoration: none;
|
| 78 |
+
margin-right: 5px;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
.breadcrumb span {
|
| 82 |
+
margin: 0 5px;
|
| 83 |
+
}
|