switched to angular
This commit is contained in:
parent
d3efed7ce7
commit
94e3c96ef6
53 changed files with 10976 additions and 1020 deletions
227
CONTEXT.md
Normal file
227
CONTEXT.md
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
# 📋 Stock Bot Trading System - Architecture Context
|
||||
|
||||
## 🏗️ System Overview
|
||||
|
||||
A comprehensive, microservice-based trading bot system built with **Bun**, **TypeScript**, and **Turborepo**. The system features a service-oriented architecture designed for real-time market data processing, strategy execution, and risk management.
|
||||
|
||||
## 🎯 Current System Status
|
||||
|
||||
### ✅ **Operational Services**
|
||||
- **Market Data Gateway** (`apps/core-services/market-data-gateway`) - Port 3001
|
||||
- Real-time market data ingestion from multiple sources
|
||||
- WebSocket server for live data streaming
|
||||
- REST API for market data queries
|
||||
- Event publishing to Dragonfly streams
|
||||
|
||||
- **Trading Dashboard** (`apps/interface-services/trading-dashboard`) - Port 5173
|
||||
- React + TypeScript frontend with Tremor UI
|
||||
- Real-time data visualization
|
||||
- WebSocket client for live updates
|
||||
- Professional financial dashboard components
|
||||
|
||||
### 🚧 **Ready for Implementation**
|
||||
- **Strategy Orchestrator** (`apps/intelligence-services/strategy-orchestrator`) - Port 4001
|
||||
- Package structure created, implementation needed
|
||||
- Strategy execution and management
|
||||
- Signal generation coordination
|
||||
|
||||
- **Risk Guardian** (`apps/core-services/risk-guardian`) - Port 3002
|
||||
- Package structure created, implementation needed
|
||||
- Real-time risk monitoring and alerts
|
||||
- Position and exposure limits
|
||||
|
||||
## 🏗️ Service Architecture
|
||||
|
||||
### **Service Categories**
|
||||
```
|
||||
apps/
|
||||
├── core-services/ # Essential trading infrastructure
|
||||
│ ├── market-data-gateway/ ✅ Operational
|
||||
│ └── risk-guardian/ 📋 Ready to implement
|
||||
├── intelligence-services/ # Strategy and signal generation
|
||||
│ └── strategy-orchestrator/ 📋 Ready to implement
|
||||
├── interface-services/ # User interfaces and APIs
|
||||
│ ├── trading-dashboard/ ✅ Operational
|
||||
│ └── trading-dashboard-react/ 📋 Alternative implementation
|
||||
├── data-services/ # Data processing and analytics
|
||||
├── execution-services/ # Order management and execution
|
||||
├── integration-services/ # External system integrations
|
||||
└── platform-services/ # Infrastructure and monitoring
|
||||
```
|
||||
|
||||
### **Shared Packages**
|
||||
```
|
||||
packages/
|
||||
├── shared-types/ # TypeScript type definitions
|
||||
├── config/ # Configuration management
|
||||
├── database/ # Database utilities (planned)
|
||||
└── trading-core/ # Core trading logic (planned)
|
||||
```
|
||||
|
||||
## 🔄 Communication Architecture
|
||||
|
||||
### **Event-Driven Core (Dragonfly)**
|
||||
- **Primary Event Bus**: Dragonfly Redis-compatible streams
|
||||
- **Event Types**: Market data, trading signals, risk alerts, portfolio updates
|
||||
- **Communication Pattern**: Publish/Subscribe for loose coupling
|
||||
- **Real-time Processing**: Sub-millisecond event propagation
|
||||
|
||||
### **Data Flow Patterns**
|
||||
1. **Market Data Flow**
|
||||
```
|
||||
External APIs → Market Data Gateway → Dragonfly Events → Dashboard/Services
|
||||
```
|
||||
|
||||
2. **Trading Signal Flow**
|
||||
```
|
||||
Market Data → Strategy Orchestrator → Signals → Risk Guardian → Execution
|
||||
```
|
||||
|
||||
3. **Real-time Updates**
|
||||
```
|
||||
Services → WebSocket Server → Dashboard (Live UI Updates)
|
||||
```
|
||||
|
||||
### **API Communication**
|
||||
- **REST APIs**: Service-to-service and client-to-service communication
|
||||
- **WebSocket**: Real-time bidirectional communication
|
||||
- **HTTP Health Checks**: Service monitoring and discovery
|
||||
|
||||
## 🗄️ Data Infrastructure
|
||||
|
||||
### **Storage Systems**
|
||||
- **🐉 Dragonfly** (Port 6379): Redis-compatible event streaming and caching
|
||||
- **🐘 PostgreSQL** (Port 5432): Operational data with trading schemas
|
||||
- **📊 QuestDB** (Ports 9000/8812): Time-series market data and analytics
|
||||
- **🍃 MongoDB** (Port 27017): Document storage for sentiment analysis and raw documents
|
||||
|
||||
### **Database Schemas**
|
||||
- `trading.*` - Orders, positions, executions, accounts
|
||||
- `strategy.*` - Strategies, signals, performance metrics
|
||||
- `risk.*` - Risk limits, events, monitoring
|
||||
- `audit.*` - System events, health checks, configuration
|
||||
|
||||
### **MongoDB Collections**
|
||||
- `sentiment_analysis` - Market sentiment scores and analysis
|
||||
- `raw_documents` - News articles, social media posts, research reports
|
||||
- `market_events` - Significant market events and their impact
|
||||
|
||||
### **Admin Interfaces**
|
||||
- **Redis Insight** (Port 8001): Dragonfly management
|
||||
- **PgAdmin** (Port 8080): PostgreSQL administration
|
||||
- **QuestDB Console** (Port 9000): Time-series data management
|
||||
- **Mongo Express** (Port 8081): MongoDB document browser and editor
|
||||
|
||||
## 🛡️ Infrastructure & DevOps
|
||||
|
||||
### **Container Management**
|
||||
- **Docker Compose**: Multi-service orchestration
|
||||
- **Development Environment**: `npm run dev:full`
|
||||
- **Infrastructure Management**: PowerShell scripts in `scripts/docker.ps1`
|
||||
|
||||
### **Monitoring Stack**
|
||||
- **Prometheus** (Port 9090): Metrics collection
|
||||
- **Grafana** (Port 3000): Dashboards and alerting
|
||||
- **Health Checks**: Each service exposes `/health` endpoints
|
||||
|
||||
### **Development Workflow**
|
||||
```bash
|
||||
# Start infrastructure
|
||||
npm run infra:up
|
||||
|
||||
# Start admin tools
|
||||
npm run docker:admin
|
||||
|
||||
# Start development services
|
||||
npm run dev:full
|
||||
```
|
||||
|
||||
## 🌐 Port Allocation
|
||||
|
||||
| Service Category | Port Range | Current Services |
|
||||
|-----------------|------------|------------------|
|
||||
| **Core Services** | 3001-3099 | Market Data Gateway (3001), Risk Guardian (3002) |
|
||||
| **Intelligence** | 4001-4099 | Strategy Orchestrator (4001) |
|
||||
| **Data Services** | 5001-5099 | (Future expansion) |
|
||||
| **Interface** | 5173, 8001+ | Trading Dashboard (5173) |
|
||||
| **Infrastructure** | 6379, 5432, 9000+, 27017 | Dragonfly, PostgreSQL, QuestDB, MongoDB |
|
||||
|
||||
## 🎯 Implementation Roadmap
|
||||
|
||||
### **Phase 1 - Foundation** ✅ Complete
|
||||
- [x] Monorepo setup with Turborepo
|
||||
- [x] Market Data Gateway with real-time streaming
|
||||
- [x] Professional React dashboard with Tremor UI
|
||||
- [x] Docker infrastructure with Dragonfly/PostgreSQL/QuestDB
|
||||
- [x] WebSocket real-time communication
|
||||
|
||||
### **Phase 2 - Trading Logic** 📋 Next Priority
|
||||
- [ ] Strategy Orchestrator implementation
|
||||
- [ ] Risk Guardian implementation
|
||||
- [ ] Event-driven strategy execution
|
||||
- [ ] Portfolio position tracking
|
||||
|
||||
### **Phase 3 - Advanced Features** ⏳ Future
|
||||
- [ ] Execution Engine with broker integration
|
||||
- [ ] Advanced analytics and backtesting
|
||||
- [ ] Machine learning signal generation
|
||||
- [ ] Multi-broker support
|
||||
|
||||
## 🔧 Technology Stack
|
||||
|
||||
### **Backend Services**
|
||||
- **Runtime**: Bun (fast JavaScript runtime)
|
||||
- **Framework**: Hono (lightweight web framework)
|
||||
- **Language**: TypeScript (type safety)
|
||||
- **Events**: Dragonfly Redis Streams
|
||||
- **WebSocket**: Native WebSocket implementation
|
||||
|
||||
### **Frontend**
|
||||
- **Framework**: React with TypeScript
|
||||
- **Build Tool**: Vite (fast development)
|
||||
- **UI Library**: Tremor UI (financial components)
|
||||
- **Styling**: Modern CSS with responsive design
|
||||
|
||||
### **Infrastructure**
|
||||
- **Monorepo**: Turborepo for build orchestration
|
||||
- **Containers**: Docker & Docker Compose
|
||||
- **Databases**: PostgreSQL, QuestDB, Dragonfly
|
||||
- **Monitoring**: Prometheus + Grafana
|
||||
|
||||
## 🔒 Security & Configuration
|
||||
|
||||
### **Environment Management**
|
||||
- Environment-specific configurations
|
||||
- Secure credential management
|
||||
- Development vs production separation
|
||||
|
||||
### **Service Security**
|
||||
- Inter-service authentication
|
||||
- API rate limiting
|
||||
- Database connection security
|
||||
- External API key management
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
1. **Prerequisites**: Docker Desktop, Bun runtime
|
||||
2. **Infrastructure**: `npm run infra:up`
|
||||
3. **Admin Tools**: `npm run docker:admin`
|
||||
4. **Development**: `npm run dev:full`
|
||||
5. **Access**: Dashboard at http://localhost:5173
|
||||
|
||||
## 📁 Key Configuration Files
|
||||
|
||||
- `turbo.json` - Monorepo build configuration
|
||||
- `docker-compose.yml` - Infrastructure orchestration
|
||||
- `packages/config/` - Shared configuration management
|
||||
- `database/postgres/init/` - Database schema definitions
|
||||
|
||||
---
|
||||
|
||||
**Architecture Design Principles:**
|
||||
- **Microservices**: Independent, scalable services
|
||||
- **Event-Driven**: Loose coupling via Dragonfly events
|
||||
- **Type Safety**: TypeScript across all services
|
||||
- **Real-time**: WebSocket and event streaming
|
||||
- **Observability**: Comprehensive monitoring and logging
|
||||
- **Developer Experience**: Fast development with hot reload
|
||||
|
|
@ -8,10 +8,12 @@ Your Docker infrastructure has been successfully configured. Here's what you hav
|
|||
- **🐉 Dragonfly**: Redis-compatible cache and event streaming (Port 6379)
|
||||
- **🐘 PostgreSQL**: Operational database with complete trading schema (Port 5432)
|
||||
- **📊 QuestDB**: Time-series database for market data (Ports 9000, 8812, 9009)
|
||||
- **🍃 MongoDB**: Document storage for sentiment analysis and raw documents (Port 27017)
|
||||
|
||||
### Admin Tools
|
||||
- **🔧 Redis Insight**: Dragonfly management GUI (Port 8001)
|
||||
- **🛠️ PgAdmin**: PostgreSQL administration (Port 8080)
|
||||
- **🍃 Mongo Express**: MongoDB document browser (Port 8081)
|
||||
|
||||
### Monitoring (Optional)
|
||||
- **📈 Prometheus**: Metrics collection (Port 9090)
|
||||
|
|
@ -49,6 +51,7 @@ Once running, access these services:
|
|||
| **QuestDB Console** | http://localhost:9000 | No login required |
|
||||
| **Redis Insight** | http://localhost:8001 | No login required |
|
||||
| **PgAdmin** | http://localhost:8080 | `admin@tradingbot.local` / `admin123` |
|
||||
| **Mongo Express** | http://localhost:8081 | `admin` / `admin123` |
|
||||
| **Prometheus** | http://localhost:9090 | No login required |
|
||||
| **Grafana** | http://localhost:3000 | `admin` / `admin123` |
|
||||
|
||||
|
|
@ -72,6 +75,13 @@ POSTGRES_PASSWORD=trading_pass_dev
|
|||
QUESTDB_HOST=localhost
|
||||
QUESTDB_PORT=8812
|
||||
QUESTDB_DB=qdb
|
||||
|
||||
# MongoDB
|
||||
MONGODB_HOST=localhost
|
||||
MONGODB_PORT=27017
|
||||
MONGODB_DB=trading_documents
|
||||
MONGODB_USER=trading_admin
|
||||
MONGODB_PASSWORD=trading_mongo_dev
|
||||
```
|
||||
|
||||
### Database Schema
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
}, "dependencies": {
|
||||
"hono": "^4.6.3",
|
||||
"ioredis": "^5.4.1",
|
||||
"@stock-bot/config": "workspace:*",
|
||||
"@stock-bot/shared-types": "workspace:*",
|
||||
"@stock-bot/config": "*",
|
||||
"@stock-bot/shared-types": "*",
|
||||
"ws": "^8.18.0"
|
||||
}, "devDependencies": {
|
||||
"bun-types": "^1.2.15",
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
"dependencies": {
|
||||
"hono": "^4.6.3",
|
||||
"ioredis": "^5.4.1",
|
||||
"@stock-bot/config": "workspace:*",
|
||||
"@stock-bot/shared-types": "workspace:*",
|
||||
"@stock-bot/config": "*",
|
||||
"@stock-bot/shared-types": "*",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
"dependencies": {
|
||||
"hono": "^4.6.3",
|
||||
"ioredis": "^5.4.1",
|
||||
"@stock-bot/config": "workspace:*",
|
||||
"@stock-bot/shared-types": "workspace:*",
|
||||
"@stock-bot/config": "*",
|
||||
"@stock-bot/shared-types": "*",
|
||||
"ws": "^8.18.0",
|
||||
"node-cron": "^3.0.3"
|
||||
},
|
||||
|
|
|
|||
17
apps/interface-services/trading-dashboard/.editorconfig
Normal file
17
apps/interface-services/trading-dashboard/.editorconfig
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
ij_typescript_use_double_quotes = false
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
|
|
@ -1,24 +1,42 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
# Compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
/bazel-out
|
||||
|
||||
# Editor directories and files
|
||||
# Node
|
||||
/node_modules
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# IDEs and editors
|
||||
.idea/
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.history/*
|
||||
|
||||
# Miscellaneous
|
||||
/.angular/cache
|
||||
.sass-cache/
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System files
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
Thumbs.db
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"plugins": {
|
||||
"@tailwindcss/postcss": {}
|
||||
}
|
||||
}
|
||||
4
apps/interface-services/trading-dashboard/.vscode/extensions.json
vendored
Normal file
4
apps/interface-services/trading-dashboard/.vscode/extensions.json
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
|
||||
"recommendations": ["angular.ng-template"]
|
||||
}
|
||||
20
apps/interface-services/trading-dashboard/.vscode/launch.json
vendored
Normal file
20
apps/interface-services/trading-dashboard/.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "ng serve",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "npm: start",
|
||||
"url": "http://localhost:4200/"
|
||||
},
|
||||
{
|
||||
"name": "ng test",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "npm: test",
|
||||
"url": "http://localhost:9876/debug.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
42
apps/interface-services/trading-dashboard/.vscode/tasks.json
vendored
Normal file
42
apps/interface-services/trading-dashboard/.vscode/tasks.json
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "start",
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"pattern": "$tsc",
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": {
|
||||
"regexp": "(.*?)"
|
||||
},
|
||||
"endsPattern": {
|
||||
"regexp": "bundle generation complete"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "test",
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"pattern": "$tsc",
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": {
|
||||
"regexp": "(.*?)"
|
||||
},
|
||||
"endsPattern": {
|
||||
"regexp": "bundle generation complete"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,54 +1,59 @@
|
|||
# React + TypeScript + Vite
|
||||
# TradingDashboard
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.0.0.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
## Development server
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
To start a local development server, run:
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||
|
||||
```js
|
||||
export default tseslint.config({
|
||||
extends: [
|
||||
// Remove ...tseslint.configs.recommended and replace with this
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
// Alternatively, use this for stricter rules
|
||||
...tseslint.configs.strictTypeChecked,
|
||||
// Optionally, add this for stylistic rules
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
],
|
||||
languageOptions: {
|
||||
// other options...
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
})
|
||||
```bash
|
||||
ng serve
|
||||
```
|
||||
|
||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import reactX from 'eslint-plugin-react-x'
|
||||
import reactDom from 'eslint-plugin-react-dom'
|
||||
## Code scaffolding
|
||||
|
||||
export default tseslint.config({
|
||||
plugins: {
|
||||
// Add the react-x and react-dom plugins
|
||||
'react-x': reactX,
|
||||
'react-dom': reactDom,
|
||||
},
|
||||
rules: {
|
||||
// other rules...
|
||||
// Enable its recommended typescript rules
|
||||
...reactX.configs['recommended-typescript'].rules,
|
||||
...reactDom.configs.recommended.rules,
|
||||
},
|
||||
})
|
||||
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
|
||||
|
||||
```bash
|
||||
ng generate component component-name
|
||||
```
|
||||
|
||||
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
|
||||
|
||||
```bash
|
||||
ng generate --help
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To build the project run:
|
||||
|
||||
```bash
|
||||
ng build
|
||||
```
|
||||
|
||||
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
|
||||
|
||||
```bash
|
||||
ng test
|
||||
```
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
For end-to-end (e2e) testing, run:
|
||||
|
||||
```bash
|
||||
ng e2e
|
||||
```
|
||||
|
||||
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|
||||
|
|
|
|||
94
apps/interface-services/trading-dashboard/angular.json
Normal file
94
apps/interface-services/trading-dashboard/angular.json
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"cli": {
|
||||
"packageManager": "npm"
|
||||
},
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"trading-dashboard": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss"
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular/build:application",
|
||||
"options": {
|
||||
"browser": "src/main.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kB",
|
||||
"maximumError": "1MB"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "4kB",
|
||||
"maximumError": "8kB"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular/build:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "trading-dashboard:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "trading-dashboard:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular/build:extract-i18n"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular/build:karma",
|
||||
"options": {
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,32 +1,44 @@
|
|||
{
|
||||
"name": "trading-dashboard",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"dev": "ng serve --port 5173 --host 0.0.0.0",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@stock-bot/config": "workspace:*",
|
||||
"@stock-bot/shared-types": "workspace:*",
|
||||
"@tremor/react": "^3.18.7",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
"@angular/animations": "^20.0.0",
|
||||
"@angular/cdk": "^20.0.1",
|
||||
"@angular/common": "^20.0.0",
|
||||
"@angular/compiler": "^20.0.0",
|
||||
"@angular/core": "^20.0.0",
|
||||
"@angular/forms": "^20.0.0",
|
||||
"@angular/material": "^20.0.1",
|
||||
"@angular/platform-browser": "^20.0.0",
|
||||
"@angular/router": "^20.0.0",
|
||||
"rxjs": "~7.8.2",
|
||||
"tslib": "^2.8.1",
|
||||
"zone.js": "~0.15.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.0",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"eslint": "^9.25.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"globals": "^16.0.0",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.30.1",
|
||||
"vite": "^6.3.5"
|
||||
"@angular/build": "^20.0.0",
|
||||
"@angular/cli": "^20.0.0",
|
||||
"@angular/compiler-cli": "^20.0.0",
|
||||
"@tailwindcss/postcss": "^4.1.8",
|
||||
"@types/jasmine": "~5.1.8",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"jasmine-core": "~5.7.1",
|
||||
"karma": "~6.4.4",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "~2.2.1",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"postcss": "^8.5.4",
|
||||
"tailwindcss": "^4.1.8",
|
||||
"typescript": "~5.8.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
apps/interface-services/trading-dashboard/public/favicon.ico
Normal file
BIN
apps/interface-services/trading-dashboard/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
|
|
@ -1,42 +0,0 @@
|
|||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||
}
|
||||
|
||||
@keyframes logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
a:nth-of-type(2) .logo {
|
||||
animation: logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import { TradingDashboard } from './components/TradingDashboard'
|
||||
import './App.css'
|
||||
|
||||
function App() {
|
||||
return <TradingDashboard />
|
||||
}
|
||||
|
||||
export default App
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
provideZonelessChangeDetection(),
|
||||
provideRouter(routes),
|
||||
provideAnimationsAsync()
|
||||
]
|
||||
};
|
||||
562
apps/interface-services/trading-dashboard/src/app/app.html
Normal file
562
apps/interface-services/trading-dashboard/src/app/app.html
Normal file
|
|
@ -0,0 +1,562 @@
|
|||
<div class="min-h-screen bg-gray-50">
|
||||
<mat-sidenav-container class="min-h-screen">
|
||||
<!-- Sidebar -->
|
||||
<mat-sidenav
|
||||
[opened]="sidenavOpened()"
|
||||
mode="side"
|
||||
class="w-64 bg-white border-r border-gray-200">
|
||||
|
||||
<!-- Logo/Brand -->
|
||||
<div class="p-6 border-b border-gray-200">
|
||||
<h2 class="text-xl font-bold text-gray-900">
|
||||
📈 Trading Bot
|
||||
</h2>
|
||||
<p class="text-sm text-gray-600 mt-1">
|
||||
Real-time Dashboard
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Menu -->
|
||||
<nav class="p-6">
|
||||
<div class="space-y-2">
|
||||
<button mat-button class="w-full text-left bg-blue-50 text-blue-700">
|
||||
<mat-icon class="mr-3">dashboard</mat-icon>
|
||||
Dashboard
|
||||
</button>
|
||||
<button mat-button class="w-full text-left text-gray-600 hover:bg-gray-100">
|
||||
<mat-icon class="mr-3">trending_up</mat-icon>
|
||||
Market Data
|
||||
</button>
|
||||
<button mat-button class="w-full text-left text-gray-600 hover:bg-gray-100">
|
||||
<mat-icon class="mr-3">account_balance_wallet</mat-icon>
|
||||
Portfolio
|
||||
</button>
|
||||
<button mat-button class="w-full text-left text-gray-600 hover:bg-gray-100">
|
||||
<mat-icon class="mr-3">psychology</mat-icon>
|
||||
Strategies
|
||||
</button>
|
||||
<button mat-button class="w-full text-left text-gray-600 hover:bg-gray-100">
|
||||
<mat-icon class="mr-3">security</mat-icon>
|
||||
Risk Management
|
||||
</button>
|
||||
<button mat-button class="w-full text-left text-gray-600 hover:bg-gray-100">
|
||||
<mat-icon class="mr-3">settings</mat-icon>
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</mat-sidenav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<mat-sidenav-content>
|
||||
<!-- Top Toolbar -->
|
||||
<mat-toolbar class="bg-white border-b border-gray-200 shadow-sm">
|
||||
<button mat-icon-button (click)="toggleSidenav()" class="mr-4">
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
|
||||
<span class="flex-1 text-lg font-semibold text-gray-900">
|
||||
{{ title }}
|
||||
</span>
|
||||
|
||||
<!-- Status Indicators -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<mat-chip-set>
|
||||
<mat-chip class="bg-green-100 text-green-800">
|
||||
<mat-icon matChipAvatar>wifi</mat-icon>
|
||||
Live Data
|
||||
</mat-chip>
|
||||
<mat-chip class="bg-blue-100 text-blue-800">
|
||||
<mat-icon matChipAvatar>smart_toy</mat-icon>
|
||||
Bot Active
|
||||
</mat-chip>
|
||||
</mat-chip-set>
|
||||
|
||||
<button mat-icon-button>
|
||||
<mat-icon>notifications</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button>
|
||||
<mat-icon>account_circle</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-toolbar>
|
||||
|
||||
<!-- Dashboard Content -->
|
||||
<div class="p-6 space-y-6">
|
||||
<!-- Portfolio Summary Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<!-- Total Portfolio Value -->
|
||||
<mat-card class="p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-600">Portfolio Value</p>
|
||||
<p class="text-2xl font-bold text-gray-900">
|
||||
${{ portfolioValue().toLocaleString('en-US', {minimumFractionDigits: 2}) }}
|
||||
</p>
|
||||
</div>
|
||||
<mat-icon class="text-blue-600">account_balance_wallet</mat-icon>
|
||||
</div>
|
||||
</mat-card>
|
||||
|
||||
<!-- Day Change -->
|
||||
<mat-card class="p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-600">Day Change</p>
|
||||
<p class="text-2xl font-bold"
|
||||
[class.text-green-600]="dayChange() > 0"
|
||||
[class.text-red-600]="dayChange() < 0">
|
||||
${{ dayChange().toLocaleString('en-US', {minimumFractionDigits: 2}) }}
|
||||
</p>
|
||||
<p class="text-sm"
|
||||
[class.text-green-600]="dayChangePercent() > 0"
|
||||
[class.text-red-600]="dayChangePercent() < 0">
|
||||
{{ dayChangePercent() > 0 ? '+' : '' }}{{ dayChangePercent().toFixed(2) }}%
|
||||
</p>
|
||||
</div>
|
||||
<mat-icon
|
||||
[class.text-green-600]="dayChange() > 0"
|
||||
[class.text-red-600]="dayChange() < 0">
|
||||
{{ dayChange() > 0 ? 'trending_up' : 'trending_down' }}
|
||||
</mat-icon>
|
||||
</div>
|
||||
</mat-card>
|
||||
|
||||
<!-- Active Strategies -->
|
||||
<mat-card class="p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-600">Active Strategies</p>
|
||||
<p class="text-2xl font-bold text-gray-900">3</p>
|
||||
</div>
|
||||
<mat-icon class="text-purple-600">psychology</mat-icon>
|
||||
</div>
|
||||
</mat-card>
|
||||
|
||||
<!-- Risk Level -->
|
||||
<mat-card class="p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-600">Risk Level</p>
|
||||
<p class="text-2xl font-bold text-green-600">Low</p>
|
||||
</div>
|
||||
<mat-icon class="text-green-600">security</mat-icon>
|
||||
</div>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
||||
<!-- Market Data Table -->
|
||||
<mat-card class="p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Market Watchlist</h3>
|
||||
<button mat-raised-button color="primary">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left font-medium text-gray-900">Symbol</th>
|
||||
<th class="text-right font-medium text-gray-900">Price</th>
|
||||
<th class="text-right font-medium text-gray-900">Change</th>
|
||||
<th class="text-right font-medium text-gray-900">Change %</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (stock of marketData(); track stock.symbol) {
|
||||
<tr>
|
||||
<td>
|
||||
<span class="font-semibold text-gray-900">{{ stock.symbol }}</span>
|
||||
</td>
|
||||
<td class="text-right font-medium text-gray-900">
|
||||
${{ stock.price.toFixed(2) }}
|
||||
</td>
|
||||
<td class="text-right font-medium"
|
||||
[class.text-green-600]="stock.change > 0"
|
||||
[class.text-red-600]="stock.change < 0">
|
||||
{{ stock.change > 0 ? '+' : '' }}${{ stock.change.toFixed(2) }}
|
||||
</td>
|
||||
<td class="text-right font-medium"
|
||||
[class.text-green-600]="stock.changePercent > 0"
|
||||
[class.text-red-600]="stock.changePercent < 0">
|
||||
{{ stock.changePercent > 0 ? '+' : '' }}{{ stock.changePercent.toFixed(2) }}%
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</mat-card>
|
||||
|
||||
<!-- Tabs for Additional Content -->
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Chart">
|
||||
<div class="p-6">
|
||||
<mat-card class="p-6 h-96 flex items-center">
|
||||
<div class="text-center text-gray-500 w-full">
|
||||
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">show_chart</mat-icon>
|
||||
<p class="mb-4">Chart visualization will be implemented here</p>
|
||||
</div>
|
||||
</mat-card>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab label="Orders">
|
||||
<div class="p-6">
|
||||
<mat-card class="p-6 h-96 flex items-center">
|
||||
<div class="text-center text-gray-500 w-full">
|
||||
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">receipt_long</mat-icon>
|
||||
<p class="mb-4">Order history and management will be implemented here</p>
|
||||
</div>
|
||||
</mat-card>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab label="Analytics">
|
||||
<div class="p-6">
|
||||
<mat-card class="p-6 h-96 flex items-center">
|
||||
<div class="text-center text-gray-500 w-full">
|
||||
<mat-icon style="font-size: 4rem; width: 4rem; height: 4rem;">analytics</mat-icon>
|
||||
<p class="mb-4">Advanced analytics and performance metrics will be implemented here</p>
|
||||
</div>
|
||||
</mat-card>
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
</mat-sidenav-content>
|
||||
</mat-sidenav-container>
|
||||
</div>
|
||||
|
||||
<router-outlet />
|
||||
|
||||
<style>
|
||||
:host {
|
||||
--bright-blue: oklch(51.01% 0.274 263.83);
|
||||
--electric-violet: oklch(53.18% 0.28 296.97);
|
||||
--french-violet: oklch(47.66% 0.246 305.88);
|
||||
--vivid-pink: oklch(69.02% 0.277 332.77);
|
||||
--hot-red: oklch(61.42% 0.238 15.34);
|
||||
--orange-red: oklch(63.32% 0.24 31.68);
|
||||
|
||||
--gray-900: oklch(19.37% 0.006 300.98);
|
||||
--gray-700: oklch(36.98% 0.014 302.71);
|
||||
--gray-400: oklch(70.9% 0.015 304.04);
|
||||
|
||||
--red-to-pink-to-purple-vertical-gradient: linear-gradient(
|
||||
180deg,
|
||||
var(--orange-red) 0%,
|
||||
var(--vivid-pink) 50%,
|
||||
var(--electric-violet) 100%
|
||||
);
|
||||
|
||||
--red-to-pink-to-purple-horizontal-gradient: linear-gradient(
|
||||
90deg,
|
||||
var(--orange-red) 0%,
|
||||
var(--vivid-pink) 50%,
|
||||
var(--electric-violet) 100%
|
||||
);
|
||||
|
||||
--pill-accent: var(--bright-blue);
|
||||
|
||||
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
|
||||
"Segoe UI Symbol";
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.125rem;
|
||||
color: var(--gray-900);
|
||||
font-weight: 500;
|
||||
line-height: 100%;
|
||||
letter-spacing: -0.125rem;
|
||||
margin: 0;
|
||||
font-family: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
|
||||
"Segoe UI Symbol";
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
main {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
box-sizing: inherit;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.angular-logo {
|
||||
max-width: 9.2rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.content h1 {
|
||||
margin-top: 1.75rem;
|
||||
}
|
||||
|
||||
.content p {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 1px;
|
||||
background: var(--red-to-pink-to-purple-vertical-gradient);
|
||||
margin-inline: 0.5rem;
|
||||
}
|
||||
|
||||
.pill-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.pill {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
--pill-accent: var(--bright-blue);
|
||||
background: color-mix(in srgb, var(--pill-accent) 5%, transparent);
|
||||
color: var(--pill-accent);
|
||||
padding-inline: 0.75rem;
|
||||
padding-block: 0.375rem;
|
||||
border-radius: 2.75rem;
|
||||
border: 0;
|
||||
transition: background 0.3s ease;
|
||||
font-family: var(--inter-font);
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.4rem;
|
||||
letter-spacing: -0.00875rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.pill:hover {
|
||||
background: color-mix(in srgb, var(--pill-accent) 15%, transparent);
|
||||
}
|
||||
|
||||
.pill-group .pill:nth-child(6n + 1) {
|
||||
--pill-accent: var(--bright-blue);
|
||||
}
|
||||
.pill-group .pill:nth-child(6n + 2) {
|
||||
--pill-accent: var(--french-violet);
|
||||
}
|
||||
.pill-group .pill:nth-child(6n + 3),
|
||||
.pill-group .pill:nth-child(6n + 4),
|
||||
.pill-group .pill:nth-child(6n + 5) {
|
||||
--pill-accent: var(--hot-red);
|
||||
}
|
||||
|
||||
.pill-group svg {
|
||||
margin-inline-start: 0.25rem;
|
||||
}
|
||||
|
||||
.social-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.73rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.social-links path {
|
||||
transition: fill 0.3s ease;
|
||||
fill: var(--gray-400);
|
||||
}
|
||||
|
||||
.social-links a:hover svg path {
|
||||
fill: var(--gray-900);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 650px) {
|
||||
.content {
|
||||
flex-direction: column;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background: var(--red-to-pink-to-purple-horizontal-gradient);
|
||||
margin-block: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<main class="main">
|
||||
<div class="content">
|
||||
<div class="left-side">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 982 239"
|
||||
fill="none"
|
||||
class="angular-logo"
|
||||
>
|
||||
<g clip-path="url(#a)">
|
||||
<path
|
||||
fill="url(#b)"
|
||||
d="M388.676 191.625h30.849L363.31 31.828h-35.758l-56.215 159.797h30.848l13.174-39.356h60.061l13.256 39.356Zm-65.461-62.675 21.602-64.311h1.227l21.602 64.311h-44.431Zm126.831-7.527v70.202h-28.23V71.839h27.002v20.374h1.392c2.782-6.71 7.2-12.028 13.255-15.956 6.056-3.927 13.584-5.89 22.503-5.89 8.264 0 15.465 1.8 21.684 5.318 6.137 3.518 10.964 8.673 14.319 15.382 3.437 6.71 5.074 14.81 4.992 24.383v76.175h-28.23v-71.92c0-8.019-2.046-14.237-6.219-18.819-4.173-4.5-9.819-6.791-17.102-6.791-4.91 0-9.328 1.063-13.174 3.272-3.846 2.128-6.792 5.237-9.001 9.328-2.046 4.009-3.191 8.918-3.191 14.728ZM589.233 239c-10.147 0-18.82-1.391-26.103-4.091-7.282-2.7-13.092-6.382-17.511-10.964-4.418-4.582-7.528-9.655-9.164-15.219l25.448-6.136c1.145 2.372 2.782 4.663 4.991 6.954 2.209 2.291 5.155 4.255 8.837 5.81 3.683 1.554 8.428 2.291 14.074 2.291 8.019 0 14.647-1.964 19.884-5.81 5.237-3.845 7.856-10.227 7.856-19.064v-22.665h-1.391c-1.473 2.946-3.601 5.892-6.383 9.001-2.782 3.109-6.464 5.645-10.965 7.691-4.582 2.046-10.228 3.109-17.101 3.109-9.165 0-17.511-2.209-25.039-6.545-7.446-4.337-13.42-10.883-17.757-19.474-4.418-8.673-6.628-19.473-6.628-32.565 0-13.091 2.21-24.301 6.628-33.383 4.419-9.082 10.311-15.955 17.839-20.7 7.528-4.746 15.874-7.037 25.039-7.037 7.037 0 12.846 1.145 17.347 3.518 4.582 2.373 8.182 5.236 10.883 8.51 2.7 3.272 4.746 6.382 6.137 9.327h1.554v-19.8h27.821v121.749c0 10.228-2.454 18.737-7.364 25.447-4.91 6.709-11.538 11.7-20.048 15.055-8.509 3.355-18.165 4.991-28.884 4.991Zm.245-71.266c5.974 0 11.047-1.473 15.302-4.337 4.173-2.945 7.446-7.118 9.573-12.519 2.21-5.482 3.274-12.027 3.274-19.637 0-7.609-1.064-14.155-3.274-19.8-2.127-5.646-5.318-10.064-9.491-13.255-4.174-3.11-9.329-4.746-15.384-4.746s-11.537 1.636-15.792 4.91c-4.173 3.272-7.365 7.772-9.492 13.418-2.128 5.727-3.191 12.191-3.191 19.392 0 7.2 1.063 13.745 3.273 19.228 2.127 5.482 5.318 9.736 9.573 12.764 4.174 3.027 9.41 4.582 15.629 4.582Zm141.56-26.51V71.839h28.23v119.786h-27.412v-21.273h-1.227c-2.7 6.709-7.119 12.191-13.338 16.446-6.137 4.255-13.747 6.382-22.748 6.382-7.855 0-14.81-1.718-20.783-5.237-5.974-3.518-10.72-8.591-14.075-15.382-3.355-6.709-5.073-14.891-5.073-24.464V71.839h28.312v71.921c0 7.609 2.046 13.664 6.219 18.083 4.173 4.5 9.655 6.709 16.365 6.709 4.173 0 8.183-.982 12.111-3.028 3.927-2.045 7.118-5.072 9.655-9.082 2.537-4.091 3.764-9.164 3.764-15.218Zm65.707-109.395v159.796h-28.23V31.828h28.23Zm44.841 162.169c-7.61 0-14.402-1.391-20.457-4.091-6.055-2.7-10.883-6.791-14.32-12.109-3.518-5.319-5.237-11.946-5.237-19.801 0-6.791 1.228-12.355 3.765-16.773 2.536-4.419 5.891-7.937 10.228-10.637 4.337-2.618 9.164-4.664 14.647-6.055 5.4-1.391 11.046-2.373 16.856-3.027 7.037-.737 12.683-1.391 17.102-1.964 4.337-.573 7.528-1.555 9.574-2.782 1.963-1.309 3.027-3.273 3.027-5.973v-.491c0-5.891-1.718-10.391-5.237-13.664-3.518-3.191-8.51-4.828-15.056-4.828-6.955 0-12.356 1.473-16.447 4.5-4.009 3.028-6.71 6.546-8.183 10.719l-26.348-3.764c2.046-7.282 5.483-13.336 10.31-18.328 4.746-4.909 10.638-8.59 17.511-11.045 6.955-2.455 14.565-3.682 22.912-3.682 5.809 0 11.537.654 17.265 2.045s10.965 3.6 15.711 6.71c4.746 3.109 8.51 7.282 11.455 12.6 2.864 5.318 4.337 11.946 4.337 19.883v80.184h-27.166v-16.446h-.9c-1.719 3.355-4.092 6.464-7.201 9.328-3.109 2.864-6.955 5.237-11.619 6.955-4.828 1.718-10.229 2.536-16.529 2.536Zm7.364-20.701c5.646 0 10.556-1.145 14.729-3.354 4.173-2.291 7.364-5.237 9.655-9.001 2.292-3.763 3.355-7.854 3.355-12.273v-14.155c-.9.737-2.373 1.391-4.5 2.046-2.128.654-4.419 1.145-7.037 1.636-2.619.491-5.155.9-7.692 1.227-2.537.328-4.746.655-6.628.901-4.173.572-8.019 1.472-11.292 2.781-3.355 1.31-5.973 3.11-7.855 5.401-1.964 2.291-2.864 5.318-2.864 8.918 0 5.237 1.882 9.164 5.728 11.782 3.682 2.782 8.51 4.091 14.401 4.091Zm64.643 18.328V71.839h27.412v19.965h1.227c2.21-6.955 5.974-12.274 11.292-16.038 5.319-3.763 11.456-5.645 18.329-5.645 1.555 0 3.355.082 5.237.163 1.964.164 3.601.328 4.91.573v25.938c-1.227-.41-3.109-.819-5.646-1.146a58.814 58.814 0 0 0-7.446-.49c-5.155 0-9.738 1.145-13.829 3.354-4.091 2.209-7.282 5.236-9.655 9.164-2.373 3.927-3.519 8.427-3.519 13.5v70.448h-28.312ZM222.077 39.192l-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
|
||||
/>
|
||||
<path
|
||||
fill="url(#c)"
|
||||
d="M388.676 191.625h30.849L363.31 31.828h-35.758l-56.215 159.797h30.848l13.174-39.356h60.061l13.256 39.356Zm-65.461-62.675 21.602-64.311h1.227l21.602 64.311h-44.431Zm126.831-7.527v70.202h-28.23V71.839h27.002v20.374h1.392c2.782-6.71 7.2-12.028 13.255-15.956 6.056-3.927 13.584-5.89 22.503-5.89 8.264 0 15.465 1.8 21.684 5.318 6.137 3.518 10.964 8.673 14.319 15.382 3.437 6.71 5.074 14.81 4.992 24.383v76.175h-28.23v-71.92c0-8.019-2.046-14.237-6.219-18.819-4.173-4.5-9.819-6.791-17.102-6.791-4.91 0-9.328 1.063-13.174 3.272-3.846 2.128-6.792 5.237-9.001 9.328-2.046 4.009-3.191 8.918-3.191 14.728ZM589.233 239c-10.147 0-18.82-1.391-26.103-4.091-7.282-2.7-13.092-6.382-17.511-10.964-4.418-4.582-7.528-9.655-9.164-15.219l25.448-6.136c1.145 2.372 2.782 4.663 4.991 6.954 2.209 2.291 5.155 4.255 8.837 5.81 3.683 1.554 8.428 2.291 14.074 2.291 8.019 0 14.647-1.964 19.884-5.81 5.237-3.845 7.856-10.227 7.856-19.064v-22.665h-1.391c-1.473 2.946-3.601 5.892-6.383 9.001-2.782 3.109-6.464 5.645-10.965 7.691-4.582 2.046-10.228 3.109-17.101 3.109-9.165 0-17.511-2.209-25.039-6.545-7.446-4.337-13.42-10.883-17.757-19.474-4.418-8.673-6.628-19.473-6.628-32.565 0-13.091 2.21-24.301 6.628-33.383 4.419-9.082 10.311-15.955 17.839-20.7 7.528-4.746 15.874-7.037 25.039-7.037 7.037 0 12.846 1.145 17.347 3.518 4.582 2.373 8.182 5.236 10.883 8.51 2.7 3.272 4.746 6.382 6.137 9.327h1.554v-19.8h27.821v121.749c0 10.228-2.454 18.737-7.364 25.447-4.91 6.709-11.538 11.7-20.048 15.055-8.509 3.355-18.165 4.991-28.884 4.991Zm.245-71.266c5.974 0 11.047-1.473 15.302-4.337 4.173-2.945 7.446-7.118 9.573-12.519 2.21-5.482 3.274-12.027 3.274-19.637 0-7.609-1.064-14.155-3.274-19.8-2.127-5.646-5.318-10.064-9.491-13.255-4.174-3.11-9.329-4.746-15.384-4.746s-11.537 1.636-15.792 4.91c-4.173 3.272-7.365 7.772-9.492 13.418-2.128 5.727-3.191 12.191-3.191 19.392 0 7.2 1.063 13.745 3.273 19.228 2.127 5.482 5.318 9.736 9.573 12.764 4.174 3.027 9.41 4.582 15.629 4.582Zm141.56-26.51V71.839h28.23v119.786h-27.412v-21.273h-1.227c-2.7 6.709-7.119 12.191-13.338 16.446-6.137 4.255-13.747 6.382-22.748 6.382-7.855 0-14.81-1.718-20.783-5.237-5.974-3.518-10.72-8.591-14.075-15.382-3.355-6.709-5.073-14.891-5.073-24.464V71.839h28.312v71.921c0 7.609 2.046 13.664 6.219 18.083 4.173 4.5 9.655 6.709 16.365 6.709 4.173 0 8.183-.982 12.111-3.028 3.927-2.045 7.118-5.072 9.655-9.082 2.537-4.091 3.764-9.164 3.764-15.218Zm65.707-109.395v159.796h-28.23V31.828h28.23Zm44.841 162.169c-7.61 0-14.402-1.391-20.457-4.091-6.055-2.7-10.883-6.791-14.32-12.109-3.518-5.319-5.237-11.946-5.237-19.801 0-6.791 1.228-12.355 3.765-16.773 2.536-4.419 5.891-7.937 10.228-10.637 4.337-2.618 9.164-4.664 14.647-6.055 5.4-1.391 11.046-2.373 16.856-3.027 7.037-.737 12.683-1.391 17.102-1.964 4.337-.573 7.528-1.555 9.574-2.782 1.963-1.309 3.027-3.273 3.027-5.973v-.491c0-5.891-1.718-10.391-5.237-13.664-3.518-3.191-8.51-4.828-15.056-4.828-6.955 0-12.356 1.473-16.447 4.5-4.009 3.028-6.71 6.546-8.183 10.719l-26.348-3.764c2.046-7.282 5.483-13.336 10.31-18.328 4.746-4.909 10.638-8.59 17.511-11.045 6.955-2.455 14.565-3.682 22.912-3.682 5.809 0 11.537.654 17.265 2.045s10.965 3.6 15.711 6.71c4.746 3.109 8.51 7.282 11.455 12.6 2.864 5.318 4.337 11.946 4.337 19.883v80.184h-27.166v-16.446h-.9c-1.719 3.355-4.092 6.464-7.201 9.328-3.109 2.864-6.955 5.237-11.619 6.955-4.828 1.718-10.229 2.536-16.529 2.536Zm7.364-20.701c5.646 0 10.556-1.145 14.729-3.354 4.173-2.291 7.364-5.237 9.655-9.001 2.292-3.763 3.355-7.854 3.355-12.273v-14.155c-.9.737-2.373 1.391-4.5 2.046-2.128.654-4.419 1.145-7.037 1.636-2.619.491-5.155.9-7.692 1.227-2.537.328-4.746.655-6.628.901-4.173.572-8.019 1.472-11.292 2.781-3.355 1.31-5.973 3.11-7.855 5.401-1.964 2.291-2.864 5.318-2.864 8.918 0 5.237 1.882 9.164 5.728 11.782 3.682 2.782 8.51 4.091 14.401 4.091Zm64.643 18.328V71.839h27.412v19.965h1.227c2.21-6.955 5.974-12.274 11.292-16.038 5.319-3.763 11.456-5.645 18.329-5.645 1.555 0 3.355.082 5.237.163 1.964.164 3.601.328 4.91.573v25.938c-1.227-.41-3.109-.819-5.646-1.146a58.814 58.814 0 0 0-7.446-.49c-5.155 0-9.738 1.145-13.829 3.354-4.091 2.209-7.282 5.236-9.655 9.164-2.373 3.927-3.519 8.427-3.519 13.5v70.448h-28.312ZM222.077 39.192l-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<radialGradient
|
||||
id="c"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientTransform="rotate(118.122 171.182 60.81) scale(205.794)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#FF41F8" />
|
||||
<stop offset=".707" stop-color="#FF41F8" stop-opacity=".5" />
|
||||
<stop offset="1" stop-color="#FF41F8" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="b"
|
||||
x1="0"
|
||||
x2="982"
|
||||
y1="192"
|
||||
y2="192"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#F0060B" />
|
||||
<stop offset="0" stop-color="#F0070C" />
|
||||
<stop offset=".526" stop-color="#CC26D5" />
|
||||
<stop offset="1" stop-color="#7702FF" />
|
||||
</linearGradient>
|
||||
<clipPath id="a"><path fill="#fff" d="M0 0h982v239H0z" /></clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<h1>Hello, {{ title }}</h1>
|
||||
<p>Congratulations! Your app is running. 🎉</p>
|
||||
</div>
|
||||
<div class="divider" role="separator" aria-label="Divider"></div>
|
||||
<div class="right-side">
|
||||
<div class="pill-group">
|
||||
@for (item of [
|
||||
{ title: 'Explore the Docs', link: 'https://angular.dev' },
|
||||
{ title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' },
|
||||
{ title: 'CLI Docs', link: 'https://angular.dev/tools/cli' },
|
||||
{ title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' },
|
||||
{ title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' },
|
||||
]; track item.title) {
|
||||
<a
|
||||
class="pill"
|
||||
[href]="item.link"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<span>{{ item.title }}</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="14"
|
||||
viewBox="0 -960 960 960"
|
||||
width="14"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="social-links">
|
||||
<a
|
||||
href="https://github.com/angular/angular"
|
||||
aria-label="Github"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<svg
|
||||
width="25"
|
||||
height="24"
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
alt="Github"
|
||||
>
|
||||
<path
|
||||
d="M12.3047 0C5.50634 0 0 5.50942 0 12.3047C0 17.7423 3.52529 22.3535 8.41332 23.9787C9.02856 24.0946 9.25414 23.7142 9.25414 23.3871C9.25414 23.0949 9.24389 22.3207 9.23876 21.2953C5.81601 22.0377 5.09414 19.6444 5.09414 19.6444C4.53427 18.2243 3.72524 17.8449 3.72524 17.8449C2.61064 17.082 3.81137 17.0973 3.81137 17.0973C5.04697 17.1835 5.69604 18.3647 5.69604 18.3647C6.79321 20.2463 8.57636 19.7029 9.27978 19.3881C9.39052 18.5924 9.70736 18.0499 10.0591 17.7423C7.32641 17.4347 4.45429 16.3765 4.45429 11.6618C4.45429 10.3185 4.9311 9.22133 5.72065 8.36C5.58222 8.04931 5.16694 6.79833 5.82831 5.10337C5.82831 5.10337 6.85883 4.77319 9.2121 6.36459C10.1965 6.09082 11.2424 5.95546 12.2883 5.94931C13.3342 5.95546 14.3801 6.09082 15.3644 6.36459C17.7023 4.77319 18.7328 5.10337 18.7328 5.10337C19.3942 6.79833 18.9789 8.04931 18.8559 8.36C19.6403 9.22133 20.1171 10.3185 20.1171 11.6618C20.1171 16.3888 17.2409 17.4296 14.5031 17.7321C14.9338 18.1012 15.3337 18.8559 15.3337 20.0084C15.3337 21.6552 15.3183 22.978 15.3183 23.3779C15.3183 23.7009 15.5336 24.0854 16.1642 23.9623C21.0871 22.3484 24.6094 17.7341 24.6094 12.3047C24.6094 5.50942 19.0999 0 12.3047 0Z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/angular"
|
||||
aria-label="Twitter"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
alt="Twitter"
|
||||
>
|
||||
<path
|
||||
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.youtube.com/channel/UCbn1OgGei-DV7aSRo_HaAiw"
|
||||
aria-label="Youtube"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<svg
|
||||
width="29"
|
||||
height="20"
|
||||
viewBox="0 0 29 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
alt="Youtube"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M27.4896 1.52422C27.9301 1.96749 28.2463 2.51866 28.4068 3.12258C29.0004 5.35161 29.0004 10 29.0004 10C29.0004 10 29.0004 14.6484 28.4068 16.8774C28.2463 17.4813 27.9301 18.0325 27.4896 18.4758C27.0492 18.9191 26.5 19.2389 25.8972 19.4032C23.6778 20 14.8068 20 14.8068 20C14.8068 20 5.93586 20 3.71651 19.4032C3.11363 19.2389 2.56449 18.9191 2.12405 18.4758C1.68361 18.0325 1.36732 17.4813 1.20683 16.8774C0.613281 14.6484 0.613281 10 0.613281 10C0.613281 10 0.613281 5.35161 1.20683 3.12258C1.36732 2.51866 1.68361 1.96749 2.12405 1.52422C2.56449 1.08095 3.11363 0.76113 3.71651 0.596774C5.93586 0 14.8068 0 14.8068 0C14.8068 0 23.6778 0 25.8972 0.596774C26.5 0.76113 27.0492 1.08095 27.4896 1.52422ZM19.3229 10L11.9036 5.77905V14.221L19.3229 10Z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * The content above * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
|
||||
|
||||
<router-outlet />
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import { Routes } from '@angular/router';
|
||||
|
||||
export const routes: Routes = [];
|
||||
87
apps/interface-services/trading-dashboard/src/app/app.scss
Normal file
87
apps/interface-services/trading-dashboard/src/app/app.scss
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// Custom styles for the trading dashboard
|
||||
.mat-sidenav-container {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.mat-sidenav {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.mat-toolbar {
|
||||
background-color: white;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.dark .mat-toolbar {
|
||||
background-color: #1f2937;
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
// Custom button styles
|
||||
.mat-mdc-button {
|
||||
&.w-full {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
// Card styles
|
||||
.mat-mdc-card {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
// Tab styles
|
||||
.mat-mdc-tab-group {
|
||||
.mat-mdc-tab-header {
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
}
|
||||
|
||||
.dark .mat-mdc-tab-group {
|
||||
.mat-mdc-tab-header {
|
||||
border-bottom: 1px solid #374151;
|
||||
}
|
||||
}
|
||||
|
||||
// Chip styles
|
||||
.mat-mdc-chip {
|
||||
&.bg-green-100 {
|
||||
background-color: #dcfce7 !important;
|
||||
}
|
||||
|
||||
&.text-green-800 {
|
||||
color: #166534 !important;
|
||||
}
|
||||
|
||||
&.bg-blue-100 {
|
||||
background-color: #dbeafe !important;
|
||||
}
|
||||
|
||||
&.text-blue-800 {
|
||||
color: #1e40af !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Table styles
|
||||
table {
|
||||
tr:hover {
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
// Icon styles
|
||||
.mat-icon {
|
||||
&.text-3xl {
|
||||
font-size: 2rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
&.text-6xl {
|
||||
font-size: 4rem;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { provideZonelessChangeDetection } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { App } from './app';
|
||||
|
||||
describe('App', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [App],
|
||||
providers: [provideZonelessChangeDetection()]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(App);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, trading-dashboard');
|
||||
});
|
||||
});
|
||||
47
apps/interface-services/trading-dashboard/src/app/app.ts
Normal file
47
apps/interface-services/trading-dashboard/src/app/app.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { Component, signal } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatChipsModule } from '@angular/material/chips';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [
|
||||
RouterOutlet,
|
||||
CommonModule,
|
||||
MatSidenavModule,
|
||||
MatToolbarModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatCardModule,
|
||||
MatTabsModule,
|
||||
MatChipsModule
|
||||
],
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.scss'
|
||||
})
|
||||
export class App {
|
||||
protected title = 'Trading Dashboard';
|
||||
protected sidenavOpened = signal(true);
|
||||
|
||||
// Mock data for the dashboard
|
||||
protected marketData = signal([
|
||||
{ symbol: 'AAPL', price: 192.53, change: 2.41, changePercent: 1.27 },
|
||||
{ symbol: 'GOOGL', price: 138.21, change: -1.82, changePercent: -1.30 },
|
||||
{ symbol: 'MSFT', price: 378.85, change: 4.12, changePercent: 1.10 },
|
||||
{ symbol: 'TSLA', price: 248.42, change: -3.21, changePercent: -1.28 },
|
||||
]);
|
||||
|
||||
protected portfolioValue = signal(125420.50);
|
||||
protected dayChange = signal(2341.20);
|
||||
protected dayChangePercent = signal(1.90);
|
||||
|
||||
toggleSidenav() {
|
||||
this.sidenavOpened.set(!this.sidenavOpened());
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 4 KiB |
|
|
@ -1,455 +0,0 @@
|
|||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Card,
|
||||
Title,
|
||||
Text,
|
||||
Metric,
|
||||
Flex,
|
||||
Badge,
|
||||
Grid,
|
||||
AreaChart,
|
||||
DonutChart,
|
||||
BarChart,
|
||||
LineChart,
|
||||
Tab,
|
||||
TabGroup,
|
||||
TabList,
|
||||
TabPanel,
|
||||
TabPanels,
|
||||
Button,
|
||||
} from '@tremor/react';
|
||||
import type { MarketData, OHLCV, HealthStatus } from '@stock-bot/shared-types';
|
||||
|
||||
const API_BASE = 'http://localhost:3001';
|
||||
|
||||
interface DashboardData {
|
||||
marketData: MarketData[];
|
||||
ohlcvData: OHLCV[];
|
||||
serviceHealth: HealthStatus | null;
|
||||
lastUpdate: Date | null;
|
||||
}
|
||||
|
||||
export function TradingDashboard() {
|
||||
const [data, setData] = useState<DashboardData>({
|
||||
marketData: [],
|
||||
ohlcvData: [],
|
||||
serviceHealth: null,
|
||||
lastUpdate: null,
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [wsConnected, setWsConnected] = useState(false);
|
||||
|
||||
const symbols = useMemo(() => ['AAPL', 'GOOGL', 'MSFT', 'TSLA', 'AMZN'], []);
|
||||
|
||||
// Memoized fetch functions
|
||||
const fetchServiceHealth = useCallback(async () => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/health`);
|
||||
const health = await response.json();
|
||||
return health;
|
||||
} catch (error) {
|
||||
console.error('Error fetching service health:', error);
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchMarketData = useCallback(async () => {
|
||||
try {
|
||||
const promises = symbols.map(async (symbol) => {
|
||||
const response = await fetch(`${API_BASE}/api/market-data/${symbol}`);
|
||||
const result = await response.json();
|
||||
return result.success ? result.data : null;
|
||||
});
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
return results.filter(Boolean);
|
||||
} catch (error) {
|
||||
console.error('Error fetching market data:', error);
|
||||
return [];
|
||||
}
|
||||
}, [symbols]);
|
||||
|
||||
const fetchOHLCVData = useCallback(async (symbol: string = 'AAPL') => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/ohlcv/${symbol}?limit=50`);
|
||||
const result = await response.json();
|
||||
return result.success ? result.data : [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching OHLCV data:', error);
|
||||
return [];
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Load all data function
|
||||
const loadData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const [health, marketData, ohlcvData] = await Promise.all([
|
||||
fetchServiceHealth(),
|
||||
fetchMarketData(),
|
||||
fetchOHLCVData(),
|
||||
]);
|
||||
|
||||
setData({
|
||||
serviceHealth: health,
|
||||
marketData,
|
||||
ohlcvData,
|
||||
lastUpdate: new Date(),
|
||||
});
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [fetchServiceHealth, fetchMarketData, fetchOHLCVData]);
|
||||
|
||||
// WebSocket connection and data loading
|
||||
useEffect(() => {
|
||||
let ws: WebSocket | null = null;
|
||||
|
||||
const connectWebSocket = () => {
|
||||
try {
|
||||
ws = new WebSocket('ws://localhost:3001');
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log('WebSocket connected');
|
||||
setWsConnected(true);
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const message = JSON.parse(event.data);
|
||||
if (message.type === 'market-data' && message.data) {
|
||||
// Update specific symbol data
|
||||
setData(prev => ({
|
||||
...prev,
|
||||
marketData: prev.marketData.map(item =>
|
||||
item.symbol === message.data.symbol ? message.data : item
|
||||
),
|
||||
lastUpdate: new Date(),
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing WebSocket message:', error);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log('WebSocket disconnected');
|
||||
setWsConnected(false);
|
||||
// Reconnect after 5 seconds
|
||||
setTimeout(connectWebSocket, 5000);
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
setWsConnected(false);
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to connect WebSocket:', error);
|
||||
setWsConnected(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Initial data load
|
||||
loadData();
|
||||
|
||||
// Set up periodic refresh
|
||||
const interval = setInterval(loadData, 30000); // Refresh every 30 seconds
|
||||
|
||||
// Attempt WebSocket connection
|
||||
connectWebSocket();
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
};
|
||||
}, [loadData]);
|
||||
|
||||
// Prepare chart data
|
||||
const chartData = data.ohlcvData.map((item) => ({
|
||||
time: new Date(item.timestamp).toLocaleTimeString(),
|
||||
price: item.close,
|
||||
volume: item.volume,
|
||||
}));
|
||||
|
||||
// Prepare portfolio allocation data (demo)
|
||||
const portfolioData = data.marketData.map((item, index) => ({
|
||||
name: item.symbol,
|
||||
value: (index + 1) * 20000, // Demo allocation
|
||||
}));
|
||||
|
||||
// Calculate total portfolio value
|
||||
const totalValue = portfolioData.reduce((sum, item) => sum + item.value, 0);
|
||||
|
||||
// Performance metrics (demo)
|
||||
const performanceData = [
|
||||
{ date: 'Mon', value: 98000 },
|
||||
{ date: 'Tue', value: 102000 },
|
||||
{ date: 'Wed', value: 105000 },
|
||||
{ date: 'Thu', value: 103000 },
|
||||
{ date: 'Fri', value: 108000 },
|
||||
];
|
||||
|
||||
if (loading && data.marketData.length === 0) {
|
||||
return (
|
||||
<div className="p-8 bg-slate-50 min-h-screen">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="text-center">
|
||||
<Title>Loading Trading Dashboard...</Title>
|
||||
<Text className="mt-4">Connecting to market data services</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-8 bg-slate-50 min-h-screen">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<Flex justifyContent="between" alignItems="center">
|
||||
<div>
|
||||
<Title className="text-3xl font-bold">🤖 Stock Bot Dashboard</Title>
|
||||
<Text className="mt-2">Real-time market data monitoring</Text>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<Button onClick={loadData} disabled={loading}>
|
||||
{loading ? 'Refreshing...' : 'Refresh Data'}
|
||||
</Button>
|
||||
{data.lastUpdate && (
|
||||
<Text className="mt-2">
|
||||
Last update: {data.lastUpdate.toLocaleTimeString()}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<Card className="mb-6">
|
||||
<Text color="red">Error: {error}</Text>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Top Metrics */}
|
||||
<Grid numItems={1} numItemsSm={2} numItemsLg={4} className="gap-6 mb-8">
|
||||
<Card>
|
||||
<Text>Portfolio Value</Text>
|
||||
<Metric>${totalValue.toLocaleString()}</Metric>
|
||||
<Flex className="mt-4">
|
||||
<Badge color="emerald">+8.2%</Badge>
|
||||
</Flex>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Text>Service Status</Text>
|
||||
<Flex alignItems="center" className="mt-2">
|
||||
<Badge
|
||||
color={data.serviceHealth?.status === 'healthy' ? 'emerald' : 'red'}
|
||||
>
|
||||
{data.serviceHealth?.status || 'Unknown'}
|
||||
</Badge>
|
||||
</Flex>
|
||||
<Flex alignItems="center" className="mt-2">
|
||||
<Text className="text-sm mr-2">WebSocket:</Text>
|
||||
<Badge
|
||||
color={wsConnected ? 'emerald' : 'red'}
|
||||
size="sm"
|
||||
>
|
||||
{wsConnected ? 'Connected' : 'Disconnected'}
|
||||
</Badge>
|
||||
</Flex>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Text>Active Symbols</Text>
|
||||
<Metric>{data.marketData.length}</Metric>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Text>Daily P&L</Text>
|
||||
<Metric color="emerald">+$2,450</Metric>
|
||||
<Flex className="mt-4">
|
||||
<Badge color="emerald">+2.3%</Badge>
|
||||
</Flex>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{/* Main Content Tabs */}
|
||||
<TabGroup>
|
||||
<TabList className="mb-8">
|
||||
<Tab>Market Data</Tab>
|
||||
<Tab>Portfolio</Tab>
|
||||
<Tab>Charts</Tab>
|
||||
<Tab>Performance</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
{/* Market Data Tab */}
|
||||
<TabPanel>
|
||||
<Grid numItems={1} numItemsLg={2} className="gap-6">
|
||||
<Card>
|
||||
<Title>Live Prices</Title>
|
||||
<div className="mt-6">
|
||||
{data.marketData.map((item) => (
|
||||
<div
|
||||
key={item.symbol}
|
||||
className="flex justify-between items-center py-3 border-b border-gray-200 last:border-b-0"
|
||||
>
|
||||
<div>
|
||||
<Text className="font-semibold">{item.symbol}</Text>
|
||||
<Text className="text-sm text-gray-500">
|
||||
Vol: {item.volume.toLocaleString()}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<Text className="font-bold text-lg">
|
||||
${item.price.toFixed(2)}
|
||||
</Text>
|
||||
<Text className="text-sm text-gray-500">
|
||||
Bid: ${item.bid.toFixed(2)} | Ask: ${item.ask.toFixed(2)}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Title>Market Overview</Title>
|
||||
<BarChart
|
||||
className="mt-6"
|
||||
data={data.marketData}
|
||||
index="symbol"
|
||||
categories={["price"]}
|
||||
colors={["blue"]}
|
||||
valueFormatter={(value) => `$${value.toFixed(2)}`}
|
||||
/>
|
||||
</Card>
|
||||
</Grid>
|
||||
</TabPanel>
|
||||
|
||||
{/* Portfolio Tab */}
|
||||
<TabPanel>
|
||||
<Grid numItems={1} numItemsLg={2} className="gap-6">
|
||||
<Card>
|
||||
<Title>Portfolio Allocation</Title>
|
||||
<DonutChart
|
||||
className="mt-6"
|
||||
data={portfolioData}
|
||||
category="value"
|
||||
index="name"
|
||||
valueFormatter={(value) => `$${value.toLocaleString()}`}
|
||||
colors={["slate", "violet", "indigo", "rose", "cyan"]}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Title>Holdings</Title>
|
||||
<div className="mt-6">
|
||||
{portfolioData.map((item) => (
|
||||
<div
|
||||
key={item.name}
|
||||
className="flex justify-between items-center py-3 border-b border-gray-200 last:border-b-0"
|
||||
>
|
||||
<Text className="font-semibold">{item.name}</Text>
|
||||
<div className="text-right">
|
||||
<Text className="font-bold">
|
||||
${item.value.toLocaleString()}
|
||||
</Text>
|
||||
<Text className="text-sm text-gray-500">
|
||||
{((item.value / totalValue) * 100).toFixed(1)}%
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</Grid>
|
||||
</TabPanel>
|
||||
|
||||
{/* Charts Tab */}
|
||||
<TabPanel>
|
||||
<Grid numItems={1} className="gap-6">
|
||||
<Card>
|
||||
<Title>AAPL Price Chart</Title>
|
||||
<LineChart
|
||||
className="mt-6"
|
||||
data={chartData}
|
||||
index="time"
|
||||
categories={["price"]}
|
||||
colors={["indigo"]}
|
||||
valueFormatter={(value) => `$${value.toFixed(2)}`}
|
||||
yAxisWidth={60}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Title>Volume Analysis</Title>
|
||||
<AreaChart
|
||||
className="mt-6"
|
||||
data={chartData}
|
||||
index="time"
|
||||
categories={["volume"]}
|
||||
colors={["emerald"]}
|
||||
valueFormatter={(value) => value.toLocaleString()}
|
||||
yAxisWidth={80}
|
||||
/>
|
||||
</Card>
|
||||
</Grid>
|
||||
</TabPanel>
|
||||
|
||||
{/* Performance Tab */}
|
||||
<TabPanel>
|
||||
<Grid numItems={1} numItemsLg={2} className="gap-6">
|
||||
<Card>
|
||||
<Title>Weekly Performance</Title>
|
||||
<AreaChart
|
||||
className="mt-6"
|
||||
data={performanceData}
|
||||
index="date"
|
||||
categories={["value"]}
|
||||
colors={["emerald"]}
|
||||
valueFormatter={(value) => `$${value.toLocaleString()}`}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Title>Performance Metrics</Title>
|
||||
<div className="mt-6 space-y-4">
|
||||
<div className="flex justify-between">
|
||||
<Text>Total Return</Text>
|
||||
<Badge color="emerald">+12.5%</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Text>Sharpe Ratio</Text>
|
||||
<Badge color="blue">1.8</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Text>Max Drawdown</Text>
|
||||
<Badge color="red">-5.2%</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Text>Win Rate</Text>
|
||||
<Badge color="emerald">68%</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Text>Avg Trade</Text>
|
||||
<Badge color="indigo">+$245</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Grid>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
/* Reset and base styles for Tremor UI */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light;
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
#root {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Tremor component overrides */
|
||||
.tremor-Card-root {
|
||||
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
.tremor-Button-root {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.tremor-Button-root:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
16
apps/interface-services/trading-dashboard/src/index.html
Normal file
16
apps/interface-services/trading-dashboard/src/index.html
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Trading Dashboard</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
</head>
|
||||
<body class="mat-typography">
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
6
apps/interface-services/trading-dashboard/src/main.ts
Normal file
6
apps/interface-services/trading-dashboard/src/main.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { appConfig } from './app/app.config';
|
||||
import { App } from './app/app';
|
||||
|
||||
bootstrapApplication(App, appConfig)
|
||||
.catch((err) => console.error(err));
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
280
apps/interface-services/trading-dashboard/src/styles.scss
Normal file
280
apps/interface-services/trading-dashboard/src/styles.scss
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
/* You can add global styles to this file, and also import other style files */
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
@import "tailwindcss";
|
||||
|
||||
// Define a custom theme
|
||||
$primary-palette: mat.m2-define-palette(mat.$m2-blue-palette);
|
||||
$accent-palette: mat.m2-define-palette(mat.$m2-green-palette);
|
||||
$warn-palette: mat.m2-define-palette(mat.$m2-red-palette);
|
||||
|
||||
$theme: mat.m2-define-light-theme((
|
||||
color: (
|
||||
primary: $primary-palette,
|
||||
accent: $accent-palette,
|
||||
warn: $warn-palette,
|
||||
),
|
||||
typography: mat.m2-define-typography-config(),
|
||||
density: 0
|
||||
));
|
||||
|
||||
@include mat.all-component-themes($theme);
|
||||
|
||||
/* Custom global styles for trading dashboard */
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* Layout utilities */
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.space-x-4 > * + * {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.space-y-6 > * + * {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.grid-cols-1 {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
|
||||
.grid-cols-2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.grid-cols-4 {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.gap-6 {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.p-6 {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mr-3 {
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
.mr-4 {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.mt-1 {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.w-64 {
|
||||
width: 16rem;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.min-h-screen {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.h-96 {
|
||||
height: 24rem;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.text-2xl {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.font-semibold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.font-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Color utilities */
|
||||
.text-blue-600 {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.text-green-600 {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.text-red-600 {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.text-purple-600 {
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
.text-gray-900 {
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.text-gray-600 {
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.text-gray-500 {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.bg-gray-50 {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-green-100 {
|
||||
background-color: #dcfce7;
|
||||
}
|
||||
|
||||
.text-green-800 {
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.bg-blue-100 {
|
||||
background-color: #dbeafe;
|
||||
}
|
||||
|
||||
.text-blue-800 {
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.bg-blue-50 {
|
||||
background-color: #eff6ff;
|
||||
}
|
||||
|
||||
.text-blue-700 {
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
/* Border utilities */
|
||||
.border-b {
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.border-r {
|
||||
border-right: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.border-gray-200 {
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.border-gray-100 {
|
||||
border-color: #f3f4f6;
|
||||
}
|
||||
|
||||
/* Shadow utilities */
|
||||
.shadow-sm {
|
||||
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
}
|
||||
|
||||
/* Hover utilities */
|
||||
.hover\:bg-gray-100:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
/* Responsive grid */
|
||||
@media (min-width: 768px) {
|
||||
.md\:grid-cols-2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:grid-cols-4 {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Table styles */
|
||||
.overflow-x-auto {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table th,
|
||||
table td {
|
||||
padding: 0.75rem 1rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
table th {
|
||||
font-weight: 500;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
table td {
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
}
|
||||
|
||||
table tr:hover {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
/// <reference types="vite/client" />
|
||||
52
apps/interface-services/trading-dashboard/tailwind.config.js
Normal file
52
apps/interface-services/trading-dashboard/tailwind.config.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{html,ts}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
50: '#eff6ff',
|
||||
100: '#dbeafe',
|
||||
200: '#bfdbfe',
|
||||
300: '#93c5fd',
|
||||
400: '#60a5fa',
|
||||
500: '#3b82f6',
|
||||
600: '#2563eb',
|
||||
700: '#1d4ed8',
|
||||
800: '#1e40af',
|
||||
900: '#1e3a8a',
|
||||
950: '#172554',
|
||||
},
|
||||
success: {
|
||||
50: '#f0fdf4',
|
||||
100: '#dcfce7',
|
||||
200: '#bbf7d0',
|
||||
300: '#86efac',
|
||||
400: '#4ade80',
|
||||
500: '#22c55e',
|
||||
600: '#16a34a',
|
||||
700: '#15803d',
|
||||
800: '#166534',
|
||||
900: '#14532d',
|
||||
950: '#052e16',
|
||||
},
|
||||
danger: {
|
||||
50: '#fef2f2',
|
||||
100: '#fee2e2',
|
||||
200: '#fecaca',
|
||||
300: '#fca5a5',
|
||||
400: '#f87171',
|
||||
500: '#ef4444',
|
||||
600: '#dc2626',
|
||||
700: '#b91c1c',
|
||||
800: '#991b1b',
|
||||
900: '#7f1d1d',
|
||||
950: '#450a0a',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
|
@ -1,27 +1,15 @@
|
|||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"include": ["src"]
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,34 @@
|
|||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"skipLibCheck": true,
|
||||
"isolatedModules": true,
|
||||
"experimentalDecorators": true,
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "preserve"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"typeCheckHostBindings": true,
|
||||
"strictTemplates": true
|
||||
},
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
14
apps/interface-services/trading-dashboard/tsconfig.spec.json
Normal file
14
apps/interface-services/trading-dashboard/tsconfig.spec.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
234
database/mongodb/init/01-init-collections.js
Normal file
234
database/mongodb/init/01-init-collections.js
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
// MongoDB Initialization Script for Trading Bot
|
||||
// This script creates collections and indexes for sentiment and document storage
|
||||
|
||||
// Switch to the trading_documents database
|
||||
db = db.getSiblingDB('trading_documents');
|
||||
|
||||
// Create collections with validation schemas
|
||||
|
||||
// Sentiment Analysis Collection
|
||||
db.createCollection('sentiment_analysis', {
|
||||
validator: {
|
||||
$jsonSchema: {
|
||||
bsonType: 'object',
|
||||
required: ['symbol', 'source', 'timestamp', 'sentiment_score'],
|
||||
properties: {
|
||||
symbol: {
|
||||
bsonType: 'string',
|
||||
description: 'Stock symbol (e.g., AAPL, GOOGL)'
|
||||
},
|
||||
source: {
|
||||
bsonType: 'string',
|
||||
description: 'Data source (news, social, earnings_call, etc.)'
|
||||
},
|
||||
timestamp: {
|
||||
bsonType: 'date',
|
||||
description: 'When the sentiment was recorded'
|
||||
},
|
||||
sentiment_score: {
|
||||
bsonType: 'double',
|
||||
minimum: -1.0,
|
||||
maximum: 1.0,
|
||||
description: 'Sentiment score between -1 (negative) and 1 (positive)'
|
||||
},
|
||||
confidence: {
|
||||
bsonType: 'double',
|
||||
minimum: 0.0,
|
||||
maximum: 1.0,
|
||||
description: 'Confidence level of the sentiment analysis'
|
||||
},
|
||||
text_snippet: {
|
||||
bsonType: 'string',
|
||||
description: 'Original text that was analyzed'
|
||||
},
|
||||
metadata: {
|
||||
bsonType: 'object',
|
||||
description: 'Additional metadata about the sentiment source'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Raw Documents Collection (for news articles, social media posts, etc.)
|
||||
db.createCollection('raw_documents', {
|
||||
validator: {
|
||||
$jsonSchema: {
|
||||
bsonType: 'object',
|
||||
required: ['source', 'document_type', 'timestamp', 'content'],
|
||||
properties: {
|
||||
source: {
|
||||
bsonType: 'string',
|
||||
description: 'Document source (news_api, twitter, reddit, etc.)'
|
||||
},
|
||||
document_type: {
|
||||
bsonType: 'string',
|
||||
enum: ['news_article', 'social_post', 'earnings_transcript', 'research_report', 'press_release'],
|
||||
description: 'Type of document'
|
||||
},
|
||||
timestamp: {
|
||||
bsonType: 'date',
|
||||
description: 'When the document was created/published'
|
||||
},
|
||||
symbols: {
|
||||
bsonType: 'array',
|
||||
items: {
|
||||
bsonType: 'string'
|
||||
},
|
||||
description: 'Array of stock symbols mentioned in the document'
|
||||
},
|
||||
title: {
|
||||
bsonType: 'string',
|
||||
description: 'Document title or headline'
|
||||
},
|
||||
content: {
|
||||
bsonType: 'string',
|
||||
description: 'Full document content'
|
||||
},
|
||||
url: {
|
||||
bsonType: 'string',
|
||||
description: 'Original URL of the document'
|
||||
},
|
||||
author: {
|
||||
bsonType: 'string',
|
||||
description: 'Document author or source account'
|
||||
},
|
||||
processed: {
|
||||
bsonType: 'bool',
|
||||
description: 'Whether this document has been processed for sentiment'
|
||||
},
|
||||
metadata: {
|
||||
bsonType: 'object',
|
||||
description: 'Additional document metadata'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Market Events Collection (for significant market events and their impact)
|
||||
db.createCollection('market_events', {
|
||||
validator: {
|
||||
$jsonSchema: {
|
||||
bsonType: 'object',
|
||||
required: ['event_type', 'timestamp', 'description'],
|
||||
properties: {
|
||||
event_type: {
|
||||
bsonType: 'string',
|
||||
enum: ['earnings', 'merger', 'acquisition', 'ipo', 'dividend', 'split', 'regulatory', 'economic_indicator'],
|
||||
description: 'Type of market event'
|
||||
},
|
||||
timestamp: {
|
||||
bsonType: 'date',
|
||||
description: 'When the event occurred or was announced'
|
||||
},
|
||||
symbols: {
|
||||
bsonType: 'array',
|
||||
items: {
|
||||
bsonType: 'string'
|
||||
},
|
||||
description: 'Stock symbols affected by this event'
|
||||
},
|
||||
description: {
|
||||
bsonType: 'string',
|
||||
description: 'Event description'
|
||||
},
|
||||
impact_score: {
|
||||
bsonType: 'double',
|
||||
minimum: -5.0,
|
||||
maximum: 5.0,
|
||||
description: 'Expected market impact score'
|
||||
},
|
||||
source_documents: {
|
||||
bsonType: 'array',
|
||||
items: {
|
||||
bsonType: 'objectId'
|
||||
},
|
||||
description: 'References to raw_documents that reported this event'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Create indexes for efficient querying
|
||||
|
||||
// Sentiment Analysis indexes
|
||||
db.sentiment_analysis.createIndex({ symbol: 1, timestamp: -1 });
|
||||
db.sentiment_analysis.createIndex({ source: 1, timestamp: -1 });
|
||||
db.sentiment_analysis.createIndex({ timestamp: -1 });
|
||||
db.sentiment_analysis.createIndex({ symbol: 1, source: 1, timestamp: -1 });
|
||||
|
||||
// Raw Documents indexes
|
||||
db.raw_documents.createIndex({ symbols: 1, timestamp: -1 });
|
||||
db.raw_documents.createIndex({ source: 1, timestamp: -1 });
|
||||
db.raw_documents.createIndex({ document_type: 1, timestamp: -1 });
|
||||
db.raw_documents.createIndex({ processed: 1, timestamp: -1 });
|
||||
db.raw_documents.createIndex({ timestamp: -1 });
|
||||
|
||||
// Market Events indexes
|
||||
db.market_events.createIndex({ symbols: 1, timestamp: -1 });
|
||||
db.market_events.createIndex({ event_type: 1, timestamp: -1 });
|
||||
db.market_events.createIndex({ timestamp: -1 });
|
||||
|
||||
// Insert some sample data for testing
|
||||
|
||||
// Sample sentiment data
|
||||
db.sentiment_analysis.insertMany([
|
||||
{
|
||||
symbol: 'AAPL',
|
||||
source: 'news_analysis',
|
||||
timestamp: new Date(),
|
||||
sentiment_score: 0.75,
|
||||
confidence: 0.89,
|
||||
text_snippet: 'Apple reports strong quarterly earnings...',
|
||||
metadata: {
|
||||
article_id: 'news_001',
|
||||
provider: 'financial_news_api'
|
||||
}
|
||||
},
|
||||
{
|
||||
symbol: 'GOOGL',
|
||||
source: 'social_media',
|
||||
timestamp: new Date(),
|
||||
sentiment_score: -0.25,
|
||||
confidence: 0.67,
|
||||
text_snippet: 'Concerns about Google AI regulation...',
|
||||
metadata: {
|
||||
platform: 'twitter',
|
||||
engagement_score: 450
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
// Sample raw document
|
||||
db.raw_documents.insertOne({
|
||||
source: 'financial_news_api',
|
||||
document_type: 'news_article',
|
||||
timestamp: new Date(),
|
||||
symbols: ['AAPL', 'MSFT'],
|
||||
title: 'Tech Giants Show Strong Q4 Performance',
|
||||
content: 'Apple and Microsoft both reported better than expected earnings for Q4...',
|
||||
url: 'https://example.com/tech-earnings-q4',
|
||||
author: 'Financial Reporter',
|
||||
processed: true,
|
||||
metadata: {
|
||||
word_count: 850,
|
||||
readability_score: 0.75
|
||||
}
|
||||
});
|
||||
|
||||
// Sample market event
|
||||
db.market_events.insertOne({
|
||||
event_type: 'earnings',
|
||||
timestamp: new Date(),
|
||||
symbols: ['AAPL'],
|
||||
description: 'Apple Q4 2024 Earnings Report',
|
||||
impact_score: 2.5,
|
||||
source_documents: []
|
||||
});
|
||||
|
||||
print('MongoDB initialization completed successfully!');
|
||||
print('Created collections: sentiment_analysis, raw_documents, market_events');
|
||||
print('Created indexes for efficient querying');
|
||||
print('Inserted sample data for testing');
|
||||
|
|
@ -67,6 +67,28 @@ services:
|
|||
networks:
|
||||
- trading-bot-network
|
||||
|
||||
# MongoDB - Document storage (sentiment, raw docs, unstructured data)
|
||||
mongodb:
|
||||
image: mongo:7-jammy
|
||||
container_name: trading-bot-mongodb
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: trading_admin
|
||||
MONGO_INITDB_ROOT_PASSWORD: trading_mongo_dev
|
||||
MONGO_INITDB_DATABASE: trading_documents
|
||||
ports:
|
||||
- "27017:27017"
|
||||
volumes:
|
||||
- mongodb_data:/data/db
|
||||
- ./database/mongodb/init:/docker-entrypoint-initdb.d
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
networks:
|
||||
- trading-bot-network
|
||||
|
||||
# Redis Insight - GUI for Dragonfly debugging
|
||||
redis-insight:
|
||||
image: redislabs/redisinsight:latest
|
||||
|
|
@ -86,7 +108,7 @@ services:
|
|||
image: dpage/pgadmin4:latest
|
||||
container_name: trading-bot-pgadmin
|
||||
environment:
|
||||
PGADMIN_DEFAULT_EMAIL: admin@example.com
|
||||
PGADMIN_DEFAULT_EMAIL: boki@stare.gg
|
||||
PGADMIN_DEFAULT_PASSWORD: admin123
|
||||
PGADMIN_CONFIG_SERVER_MODE: 'False'
|
||||
PGADMIN_DISABLE_POSTFIX: 'true'
|
||||
|
|
@ -100,6 +122,25 @@ services:
|
|||
networks:
|
||||
- trading-bot-network
|
||||
|
||||
# Mongo Express - MongoDB GUI
|
||||
mongo-express:
|
||||
image: mongo-express:latest
|
||||
container_name: trading-bot-mongo-express
|
||||
environment:
|
||||
ME_CONFIG_MONGODB_ADMINUSERNAME: trading_admin
|
||||
ME_CONFIG_MONGODB_ADMINPASSWORD: trading_mongo_dev
|
||||
ME_CONFIG_MONGODB_SERVER: mongodb
|
||||
ME_CONFIG_MONGODB_PORT: 27017
|
||||
ME_CONFIG_BASICAUTH_USERNAME: boki
|
||||
ME_CONFIG_BASICAUTH_PASSWORD: admin123
|
||||
ports:
|
||||
- "8081:8081"
|
||||
depends_on:
|
||||
- mongodb
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- trading-bot-network
|
||||
|
||||
# Prometheus - Metrics collection (optional)
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
|
|
@ -127,7 +168,7 @@ services:
|
|||
- "3000:3000"
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_PASSWORD=admin123
|
||||
- GF_SECURITY_ADMIN_USER=admin
|
||||
- GF_SECURITY_ADMIN_USER=boki
|
||||
- GF_USERS_ALLOW_SIGN_UP=false
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
|
|
@ -142,6 +183,7 @@ volumes:
|
|||
postgres_data:
|
||||
questdb_data:
|
||||
dragonfly_data:
|
||||
mongodb_data:
|
||||
pgadmin_data:
|
||||
prometheus_data:
|
||||
grafana_data:
|
||||
|
|
|
|||
7979
package-lock.json
generated
Normal file
7979
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -19,10 +19,9 @@
|
|||
"docker:reset": "pwsh ./scripts/docker.ps1 reset",
|
||||
"docker:admin": "pwsh ./scripts/docker.ps1 admin",
|
||||
"docker:monitoring": "pwsh ./scripts/docker.ps1 monitoring",
|
||||
|
||||
"infra:up": "docker-compose up -d dragonfly postgres questdb",
|
||||
"infra:up": "docker-compose up -d dragonfly postgres questdb mongodb",
|
||||
"infra:down": "docker-compose down",
|
||||
"infra:reset": "docker-compose down -v && docker-compose up -d dragonfly postgres questdb",
|
||||
"infra:reset": "docker-compose down -v && docker-compose up -d dragonfly postgres questdb mongodb",
|
||||
|
||||
"dev:full": "npm run infra:up && npm run docker:admin && turbo run dev",
|
||||
"dev:clean": "npm run infra:reset && npm run dev:full"
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"test": "echo \"No tests yet\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@stock-bot/shared-types": "workspace:*",
|
||||
"@stock-bot/shared-types": "*",
|
||||
"dotenv": "^16.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue