switched to angular

This commit is contained in:
Bojan Kucera 2025-06-02 19:37:33 -04:00
parent d3efed7ce7
commit 94e3c96ef6
53 changed files with 10976 additions and 1020 deletions

227
CONTEXT.md Normal file
View 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

View file

@ -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

View file

@ -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",

View file

@ -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": {

View file

@ -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"
},

View 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

View file

@ -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

View file

@ -0,0 +1,5 @@
{
"plugins": {
"@tailwindcss/postcss": {}
}
}

View file

@ -0,0 +1,4 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}

View 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"
}
]
}

View 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"
}
}
}
}
]
}

View file

@ -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.

View 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"
]
}
}
}
}
}
}

View file

@ -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 },
],
},
},
)

View file

@ -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>

View file

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -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

View file

@ -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;
}

View file

@ -1,8 +0,0 @@
import { TradingDashboard } from './components/TradingDashboard'
import './App.css'
function App() {
return <TradingDashboard />
}
export default App

View file

@ -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()
]
};

View 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 />

View file

@ -0,0 +1,3 @@
import { Routes } from '@angular/router';
export const routes: Routes = [];

View 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;
}
}

View file

@ -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');
});
});

View 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());
}
}

View file

@ -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

View file

@ -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>
);
}

View file

@ -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);
}

View 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>

View 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));

View file

@ -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>,
)

View 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;
}

View file

@ -1 +0,0 @@
/// <reference types="vite/client" />

View 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: [],
}

View file

@ -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"
]
}

View file

@ -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"
}
]
}

View file

@ -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"]
}

View 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"
]
}

View file

@ -1,7 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})

1287
bun.lock

File diff suppressed because it is too large Load diff

View 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');

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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"

View file

@ -11,7 +11,7 @@
"test": "echo \"No tests yet\""
},
"dependencies": {
"@stock-bot/shared-types": "workspace:*",
"@stock-bot/shared-types": "*",
"dotenv": "^16.4.5"
},
"devDependencies": {