commit 232a63dfe82449c505ee297f4a6af85c3a3d3cd9 Author: Bojan Kucera Date: Mon Jun 2 08:15:20 2025 -0400 initial setup diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..87e525d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,60 @@ +# Node.js +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# Build outputs +dist/ +build/ +.turbo/ +.next/ + +# Environment files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +logs/ +*.log + +# Git +.git/ +.gitignore + +# Documentation +README.md +DOCKER.md +docs/ + +# Docker +Dockerfile* +docker-compose* +.dockerignore + +# Cache +.cache/ +.temp/ +.tmp/ + +# Test coverage +coverage/ +.nyc_output/ + +# Misc +*.tgz +*.tar.gz diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..15bc837 --- /dev/null +++ b/.env.example @@ -0,0 +1,35 @@ +# Environment Configuration +NODE_ENV=development +PORT=3000 + +# Database Configuration +QUESTDB_HOST=localhost +QUESTDB_PORT=9000 +QUESTDB_DATABASE=qdb + +POSTGRES_HOST=localhost +POSTGRES_PORT=5432 +POSTGRES_DATABASE=stockbot +POSTGRES_USERNAME=postgres +POSTGRES_PASSWORD=password + +DRAGONFLY_HOST=localhost +DRAGONFLY_PORT=6379 +DRAGONFLY_PASSWORD= + +# API Keys (Add your actual keys here) +ALPHA_VANTAGE_API_KEY=demo +ALPACA_API_KEY=your_alpaca_key_here +ALPACA_SECRET_KEY=your_alpaca_secret_here + +# Trading Configuration +PAPER_TRADING=true +MAX_POSITION_SIZE=0.1 +MAX_DAILY_LOSS=1000 + +# Logging +LOG_LEVEL=debug + +# Feature Flags +ENABLE_ML_SIGNALS=false +ENABLE_SENTIMENT_ANALYSIS=false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e77522f --- /dev/null +++ b/.gitignore @@ -0,0 +1,106 @@ +# Dependencies +node_modules/ +.pnp +.pnp.js + +# Production builds +dist/ +build/ +.next/ + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +bun-debug.log* +bun-error.log* +*.log + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ +*.lcov + +# Dependency directories +.pnpm-store/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt + +# Storybook build outputs +.out +.storybook-out + +# Temporary folders +tmp/ +temp/ + +# Editor directories and files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Trading bot specific +data/ +backtest-results/ +logs/ +*.db +*.sqlite +*.sqlite3 + +# Turbo +.turbo diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..2146043 --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,272 @@ +# 🐳 Trading Bot Docker Infrastructure + +This directory contains the Docker Compose configuration for the Trading Bot infrastructure, including databases, caching, monitoring, and admin tools. + +## 🏗️ Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Trading Bot Infrastructure │ +├─────────────────────────────────────────────────────────────────┤ +│ 🐉 Dragonfly (Redis) │ 🐘 PostgreSQL │ 📊 QuestDB │ +│ Events & Caching │ Operational DB │ Time-series Data │ +│ Port: 6379 │ Port: 5432 │ Port: 9000/8812 │ +├─────────────────────────────────────────────────────────────────┤ +│ 🔧 Redis Insight │ 🛠️ PgAdmin │ 📈 Prometheus │ +│ Dragonfly GUI │ PostgreSQL GUI │ Metrics Collection │ +│ Port: 8001 │ Port: 8080 │ Port: 9090 │ +├─────────────────────────────────────────────────────────────────┤ +│ 📊 Grafana Dashboard │ +│ Visualization & Alerting │ +│ Port: 3000 │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## 🚀 Quick Start + +### 1. Start Core Infrastructure +```powershell +# Start essential services (Dragonfly, PostgreSQL, QuestDB) +npm run infra:up + +# Or use the management script +npm run docker:start +``` + +### 2. Start Admin Interfaces +```powershell +# Start Redis Insight and PgAdmin +npm run docker:admin +``` + +### 3. Start Full Development Environment +```powershell +# Start infrastructure + admin + your trading services +npm run dev:full +``` + +## 📊 Service Access Points + +| Service | URL | Credentials | Purpose | +|---------|-----|-------------|---------| +| **Dragonfly** | `localhost:6379` | None | Redis-compatible cache & events | +| **PostgreSQL** | `localhost:5432` | `trading_user` / `trading_pass_dev` | Operational database | +| **QuestDB Console** | http://localhost:9000 | None | Time-series database GUI | +| **QuestDB Wire** | `localhost:8812` | `admin` / `quest` | PostgreSQL protocol access | +| **Redis Insight** | http://localhost:8001 | None | Dragonfly/Redis management | +| **PgAdmin** | http://localhost:8080 | `admin@tradingbot.local` / `admin123` | PostgreSQL management | +| **Prometheus** | http://localhost:9090 | None | Metrics collection | +| **Grafana** | http://localhost:3000 | `admin` / `admin123` | Dashboards & alerts | + +## 🛠️ Management Commands + +### Using NPM Scripts (Recommended) +```powershell +# Infrastructure management +npm run docker:start # Start core services +npm run docker:stop # Stop all services +npm run docker:restart # Restart services +npm run docker:status # Show service status +npm run docker:logs # Show logs +npm run docker:reset # Reset all data (destructive) + +# Additional services +npm run docker:admin # Start admin interfaces +npm run docker:monitoring # Start monitoring stack + +# Quick development +npm run dev:full # Infrastructure + admin + services +npm run dev:clean # Reset + full restart +``` + +### Using PowerShell Script Directly +```powershell +# Basic operations +./scripts/docker.ps1 start +./scripts/docker.ps1 stop +./scripts/docker.ps1 status + +# View logs for specific service +./scripts/docker.ps1 logs -Service dragonfly + +# Development mode (lighter configuration) +./scripts/docker.ps1 start -Dev + +# Get help +./scripts/docker.ps1 help +``` + +### Using Docker Compose Directly +```powershell +# Start core services +docker-compose up -d dragonfly postgres questdb + +# Start with development overrides +docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d + +# Start monitoring (optional) +docker-compose --profile monitoring up -d + +# View logs +docker-compose logs -f + +# Stop everything +docker-compose down + +# Reset all data +docker-compose down -v +``` + +## 🗃️ Database Schemas + +### PostgreSQL Structure +```sql +trading.* -- Orders, positions, executions, accounts +strategy.* -- Strategies, signals, performance +risk.* -- Risk limits, events, monitoring +audit.* -- System events, health, configuration +``` + +### Initial Data +The PostgreSQL database is automatically initialized with: +- Common stock symbols (AAPL, GOOGL, etc.) +- Demo trading account with $100k +- Sample mean reversion strategy +- Basic risk limits +- Audit triggers for data tracking + +## 📈 Monitoring & Observability + +### Prometheus Metrics +The setup includes Prometheus configuration to monitor: +- Trading bot services (ports 3001, 3002, 4001) +- Dragonfly performance +- PostgreSQL health +- QuestDB metrics + +### Grafana Dashboards +Access Grafana at http://localhost:3000 to view: +- Trading system performance +- Database metrics +- Service health +- Risk monitoring + +## 🔧 Configuration + +### Environment Variables +Services use these environment variables (configured in docker-compose.yml): + +```yaml +# Dragonfly +DRAGONFLY_HOST=localhost +DRAGONFLY_PORT=6379 + +# PostgreSQL +POSTGRES_HOST=localhost +POSTGRES_PORT=5432 +POSTGRES_DB=trading_bot +POSTGRES_USER=trading_user +POSTGRES_PASSWORD=trading_pass_dev + +# QuestDB +QUESTDB_HOST=localhost +QUESTDB_PORT=8812 +QUESTDB_DB=qdb +``` + +### Development vs Production +- **Development**: Use `docker-compose.dev.yml` for lighter resource usage +- **Production**: Use base `docker-compose.yml` with proper secrets management + +## 🚨 Troubleshooting + +### Common Issues + +**Port Conflicts** +```powershell +# Check what's using a port +netstat -ano | findstr :6379 + +# Kill process if needed +taskkill /PID /F +``` + +**Permission Issues** +```powershell +# Ensure Docker is running as administrator +# Check Docker Desktop settings +``` + +**Data Corruption** +```powershell +# Reset all data and restart fresh +npm run docker:reset +``` + +**Service Won't Start** +```powershell +# Check logs for specific service +npm run docker:logs +./scripts/docker.ps1 logs -Service dragonfly +``` + +### Health Checks +All services include health checks. Monitor with: +```powershell +npm run docker:status +docker ps --filter "name=trading-bot" +``` + +## 🔒 Security Notes + +### Development Security +- Default passwords are for development only +- No SSL/TLS in development mode +- Services exposed on localhost only + +### Production Considerations +- Change all default passwords +- Enable SSL/TLS encryption +- Use Docker secrets for sensitive data +- Implement proper backup strategies +- Set up log rotation +- Configure firewall rules + +## 📋 Maintenance + +### Backup Data +```powershell +# Backup PostgreSQL +docker exec trading-bot-postgres pg_dump -U trading_user trading_bot > backup.sql + +# Backup Dragonfly (if persistence enabled) +docker exec trading-bot-dragonfly redis-cli BGSAVE +``` + +### Update Services +```powershell +# Pull latest images +docker-compose pull + +# Restart with new images +npm run docker:restart +``` + +### Clean Up +```powershell +# Remove unused containers and images +docker system prune + +# Remove all trading-bot related containers and volumes +docker-compose down -v --remove-orphans +``` + +## 🎯 Next Steps + +1. **Start the infrastructure**: `npm run docker:start` +2. **Open admin interfaces**: `npm run docker:admin` +3. **Configure your services**: Update .env files to use Docker services +4. **Start development**: `npm run dev:full` +5. **Monitor health**: Check http://localhost:8001 and http://localhost:8080 + +For more advanced configuration, see the individual service documentation in the `database/` and `monitoring/` directories. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b881618 --- /dev/null +++ b/README.md @@ -0,0 +1,180 @@ +# 🤖 Stock Bot Trading System + +A comprehensive trading bot built with Bun and Turborepo, featuring a service-oriented architecture for real-time market data processing and strategy execution. + +## 🚀 Quick Start + +### Prerequisites +- [Bun](https://bun.sh/) runtime +- Node.js 18+ (for compatibility) + +### Installation +```bash +# Clone and install dependencies +git clone +cd stock-bot +bun install +``` + +### Running the System + +#### Option 1: VS Code Tasks (Recommended) +1. Open the project in VS Code +2. Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on Mac) +3. Type "Tasks: Run Task" and select it +4. Choose "Start All Services" + +#### Option 2: Manual Startup +```bash +# Terminal 1: Start Market Data Gateway +cd apps/core-services/market-data-gateway +bun run dev + +# Terminal 2: Start Trading Dashboard +cd apps/interface-services/trading-dashboard +bun run dev +``` + +### Access Points +- **Trading Dashboard**: http://localhost:5173 +- **Market Data API**: http://localhost:3001 +- **Health Check**: http://localhost:3001/health + +## 📊 Dashboard Features + +### Real-time Market Data +- Live price feeds for AAPL, GOOGL, MSFT, TSLA, AMZN +- WebSocket connections for real-time updates +- Service health monitoring + +### Professional UI Components +- Built with Tremor UI for financial visualizations +- Interactive charts and metrics +- Responsive design for all devices + +### Dashboard Tabs +1. **Market Data**: Live prices, volume, bid/ask spreads +2. **Portfolio**: Holdings allocation and performance +3. **Charts**: Price and volume analysis +4. **Performance**: Trading metrics and statistics + +## 🏗️ Architecture + +### Service-Oriented Design +``` +apps/ +├── core-services/ +│ └── market-data-gateway/ # Market data ingestion +├── interface-services/ +│ └── trading-dashboard/ # React dashboard +├── data-services/ # (Future) Data processing +├── execution-services/ # (Future) Order management +├── intelligence-services/ # (Future) Strategy engine +├── platform-services/ # (Future) Infrastructure +└── integration-services/ # (Future) External APIs +``` + +### Shared Packages +``` +packages/ +├── shared-types/ # TypeScript definitions +├── config/ # Configuration management +├── database/ # (Future) Database utilities +└── trading-core/ # (Future) Core trading logic +``` + +## 🔧 Development + +### Project Structure +- **Turborepo**: Monorepo management +- **Bun**: Package manager and runtime +- **TypeScript**: Type safety across all services +- **React + Vite**: Modern frontend development +- **Tremor UI**: Financial dashboard components + +### Key Technologies +- **Backend**: Hono framework, WebSockets, Redis +- **Frontend**: React, TypeScript, Tremor UI +- **Data**: QuestDB (planned), PostgreSQL (planned) +- **Deployment**: Docker, Kubernetes (planned) + +## 📈 Current Status + +### ✅ Completed +- [x] Monorepo setup with Turborepo +- [x] Market Data Gateway service +- [x] Real-time WebSocket connections +- [x] Professional React dashboard +- [x] Tremor UI integration +- [x] TypeScript type system +- [x] Service health monitoring + +### 🚧 In Progress +- [ ] Strategy execution engine +- [ ] Risk management system +- [ ] Portfolio tracking +- [ ] Real broker integration + +### 🔮 Planned +- [ ] Advanced charting +- [ ] Backtesting framework +- [ ] Machine learning signals +- [ ] Multi-broker support +- [ ] Mobile application + +## 🛠️ API Endpoints + +### Market Data Gateway (Port 3001) +``` +GET /health # Service health check +GET /api/market-data/:symbol # Current market data +GET /api/ohlcv/:symbol # Historical OHLCV data +WS ws://localhost:3001 # Real-time data stream +``` + +### Data Format +```typescript +interface MarketData { + symbol: string; + price: number; + bid: number; + ask: number; + volume: number; + timestamp: string; +} +``` + +## 🔧 Configuration + +Environment variables are managed in `.env`: +```bash +# Database Configuration +DATABASE_URL=postgresql://... +QUESTDB_URL=http://localhost:9000 + +# External APIs +ALPHA_VANTAGE_API_KEY=your_key_here +ALPACA_API_KEY=your_key_here + +# Service Configuration +NODE_ENV=development +LOG_LEVEL=info +``` + +## 🤝 Contributing + +1. Fork the repository +2. Create a feature branch: `git checkout -b feature/amazing-feature` +3. Commit changes: `git commit -m 'Add amazing feature'` +4. Push to branch: `git push origin feature/amazing-feature` +5. Open a Pull Request + +## 📝 License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## 🙏 Acknowledgments + +- [Tremor UI](https://tremor.so/) for beautiful financial components +- [Bun](https://bun.sh/) for fast runtime and package management +- [Turborepo](https://turbo.build/) for monorepo tooling diff --git a/SETUP-COMPLETE.md b/SETUP-COMPLETE.md new file mode 100644 index 0000000..3cf2298 --- /dev/null +++ b/SETUP-COMPLETE.md @@ -0,0 +1,149 @@ +# 🚀 Trading Bot Docker Infrastructure Setup Complete! + +Your Docker infrastructure has been successfully configured. Here's what you have: + +## 📦 What's Included + +### Core Services +- **🐉 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) + +### Admin Tools +- **🔧 Redis Insight**: Dragonfly management GUI (Port 8001) +- **🛠️ PgAdmin**: PostgreSQL administration (Port 8080) + +### Monitoring (Optional) +- **📈 Prometheus**: Metrics collection (Port 9090) +- **📊 Grafana**: Dashboards and alerting (Port 3000) + +## 🏁 Getting Started + +### Step 1: Start Docker Desktop +Make sure Docker Desktop is running on your Windows machine. + +### Step 2: Start Infrastructure +```powershell +# Quick start - core services only +npm run infra:up + +# Or with management script +npm run docker:start + +# Full development environment +npm run dev:full +``` + +### Step 3: Access Admin Interfaces +```powershell +# Start admin tools +npm run docker:admin +``` + +## 🔗 Access URLs + +Once running, access these services: + +| Service | URL | Login | +|---------|-----|-------| +| **QuestDB Console** | http://localhost:9000 | No login required | +| **Redis Insight** | http://localhost:8001 | No login required | +| **PgAdmin** | http://localhost:8080 | `admin@tradingbot.local` / `admin123` | +| **Prometheus** | http://localhost:9090 | No login required | +| **Grafana** | http://localhost:3000 | `admin` / `admin123` | + +## 📊 Database Connections + +### From Your Trading Services +Update your `.env` file: +```env +# Dragonfly (Redis replacement) +DRAGONFLY_HOST=localhost +DRAGONFLY_PORT=6379 + +# PostgreSQL +POSTGRES_HOST=localhost +POSTGRES_PORT=5432 +POSTGRES_DB=trading_bot +POSTGRES_USER=trading_user +POSTGRES_PASSWORD=trading_pass_dev + +# QuestDB +QUESTDB_HOST=localhost +QUESTDB_PORT=8812 +QUESTDB_DB=qdb +``` + +### Database Schema +PostgreSQL includes these pre-configured schemas: +- `trading.*` - Orders, positions, executions, accounts +- `strategy.*` - Strategies, signals, performance metrics +- `risk.*` - Risk limits, events, monitoring +- `audit.*` - System events, health checks, configuration + +## 🛠️ Management Commands + +```powershell +# Basic operations +npm run docker:start # Start core services +npm run docker:stop # Stop all services +npm run docker:status # Check service status +npm run docker:logs # View all logs +npm run docker:reset # Reset all data (destructive!) + +# Additional services +npm run docker:admin # Start admin interfaces +npm run docker:monitoring # Start Prometheus & Grafana + +# Development workflows +npm run dev:full # Infrastructure + admin + your services +npm run dev:clean # Reset + restart everything + +# Direct PowerShell script access +./scripts/docker.ps1 start +./scripts/docker.ps1 logs -Service dragonfly +./scripts/docker.ps1 help +``` + +## ✅ Next Steps + +1. **Start Docker Desktop** if not already running +2. **Run**: `npm run docker:start` to start core infrastructure +3. **Run**: `npm run docker:admin` to start admin tools +4. **Update** your environment variables to use the Docker services +5. **Test** Dragonfly connection in your EventPublisher service +6. **Verify** database schema in PgAdmin +7. **Start** your trading services with the new infrastructure + +## 🎯 Ready for Integration + +Your EventPublisher service is already configured to use Dragonfly. The infrastructure supports: + +- ✅ **Event Streaming**: Dragonfly handles Redis Streams for real-time events +- ✅ **Caching**: High-performance caching with better memory efficiency +- ✅ **Operational Data**: PostgreSQL with complete trading schemas +- ✅ **Time-Series Data**: QuestDB for market data and analytics +- ✅ **Monitoring**: Full observability stack ready +- ✅ **Admin Tools**: Web-based management interfaces + +The system is designed to scale from development to production with the same Docker configuration. + +## 🔧 Troubleshooting + +If you encounter issues: +```powershell +# Check Docker status +docker --version +docker-compose --version + +# Verify services +npm run docker:status + +# View specific service logs +./scripts/docker.ps1 logs -Service dragonfly + +# Reset if needed +npm run docker:reset +``` + +**Happy Trading! 🚀📈** diff --git a/apps/core-services/market-data-gateway/package.json b/apps/core-services/market-data-gateway/package.json new file mode 100644 index 0000000..b8f5442 --- /dev/null +++ b/apps/core-services/market-data-gateway/package.json @@ -0,0 +1,20 @@ +{ + "name": "market-data-gateway", + "version": "1.0.0", + "description": "Market data ingestion service", + "main": "src/index.ts", + "scripts": { + "dev": "bun run --watch src/index.ts", + "start": "bun run src/index.ts", + "test": "echo 'No tests yet'" + }, "dependencies": { + "hono": "^4.6.3", + "ioredis": "^5.4.1", + "@stock-bot/config": "workspace:*", + "@stock-bot/shared-types": "workspace:*", + "ws": "^8.18.0" + }, "devDependencies": { + "bun-types": "^1.2.15", + "@types/ws": "^8.5.12" + } +} diff --git a/apps/core-services/market-data-gateway/src/index.ts b/apps/core-services/market-data-gateway/src/index.ts new file mode 100644 index 0000000..ff8aa9c --- /dev/null +++ b/apps/core-services/market-data-gateway/src/index.ts @@ -0,0 +1,83 @@ +import { Hono } from 'hono'; +import { serve } from 'bun'; + +const app = new Hono(); + +// Health check endpoint +app.get('/health', (c) => { + return c.json({ + service: 'market-data-gateway', + status: 'healthy', + timestamp: new Date(), + version: '1.0.0' + }); +}); + +// Demo market data endpoint +app.get('/api/market-data/:symbol', (c) => { + const symbol = c.req.param('symbol'); + + // Generate demo data + const demoData = { + symbol: symbol.toUpperCase(), + price: 150 + Math.random() * 50, // Random price between 150-200 + bid: 149.99, + ask: 150.01, + volume: Math.floor(Math.random() * 1000000), + timestamp: new Date() + }; + + return c.json({ + success: true, + data: demoData, + timestamp: new Date() + }); +}); + +// Demo OHLCV endpoint +app.get('/api/ohlcv/:symbol', (c) => { + const symbol = c.req.param('symbol'); + const limit = parseInt(c.req.query('limit') || '10'); + + // Generate demo OHLCV data + const data = []; + let basePrice = 150; + + for (let i = limit - 1; i >= 0; i--) { + const open = basePrice + (Math.random() - 0.5) * 10; + const close = open + (Math.random() - 0.5) * 5; + const high = Math.max(open, close) + Math.random() * 3; + const low = Math.min(open, close) - Math.random() * 3; + + data.push({ + symbol: symbol.toUpperCase(), + timestamp: new Date(Date.now() - i * 60000), // 1 minute intervals + open: Math.round(open * 100) / 100, + high: Math.round(high * 100) / 100, + low: Math.round(low * 100) / 100, + close: Math.round(close * 100) / 100, + volume: Math.floor(Math.random() * 50000) + 10000 + }); + + basePrice = close; + } + + return c.json({ + success: true, + data, + timestamp: new Date() + }); +}); + +const PORT = 3001; + +console.log(`🚀 Market Data Gateway starting on port ${PORT}`); + +serve({ + port: PORT, + fetch: app.fetch, +}); + +console.log(`📊 Market Data Gateway running on http://localhost:${PORT}`); +console.log(`🔍 Health check: http://localhost:${PORT}/health`); +console.log(`📈 Demo data: http://localhost:${PORT}/api/market-data/AAPL`); diff --git a/apps/core-services/market-data-gateway/src/services/DataNormalizer.ts b/apps/core-services/market-data-gateway/src/services/DataNormalizer.ts new file mode 100644 index 0000000..60df311 --- /dev/null +++ b/apps/core-services/market-data-gateway/src/services/DataNormalizer.ts @@ -0,0 +1,117 @@ +import type { MarketData, OHLCV } from '@stock-bot/shared-types'; +import { dataProviderConfigs } from '@stock-bot/config'; + +export class DataNormalizer { + /** + * Normalize market data from different providers to our standard format + */ + normalizeMarketData(rawData: any, source: string): MarketData { + switch (source) { + case 'alpha-vantage': + return this.normalizeAlphaVantage(rawData); + case 'yahoo-finance': + return this.normalizeYahooFinance(rawData); + default: + throw new Error(`Unsupported data source: ${source}`); + } + } + + /** + * Normalize OHLCV data from different providers + */ + normalizeOHLCV(rawData: any, source: string): OHLCV[] { + switch (source) { + case 'alpha-vantage': + return this.normalizeAlphaVantageOHLCV(rawData); + case 'yahoo-finance': + return this.normalizeYahooFinanceOHLCV(rawData); + default: + throw new Error(`Unsupported data source: ${source}`); + } + } + + private normalizeAlphaVantage(data: any): MarketData { + const quote = data['Global Quote']; + return { + symbol: quote['01. symbol'], + price: parseFloat(quote['05. price']), + bid: parseFloat(quote['05. price']) - 0.01, // Approximate bid/ask + ask: parseFloat(quote['05. price']) + 0.01, + volume: parseInt(quote['06. volume']), + timestamp: new Date(), + }; + } + + private normalizeYahooFinance(data: any): MarketData { + return { + symbol: data.symbol, + price: data.regularMarketPrice, + bid: data.bid || data.regularMarketPrice - 0.01, + ask: data.ask || data.regularMarketPrice + 0.01, + volume: data.regularMarketVolume, + timestamp: new Date(data.regularMarketTime * 1000), + }; + } + + private normalizeAlphaVantageOHLCV(data: any): OHLCV[] { + const timeSeries = data['Time Series (1min)'] || data['Time Series (5min)'] || data['Time Series (Daily)']; + const symbol = data['Meta Data']['2. Symbol']; + + return Object.entries(timeSeries).map(([timestamp, values]: [string, any]) => ({ + symbol, + timestamp: new Date(timestamp), + open: parseFloat(values['1. open']), + high: parseFloat(values['2. high']), + low: parseFloat(values['3. low']), + close: parseFloat(values['4. close']), + volume: parseInt(values['5. volume']), + })).sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); + } + + private normalizeYahooFinanceOHLCV(data: any): OHLCV[] { + const result = data.chart.result[0]; + const timestamps = result.timestamp; + const quotes = result.indicators.quote[0]; + + return timestamps.map((timestamp: number, index: number) => ({ + symbol: result.meta.symbol, + timestamp: new Date(timestamp * 1000), + open: quotes.open[index], + high: quotes.high[index], + low: quotes.low[index], + close: quotes.close[index], + volume: quotes.volume[index], + })); + } + + /** + * Validate market data quality + */ + validateMarketData(data: MarketData): boolean { + return ( + data.symbol && + typeof data.price === 'number' && + data.price > 0 && + typeof data.volume === 'number' && + data.volume >= 0 && + data.timestamp instanceof Date + ) as boolean; + } + + /** + * Validate OHLCV data quality + */ + validateOHLCV(data: OHLCV): boolean { + return ( + data.symbol && + typeof data.open === 'number' && data.open > 0 && + typeof data.high === 'number' && data.high > 0 && + typeof data.low === 'number' && data.low > 0 && + typeof data.close === 'number' && data.close > 0 && + data.high >= Math.max(data.open, data.close) && + data.low <= Math.min(data.open, data.close) && + typeof data.volume === 'number' && data.volume >= 0 && + data.timestamp instanceof Date + ) as boolean; + } +} diff --git a/apps/core-services/market-data-gateway/src/services/EventPublisher.ts b/apps/core-services/market-data-gateway/src/services/EventPublisher.ts new file mode 100644 index 0000000..3bfc790 --- /dev/null +++ b/apps/core-services/market-data-gateway/src/services/EventPublisher.ts @@ -0,0 +1,138 @@ +import Redis from 'ioredis'; +import { databaseConfig } from '@stock-bot/config'; +import type { MarketDataEvent, SignalEvent, TradingEvent } from '@stock-bot/shared-types'; + +export class EventPublisher { + private dragonfly: Redis; + private readonly STREAM_NAME = 'trading-events'; constructor() { + this.dragonfly = new Redis({ + host: databaseConfig.dragonfly.host, + port: databaseConfig.dragonfly.port, + password: databaseConfig.dragonfly.password, + maxRetriesPerRequest: 3, + }); + + this.dragonfly.on('connect', () => { + console.log('🐉 Connected to Dragonfly for event publishing'); + }); + + this.dragonfly.on('error', (error) => { + console.error('❌ Dragonfly connection error:', error); + }); + } + + /** + * Publish a market data event to the event stream + */ async publishMarketData(event: MarketDataEvent): Promise { + try { + await this.dragonfly.xadd( + this.STREAM_NAME, + '*', + 'type', event.type, + 'data', JSON.stringify(event.data), + 'timestamp', event.timestamp.toISOString() + ); + } catch (error) { + console.error('Error publishing market data event:', error); + throw error; + } + } + /** + * Publish a trading signal event + */ + async publishSignal(event: SignalEvent): Promise { + try { + await this.dragonfly.xadd( + this.STREAM_NAME, + '*', + 'type', event.type, + 'signal', JSON.stringify(event.signal), + 'timestamp', event.timestamp.toISOString() + ); + } catch (error) { + console.error('Error publishing signal event:', error); + throw error; + } + } + + /** + * Publish any trading event + */ + async publishEvent(event: TradingEvent): Promise { + try { + const fields: string[] = ['type', event.type, 'timestamp', event.timestamp.toISOString()]; + + if ('data' in event) { + fields.push('data', JSON.stringify(event.data)); + } + if ('order' in event) { + fields.push('order', JSON.stringify(event.order)); + } + if ('signal' in event) { + fields.push('signal', JSON.stringify(event.signal)); + } + + await this.dragonfly.xadd(this.STREAM_NAME, '*', ...fields); + } catch (error) { + console.error('Error publishing event:', error); + throw error; + } + } + /** + * Cache market data in Dragonfly for quick access + */ + async cacheMarketData(symbol: string, data: any, ttl: number = 60): Promise { + try { + const key = `market-data:${symbol}`; + await this.dragonfly.setex(key, ttl, JSON.stringify(data)); + } catch (error) { + console.error('Error caching market data:', error); + } + } + /** + * Get cached market data from Dragonfly + */ + async getCachedMarketData(symbol: string): Promise { + try { + const key = `market-data:${symbol}`; + const cached = await this.dragonfly.get(key); + return cached ? JSON.parse(cached) : null; + } catch (error) { + console.error('Error getting cached market data:', error); + return null; + } + } + /** + * Publish to a specific channel for real-time subscriptions + */ + async publishToChannel(channel: string, data: any): Promise { + try { + await this.dragonfly.publish(channel, JSON.stringify(data)); + } catch (error) { + console.error(`Error publishing to channel ${channel}:`, error); + throw error; + } + } + /** + * Set up health monitoring + */ + async setServiceHealth(serviceName: string, status: 'healthy' | 'unhealthy'): Promise { + try { + const key = `health:${serviceName}`; + const healthData = { + status, + timestamp: new Date().toISOString(), + lastSeen: Date.now(), + }; + await this.dragonfly.setex(key, 300, JSON.stringify(healthData)); // 5 minutes TTL + } catch (error) { + console.error('Error setting service health:', error); + } + } + /** + * Close Dragonfly connection + */ + async disconnect(): Promise { + await this.dragonfly.quit(); + } +} diff --git a/apps/core-services/market-data-gateway/src/services/MarketDataService.ts b/apps/core-services/market-data-gateway/src/services/MarketDataService.ts new file mode 100644 index 0000000..aa938d8 --- /dev/null +++ b/apps/core-services/market-data-gateway/src/services/MarketDataService.ts @@ -0,0 +1,278 @@ +import type { MarketData, OHLCV, MarketDataEvent } from '@stock-bot/shared-types'; +import { dataProviderConfigs } from '@stock-bot/config'; +import { EventPublisher } from './EventPublisher'; +import { DataNormalizer } from './DataNormalizer'; + +export class MarketDataService { + private wsClients: Set = new Set(); + private subscriptions: Map> = new Map(); + private dataUpdateInterval: Timer | null = null; + private readonly UPDATE_INTERVAL = 5000; // 5 seconds + + constructor( + private eventPublisher: EventPublisher, + private dataNormalizer: DataNormalizer + ) {} + + /** + * Initialize the market data service + */ + async initialize(): Promise { + console.log('🔄 Initializing Market Data Service...'); + + // Set up periodic data updates for demo purposes + this.startDataUpdates(); + + // Set service health + await this.eventPublisher.setServiceHealth('market-data-gateway', 'healthy'); + + console.log('✅ Market Data Service initialized'); + } + + /** + * Get latest market data for a symbol + */ + async getLatestData(symbol: string): Promise { + // First check cache + const cached = await this.eventPublisher.getCachedMarketData(symbol); + if (cached) { + return cached; + } + + // Fetch fresh data (using demo data for now) + const marketData = this.generateDemoData(symbol); + + // Cache the data + await this.eventPublisher.cacheMarketData(symbol, marketData, 60); + + // Publish market data event + const event: MarketDataEvent = { + type: 'MARKET_DATA', + data: marketData, + timestamp: new Date(), + }; + await this.eventPublisher.publishMarketData(event); + + return marketData; + } + + /** + * Get OHLCV data for a symbol + */ + async getOHLCV(symbol: string, interval: string, limit: number): Promise { + // Generate demo OHLCV data + const ohlcvData = this.generateDemoOHLCV(symbol, limit); + + // Cache the data + await this.eventPublisher.cacheMarketData(`ohlcv:${symbol}:${interval}`, ohlcvData, 300); + + return ohlcvData; + } + + /** + * Add WebSocket client for real-time updates + */ + addWebSocketClient(ws: any): void { + this.wsClients.add(ws); + } + + /** + * Remove WebSocket client + */ + removeWebSocketClient(ws: any): void { + this.wsClients.delete(ws); + + // Remove from all subscriptions + for (const [symbol, clients] of this.subscriptions) { + clients.delete(ws); + if (clients.size === 0) { + this.subscriptions.delete(symbol); + } + } + } + + /** + * Handle WebSocket messages + */ + handleWebSocketMessage(ws: any, data: any): void { + try { + const message = typeof data === 'string' ? JSON.parse(data) : data; + + switch (message.type) { + case 'subscribe': + this.subscribeToSymbol(ws, message.symbol); + break; + case 'unsubscribe': + this.unsubscribeFromSymbol(ws, message.symbol); + break; + default: + console.log('Unknown WebSocket message type:', message.type); + } + } catch (error) { + console.error('Error handling WebSocket message:', error); + } + } + + /** + * Subscribe WebSocket client to symbol updates + */ + private subscribeToSymbol(ws: any, symbol: string): void { + if (!this.subscriptions.has(symbol)) { + this.subscriptions.set(symbol, new Set()); + } + this.subscriptions.get(symbol)!.add(ws); + + ws.send(JSON.stringify({ + type: 'subscribed', + symbol, + timestamp: new Date().toISOString(), + })); + } + + /** + * Unsubscribe WebSocket client from symbol updates + */ + private unsubscribeFromSymbol(ws: any, symbol: string): void { + const clients = this.subscriptions.get(symbol); + if (clients) { + clients.delete(ws); + if (clients.size === 0) { + this.subscriptions.delete(symbol); + } + } + + ws.send(JSON.stringify({ + type: 'unsubscribed', + symbol, + timestamp: new Date().toISOString(), + })); + } + + /** + * Start periodic data updates for demo + */ + private startDataUpdates(): void { + this.dataUpdateInterval = setInterval(async () => { + const symbols = ['AAPL', 'GOOGL', 'MSFT', 'TSLA', 'AMZN']; + + for (const symbol of symbols) { + if (this.subscriptions.has(symbol)) { + const marketData = this.generateDemoData(symbol); + + // Send to subscribed WebSocket clients + const clients = this.subscriptions.get(symbol)!; + const message = JSON.stringify({ + type: 'market_data', + data: marketData, + timestamp: new Date().toISOString(), + }); + + for (const client of clients) { + try { + client.send(message); + } catch (error) { + console.error('Error sending WebSocket message:', error); + clients.delete(client); + } + } + + // Publish event + const event: MarketDataEvent = { + type: 'MARKET_DATA', + data: marketData, + timestamp: new Date(), + }; + await this.eventPublisher.publishMarketData(event); + } + } + }, this.UPDATE_INTERVAL); + } + + /** + * Generate demo market data + */ + private generateDemoData(symbol: string): MarketData { + const basePrice = this.getBasePrice(symbol); + const variation = (Math.random() - 0.5) * 0.02; // ±1% variation + const price = basePrice * (1 + variation); + + return { + symbol, + price: Math.round(price * 100) / 100, + bid: Math.round((price - 0.01) * 100) / 100, + ask: Math.round((price + 0.01) * 100) / 100, + volume: Math.floor(Math.random() * 1000000) + 100000, + timestamp: new Date(), + }; + } + + /** + * Generate demo OHLCV data + */ + private generateDemoOHLCV(symbol: string, limit: number): OHLCV[] { + const basePrice = this.getBasePrice(symbol); + const data: OHLCV[] = []; + let currentPrice = basePrice; + + for (let i = limit - 1; i >= 0; i--) { + const variation = (Math.random() - 0.5) * 0.05; // ±2.5% variation + const open = currentPrice; + const close = open * (1 + variation); + const high = Math.max(open, close) * (1 + Math.random() * 0.02); + const low = Math.min(open, close) * (1 - Math.random() * 0.02); + + data.push({ + symbol, + timestamp: new Date(Date.now() - i * 60000), // 1 minute intervals + open: Math.round(open * 100) / 100, + high: Math.round(high * 100) / 100, + low: Math.round(low * 100) / 100, + close: Math.round(close * 100) / 100, + volume: Math.floor(Math.random() * 50000) + 10000, + }); + + currentPrice = close; + } + + return data; + } + + /** + * Get base price for demo data + */ + private getBasePrice(symbol: string): number { + const prices: Record = { + 'AAPL': 175.50, + 'GOOGL': 142.30, + 'MSFT': 378.85, + 'TSLA': 208.75, + 'AMZN': 151.20, + 'NVDA': 465.80, + 'META': 298.45, + 'NFLX': 425.60, + }; + + return prices[symbol] || 100.00; + } + + /** + * Shutdown the service + */ + async shutdown(): Promise { + console.log('🔄 Shutting down Market Data Service...'); + + if (this.dataUpdateInterval) { + clearInterval(this.dataUpdateInterval); + } + + // Close all WebSocket connections + for (const client of this.wsClients) { + client.close(); + } + + await this.eventPublisher.setServiceHealth('market-data-gateway', 'unhealthy'); + await this.eventPublisher.disconnect(); + + console.log('✅ Market Data Service shutdown complete'); + } +} diff --git a/apps/core-services/market-data-gateway/tsconfig.json b/apps/core-services/market-data-gateway/tsconfig.json new file mode 100644 index 0000000..1d1f732 --- /dev/null +++ b/apps/core-services/market-data-gateway/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "types": ["bun-types"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/apps/core-services/risk-guardian/package.json b/apps/core-services/risk-guardian/package.json new file mode 100644 index 0000000..54d143b --- /dev/null +++ b/apps/core-services/risk-guardian/package.json @@ -0,0 +1,22 @@ +{ + "name": "risk-guardian", + "version": "1.0.0", + "description": "Real-time risk monitoring and controls service", + "main": "src/index.ts", + "scripts": { + "dev": "bun run --watch src/index.ts", + "start": "bun run src/index.ts", + "test": "echo 'No tests yet'" + }, + "dependencies": { + "hono": "^4.6.3", + "ioredis": "^5.4.1", + "@stock-bot/config": "workspace:*", + "@stock-bot/shared-types": "workspace:*", + "ws": "^8.18.0" + }, + "devDependencies": { + "bun-types": "^1.2.15", + "@types/ws": "^8.5.12" + } +} diff --git a/apps/intelligence-services/strategy-orchestrator/package.json b/apps/intelligence-services/strategy-orchestrator/package.json new file mode 100644 index 0000000..c1f085a --- /dev/null +++ b/apps/intelligence-services/strategy-orchestrator/package.json @@ -0,0 +1,24 @@ +{ + "name": "strategy-orchestrator", + "version": "1.0.0", + "description": "Trading strategy lifecycle management service", + "main": "src/index.ts", + "scripts": { + "dev": "bun run --watch src/index.ts", + "start": "bun run src/index.ts", + "test": "echo 'No tests yet'" + }, + "dependencies": { + "hono": "^4.6.3", + "ioredis": "^5.4.1", + "@stock-bot/config": "workspace:*", + "@stock-bot/shared-types": "workspace:*", + "ws": "^8.18.0", + "node-cron": "^3.0.3" + }, + "devDependencies": { + "bun-types": "^1.2.15", + "@types/ws": "^8.5.12", + "@types/node-cron": "^3.0.11" + } +} diff --git a/apps/interface-services/trading-dashboard-react/package.json b/apps/interface-services/trading-dashboard-react/package.json new file mode 100644 index 0000000..e69de29 diff --git a/apps/interface-services/trading-dashboard-react/src/App.tsx b/apps/interface-services/trading-dashboard-react/src/App.tsx new file mode 100644 index 0000000..e69de29 diff --git a/apps/interface-services/trading-dashboard-react/src/components/TradingDashboard.tsx b/apps/interface-services/trading-dashboard-react/src/components/TradingDashboard.tsx new file mode 100644 index 0000000..e69de29 diff --git a/apps/interface-services/trading-dashboard-react/src/index.css b/apps/interface-services/trading-dashboard-react/src/index.css new file mode 100644 index 0000000..e69de29 diff --git a/apps/interface-services/trading-dashboard/.gitignore b/apps/interface-services/trading-dashboard/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/apps/interface-services/trading-dashboard/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/apps/interface-services/trading-dashboard/README.md b/apps/interface-services/trading-dashboard/README.md new file mode 100644 index 0000000..da98444 --- /dev/null +++ b/apps/interface-services/trading-dashboard/README.md @@ -0,0 +1,54 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@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 + +## 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, + }, + }, +}) +``` + +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: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +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, + }, +}) +``` diff --git a/apps/interface-services/trading-dashboard/eslint.config.js b/apps/interface-services/trading-dashboard/eslint.config.js new file mode 100644 index 0000000..092408a --- /dev/null +++ b/apps/interface-services/trading-dashboard/eslint.config.js @@ -0,0 +1,28 @@ +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 }, + ], + }, + }, +) diff --git a/apps/interface-services/trading-dashboard/index.html b/apps/interface-services/trading-dashboard/index.html new file mode 100644 index 0000000..e4b78ea --- /dev/null +++ b/apps/interface-services/trading-dashboard/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/apps/interface-services/trading-dashboard/package.json b/apps/interface-services/trading-dashboard/package.json new file mode 100644 index 0000000..193affd --- /dev/null +++ b/apps/interface-services/trading-dashboard/package.json @@ -0,0 +1,32 @@ +{ + "name": "trading-dashboard", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@stock-bot/config": "workspace:*", + "@stock-bot/shared-types": "workspace:*", + "@tremor/react": "^3.18.7", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "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" + } +} diff --git a/apps/interface-services/trading-dashboard/public/vite.svg b/apps/interface-services/trading-dashboard/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/apps/interface-services/trading-dashboard/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/interface-services/trading-dashboard/src/App.css b/apps/interface-services/trading-dashboard/src/App.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/apps/interface-services/trading-dashboard/src/App.css @@ -0,0 +1,42 @@ +#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; +} diff --git a/apps/interface-services/trading-dashboard/src/App.tsx b/apps/interface-services/trading-dashboard/src/App.tsx new file mode 100644 index 0000000..9eebe6d --- /dev/null +++ b/apps/interface-services/trading-dashboard/src/App.tsx @@ -0,0 +1,8 @@ +import { TradingDashboard } from './components/TradingDashboard' +import './App.css' + +function App() { + return +} + +export default App diff --git a/apps/interface-services/trading-dashboard/src/assets/react.svg b/apps/interface-services/trading-dashboard/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/apps/interface-services/trading-dashboard/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/interface-services/trading-dashboard/src/components/TradingDashboard.tsx b/apps/interface-services/trading-dashboard/src/components/TradingDashboard.tsx new file mode 100644 index 0000000..9ea3d13 --- /dev/null +++ b/apps/interface-services/trading-dashboard/src/components/TradingDashboard.tsx @@ -0,0 +1,455 @@ +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({ + marketData: [], + ohlcvData: [], + serviceHealth: null, + lastUpdate: null, + }); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(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 ( +
+
+
+ Loading Trading Dashboard... + Connecting to market data services +
+
+
+ ); + } + + return ( +
+
+ {/* Header */} +
+ +
+ 🤖 Stock Bot Dashboard + Real-time market data monitoring +
+
+ + {data.lastUpdate && ( + + Last update: {data.lastUpdate.toLocaleTimeString()} + + )} +
+
+
+ + {error && ( + + Error: {error} + + )} + + {/* Top Metrics */} + + + Portfolio Value + ${totalValue.toLocaleString()} + + +8.2% + + + + + Service Status + + + {data.serviceHealth?.status || 'Unknown'} + + + + WebSocket: + + {wsConnected ? 'Connected' : 'Disconnected'} + + + + + + Active Symbols + {data.marketData.length} + + + + Daily P&L + +$2,450 + + +2.3% + + + + + {/* Main Content Tabs */} + + + Market Data + Portfolio + Charts + Performance + + + + {/* Market Data Tab */} + + + + Live Prices +
+ {data.marketData.map((item) => ( +
+
+ {item.symbol} + + Vol: {item.volume.toLocaleString()} + +
+
+ + ${item.price.toFixed(2)} + + + Bid: ${item.bid.toFixed(2)} | Ask: ${item.ask.toFixed(2)} + +
+
+ ))} +
+
+ + + Market Overview + `$${value.toFixed(2)}`} + /> + +
+
+ + {/* Portfolio Tab */} + + + + Portfolio Allocation + `$${value.toLocaleString()}`} + colors={["slate", "violet", "indigo", "rose", "cyan"]} + /> + + + + Holdings +
+ {portfolioData.map((item) => ( +
+ {item.name} +
+ + ${item.value.toLocaleString()} + + + {((item.value / totalValue) * 100).toFixed(1)}% + +
+
+ ))} +
+
+
+
+ + {/* Charts Tab */} + + + + AAPL Price Chart + `$${value.toFixed(2)}`} + yAxisWidth={60} + /> + + + + Volume Analysis + value.toLocaleString()} + yAxisWidth={80} + /> + + + + + {/* Performance Tab */} + + + + Weekly Performance + `$${value.toLocaleString()}`} + /> + + + + Performance Metrics +
+
+ Total Return + +12.5% +
+
+ Sharpe Ratio + 1.8 +
+
+ Max Drawdown + -5.2% +
+
+ Win Rate + 68% +
+
+ Avg Trade + +$245 +
+
+
+
+
+
+
+
+
+ ); +} diff --git a/apps/interface-services/trading-dashboard/src/components/TradingDashboard.v2.tsx b/apps/interface-services/trading-dashboard/src/components/TradingDashboard.v2.tsx new file mode 100644 index 0000000..e69de29 diff --git a/apps/interface-services/trading-dashboard/src/index.css b/apps/interface-services/trading-dashboard/src/index.css new file mode 100644 index 0000000..1051bc4 --- /dev/null +++ b/apps/interface-services/trading-dashboard/src/index.css @@ -0,0 +1,43 @@ +/* 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); +} diff --git a/apps/interface-services/trading-dashboard/src/index.ts b/apps/interface-services/trading-dashboard/src/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/interface-services/trading-dashboard/src/main.tsx b/apps/interface-services/trading-dashboard/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/apps/interface-services/trading-dashboard/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/apps/interface-services/trading-dashboard/src/simple.ts b/apps/interface-services/trading-dashboard/src/simple.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/interface-services/trading-dashboard/src/vite-env.d.ts b/apps/interface-services/trading-dashboard/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/apps/interface-services/trading-dashboard/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/interface-services/trading-dashboard/tsconfig.app.json b/apps/interface-services/trading-dashboard/tsconfig.app.json new file mode 100644 index 0000000..c9ccbd4 --- /dev/null +++ b/apps/interface-services/trading-dashboard/tsconfig.app.json @@ -0,0 +1,27 @@ +{ + "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 + }, + "include": ["src"] +} diff --git a/apps/interface-services/trading-dashboard/tsconfig.json b/apps/interface-services/trading-dashboard/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/apps/interface-services/trading-dashboard/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/apps/interface-services/trading-dashboard/tsconfig.node.json b/apps/interface-services/trading-dashboard/tsconfig.node.json new file mode 100644 index 0000000..9728af2 --- /dev/null +++ b/apps/interface-services/trading-dashboard/tsconfig.node.json @@ -0,0 +1,25 @@ +{ + "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"] +} diff --git a/apps/interface-services/trading-dashboard/vite.config.ts b/apps/interface-services/trading-dashboard/vite.config.ts new file mode 100644 index 0000000..8b0f57b --- /dev/null +++ b/apps/interface-services/trading-dashboard/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..751bc82 --- /dev/null +++ b/bun.lock @@ -0,0 +1,718 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "stock-bot", + "devDependencies": { + "@types/node": "^20.12.12", + "turbo": "^2.0.5", + "typescript": "^5.4.5", + }, + }, + "apps/core-services/market-data-gateway": { + "name": "market-data-gateway", + "version": "1.0.0", + "dependencies": { + "@stock-bot/config": "workspace:*", + "@stock-bot/shared-types": "workspace:*", + "hono": "^4.6.3", + "ioredis": "^5.4.1", + "ws": "^8.18.0", + }, + "devDependencies": { + "@types/ws": "^8.5.12", + "bun-types": "^1.2.15", + }, + }, + "apps/interface-services/trading-dashboard": { + "name": "trading-dashboard", + "version": "0.0.0", + "dependencies": { + "@stock-bot/config": "workspace:*", + "@stock-bot/shared-types": "workspace:*", + "@tremor/react": "^3.18.7", + "react": "^19.1.0", + "react-dom": "^19.1.0", + }, + "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", + }, + }, + "packages/config": { + "name": "@stock-bot/config", + "version": "1.0.0", + "dependencies": { + "@stock-bot/shared-types": "workspace:*", + "dotenv": "^16.4.5", + }, + "devDependencies": { + "@types/node": "^20.12.12", + "typescript": "^5.4.5", + }, + }, + "packages/shared-types": { + "name": "@stock-bot/shared-types", + "version": "1.0.0", + "devDependencies": { + "typescript": "^5.4.5", + }, + }, + }, + "packages": { + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], + + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], + + "@babel/compat-data": ["@babel/compat-data@7.27.3", "", {}, "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw=="], + + "@babel/core": ["@babel/core@7.27.4", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.4", "@babel/parser": "^7.27.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.27.4", "@babel/types": "^7.27.3", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g=="], + + "@babel/generator": ["@babel/generator@7.27.3", "", { "dependencies": { "@babel/parser": "^7.27.3", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.27.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.27.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.27.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.3" } }, "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ=="], + + "@babel/parser": ["@babel/parser@7.27.4", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g=="], + + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], + + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], + + "@babel/runtime": ["@babel/runtime@7.27.4", "", {}, "sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA=="], + + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], + + "@babel/traverse": ["@babel/traverse@7.27.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/parser": "^7.27.4", "@babel/template": "^7.27.2", "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA=="], + + "@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.5", "", { "os": "android", "cpu": "arm" }, "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.5", "", { "os": "android", "cpu": "arm64" }, "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.5", "", { "os": "android", "cpu": "x64" }, "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.5", "", { "os": "linux", "cpu": "arm" }, "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.5", "", { "os": "linux", "cpu": "x64" }, "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.5", "", { "os": "none", "cpu": "arm64" }, "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.5", "", { "os": "none", "cpu": "x64" }, "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.5", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.5", "", { "os": "win32", "cpu": "x64" }, "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint/config-array": ["@eslint/config-array@0.20.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.2.2", "", {}, "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg=="], + + "@eslint/core": ["@eslint/core@0.14.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], + + "@eslint/js": ["@eslint/js@9.28.0", "", {}, "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.1", "", { "dependencies": { "@eslint/core": "^0.14.0", "levn": "^0.4.1" } }, "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w=="], + + "@floating-ui/core": ["@floating-ui/core@1.7.0", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA=="], + + "@floating-ui/dom": ["@floating-ui/dom@1.7.0", "", { "dependencies": { "@floating-ui/core": "^1.7.0", "@floating-ui/utils": "^0.2.9" } }, "sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg=="], + + "@floating-ui/react": ["@floating-ui/react@0.19.2", "", { "dependencies": { "@floating-ui/react-dom": "^1.3.0", "aria-hidden": "^1.1.3", "tabbable": "^6.0.1" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-JyNk4A0Ezirq8FlXECvRtQOX/iBe5Ize0W/pLkrZjfHW9GUV7Xnq6zm6fyZuQzaHHqEnVizmvlA96e1/CkZv+w=="], + + "@floating-ui/react-dom": ["@floating-ui/react-dom@1.3.0", "", { "dependencies": { "@floating-ui/dom": "^1.2.1" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g=="], + + "@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="], + + "@headlessui/react": ["@headlessui/react@2.2.0", "", { "dependencies": { "@floating-ui/react": "^0.26.16", "@react-aria/focus": "^3.17.1", "@react-aria/interactions": "^3.21.3", "@tanstack/react-virtual": "^3.8.1" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-RzCEg+LXsuI7mHiSomsu/gBJSjpupm6A1qIZ5sWjd7JhARNlMiSA4kKfJpCKwU9tE+zMRterhhrP74PvfJrpXQ=="], + + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + + "@ioredis/commands": ["@ioredis/commands@1.2.0", "", {}, "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@react-aria/focus": ["@react-aria/focus@3.20.3", "", { "dependencies": { "@react-aria/interactions": "^3.25.1", "@react-aria/utils": "^3.29.0", "@react-types/shared": "^3.29.1", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-rR5uZUMSY4xLHmpK/I8bP1V6vUNHFo33gTvrvNUsAKKqvMfa7R2nu5A6v97dr5g6tVH6xzpdkPsOJCWh90H2cw=="], + + "@react-aria/interactions": ["@react-aria/interactions@3.25.1", "", { "dependencies": { "@react-aria/ssr": "^3.9.8", "@react-aria/utils": "^3.29.0", "@react-stately/flags": "^3.1.1", "@react-types/shared": "^3.29.1", "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-ntLrlgqkmZupbbjekz3fE/n3eQH2vhncx8gUp0+N+GttKWevx7jos11JUBjnJwb1RSOPgRUFcrluOqBp0VgcfQ=="], + + "@react-aria/ssr": ["@react-aria/ssr@3.9.8", "", { "dependencies": { "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-lQDE/c9uTfBSDOjaZUJS8xP2jCKVk4zjQeIlCH90xaLhHDgbpCdns3xvFpJJujfj3nI4Ll9K7A+ONUBDCASOuw=="], + + "@react-aria/utils": ["@react-aria/utils@3.29.0", "", { "dependencies": { "@react-aria/ssr": "^3.9.8", "@react-stately/flags": "^3.1.1", "@react-stately/utils": "^3.10.6", "@react-types/shared": "^3.29.1", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-jSOrZimCuT1iKNVlhjIxDkAhgF7HSp3pqyT6qjg/ZoA0wfqCi/okmrMPiWSAKBnkgX93N8GYTLT3CIEO6WZe9Q=="], + + "@react-stately/flags": ["@react-stately/flags@3.1.1", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-XPR5gi5LfrPdhxZzdIlJDz/B5cBf63l4q6/AzNqVWFKgd0QqY5LvWJftXkklaIUpKSJkIKQb8dphuZXDtkWNqg=="], + + "@react-stately/utils": ["@react-stately/utils@3.10.6", "", { "dependencies": { "@swc/helpers": "^0.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-O76ip4InfTTzAJrg8OaZxKU4vvjMDOpfA/PGNOytiXwBbkct2ZeZwaimJ8Bt9W1bj5VsZ81/o/tW4BacbdDOMA=="], + + "@react-types/shared": ["@react-types/shared@3.29.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "sha512-KtM+cDf2CXoUX439rfEhbnEdAgFZX20UP2A35ypNIawR7/PFFPjQDWyA2EnClCcW/dLWJDEPX2U8+EJff8xqmQ=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.9", "", {}, "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.41.1", "", { "os": "android", "cpu": "arm" }, "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.41.1", "", { "os": "android", "cpu": "arm64" }, "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.41.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.41.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.41.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.41.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.41.1", "", { "os": "linux", "cpu": "arm" }, "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.41.1", "", { "os": "linux", "cpu": "arm" }, "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.41.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.41.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg=="], + + "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.41.1", "", { "os": "linux", "cpu": "none" }, "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw=="], + + "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.41.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.41.1", "", { "os": "linux", "cpu": "none" }, "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.41.1", "", { "os": "linux", "cpu": "none" }, "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.41.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.41.1", "", { "os": "linux", "cpu": "x64" }, "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.41.1", "", { "os": "linux", "cpu": "x64" }, "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.41.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.41.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.41.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw=="], + + "@stock-bot/config": ["@stock-bot/config@workspace:packages/config"], + + "@stock-bot/shared-types": ["@stock-bot/shared-types@workspace:packages/shared-types"], + + "@swc/helpers": ["@swc/helpers@0.5.17", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A=="], + + "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.9", "", { "dependencies": { "@tanstack/virtual-core": "3.13.9" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-SPWC8kwG/dWBf7Py7cfheAPOxuvIv4fFQ54PdmYbg7CpXfsKxkucak43Q0qKsxVthhUJQ1A7CIMAIplq4BjVwA=="], + + "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.9", "", {}, "sha512-3jztt0jpaoJO5TARe2WIHC1UQC3VMLAFUW5mmMo0yrkwtDB2AQP0+sh10BVUpWrnvHjSLvzFizydtEGLCJKFoQ=="], + + "@tremor/react": ["@tremor/react@3.18.7", "", { "dependencies": { "@floating-ui/react": "^0.19.2", "@headlessui/react": "2.2.0", "date-fns": "^3.6.0", "react-day-picker": "^8.10.1", "react-transition-state": "^2.1.2", "recharts": "^2.13.3", "tailwind-merge": "^2.5.2" }, "peerDependencies": { "react": "^18.0.0", "react-dom": ">=16.6.0" } }, "sha512-nmqvf/1m0GB4LXc7v2ftdfSLoZhy5WLrhV6HNf0SOriE6/l8WkYeWuhQq8QsBjRi94mUIKLJ/VC3/Y/pj6VubQ=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="], + + "@types/d3-array": ["@types/d3-array@3.2.1", "", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="], + + "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], + + "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], + + "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], + + "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], + + "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], + + "@types/d3-shape": ["@types/d3-shape@3.1.7", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg=="], + + "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], + + "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], + + "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@20.17.57", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ=="], + + "@types/react": ["@types/react@19.1.6", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-JeG0rEWak0N6Itr6QUx+X60uQmN+5t3j9r/OVDtWzFXKaj6kD1BwJzOksD0FF6iWxZlbE1kB0q9vtnU2ekqa1Q=="], + + "@types/react-dom": ["@types/react-dom@19.1.5", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg=="], + + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.33.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.33.0", "@typescript-eslint/type-utils": "8.33.0", "@typescript-eslint/utils": "8.33.0", "@typescript-eslint/visitor-keys": "8.33.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.33.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.33.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.33.0", "@typescript-eslint/types": "8.33.0", "@typescript-eslint/typescript-estree": "8.33.0", "@typescript-eslint/visitor-keys": "8.33.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.33.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.33.0", "@typescript-eslint/types": "^8.33.0", "debug": "^4.3.4" } }, "sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.33.0", "", { "dependencies": { "@typescript-eslint/types": "8.33.0", "@typescript-eslint/visitor-keys": "8.33.0" } }, "sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.33.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.33.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.33.0", "@typescript-eslint/utils": "8.33.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.33.0", "", {}, "sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.33.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.33.0", "@typescript-eslint/tsconfig-utils": "8.33.0", "@typescript-eslint/types": "8.33.0", "@typescript-eslint/visitor-keys": "8.33.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.33.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.33.0", "@typescript-eslint/types": "8.33.0", "@typescript-eslint/typescript-estree": "8.33.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.33.0", "", { "dependencies": { "@typescript-eslint/types": "8.33.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ=="], + + "@vitejs/plugin-react": ["@vitejs/plugin-react@4.5.0", "", { "dependencies": { "@babel/core": "^7.26.10", "@babel/plugin-transform-react-jsx-self": "^7.25.9", "@babel/plugin-transform-react-jsx-source": "^7.25.9", "@rolldown/pluginutils": "1.0.0-beta.9", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg=="], + + "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.25.0", "", { "dependencies": { "caniuse-lite": "^1.0.30001718", "electron-to-chromium": "^1.5.160", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA=="], + + "bun-types": ["bun-types@1.2.15", "", { "dependencies": { "@types/node": "*" } }, "sha512-NarRIaS+iOaQU1JPfyKhZm4AsUOrwUOqRNHY0XxI8GI8jYxiLXLcdjYMG9UKS+fwWasc1uw1htV9AX24dD+p4w=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001720", "", {}, "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], + + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + + "d3-format": ["d3-format@3.1.0", "", {}, "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], + + "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], + + "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], + + "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], + + "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], + + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + + "date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], + + "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + + "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + + "dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="], + + "dotenv": ["dotenv@16.5.0", "", {}, "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.161", "", {}, "sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA=="], + + "esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.28.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.20.0", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.28.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ=="], + + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="], + + "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.20", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA=="], + + "eslint-scope": ["eslint-scope@8.3.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="], + + "espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-equals": ["fast-equals@5.2.2", "", {}, "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "fdir": ["fdir@6.4.5", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@16.2.0", "", {}, "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "hono": ["hono@4.7.11", "", {}, "sha512-rv0JMwC0KALbbmwJDEnxvQCeJh+xbS3KEWW5PC9cMJ08Ur9xgatI0HmtgYZfOdOSOeYsp5LO2cOhdI8cLEbDEQ=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], + + "ioredis": ["ioredis@5.6.1", "", { "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + + "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], + + "lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "market-data-gateway": ["market-data-gateway@workspace:apps/core-services/market-data-gateway"], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], + + "postcss": ["postcss@8.5.4", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="], + + "react-day-picker": ["react-day-picker@8.10.1", "", { "peerDependencies": { "date-fns": "^2.28.0 || ^3.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA=="], + + "react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="], + + "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + + "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], + + "react-smooth": ["react-smooth@4.0.4", "", { "dependencies": { "fast-equals": "^5.0.1", "prop-types": "^15.8.1", "react-transition-group": "^4.4.5" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q=="], + + "react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="], + + "react-transition-state": ["react-transition-state@2.3.1", "", { "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-Z48el73x+7HUEM131dof9YpcQ5IlM4xB+pKWH/lX3FhxGfQaNTZa16zb7pWkC/y5btTZzXfCtglIJEGc57giOw=="], + + "recharts": ["recharts@2.15.3", "", { "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.21", "react-is": "^18.3.1", "react-smooth": "^4.0.4", "recharts-scale": "^0.4.4", "tiny-invariant": "^1.3.1", "victory-vendor": "^36.6.8" }, "peerDependencies": { "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-EdOPzTwcFSuqtvkDoaM5ws/Km1+WTAO2eizL7rqiG0V2UVhTnz0m7J2i0CjVPUCdEkZImaWvXLbZDS2H5t6GFQ=="], + + "recharts-scale": ["recharts-scale@0.4.5", "", { "dependencies": { "decimal.js-light": "^2.4.1" } }, "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w=="], + + "redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="], + + "redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rollup": ["rollup@4.41.1", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.41.1", "@rollup/rollup-android-arm64": "4.41.1", "@rollup/rollup-darwin-arm64": "4.41.1", "@rollup/rollup-darwin-x64": "4.41.1", "@rollup/rollup-freebsd-arm64": "4.41.1", "@rollup/rollup-freebsd-x64": "4.41.1", "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", "@rollup/rollup-linux-arm-musleabihf": "4.41.1", "@rollup/rollup-linux-arm64-gnu": "4.41.1", "@rollup/rollup-linux-arm64-musl": "4.41.1", "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-musl": "4.41.1", "@rollup/rollup-linux-s390x-gnu": "4.41.1", "@rollup/rollup-linux-x64-gnu": "4.41.1", "@rollup/rollup-linux-x64-musl": "4.41.1", "@rollup/rollup-win32-arm64-msvc": "4.41.1", "@rollup/rollup-win32-ia32-msvc": "4.41.1", "@rollup/rollup-win32-x64-msvc": "4.41.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="], + + "tailwind-merge": ["tailwind-merge@2.6.0", "", {}, "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA=="], + + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + + "tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "trading-dashboard": ["trading-dashboard@workspace:apps/interface-services/trading-dashboard"], + + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "turbo": ["turbo@2.5.4", "", { "optionalDependencies": { "turbo-darwin-64": "2.5.4", "turbo-darwin-arm64": "2.5.4", "turbo-linux-64": "2.5.4", "turbo-linux-arm64": "2.5.4", "turbo-windows-64": "2.5.4", "turbo-windows-arm64": "2.5.4" }, "bin": { "turbo": "bin/turbo" } }, "sha512-kc8ZibdRcuWUG1pbYSBFWqmIjynlD8Lp7IB6U3vIzvOv9VG+6Sp8bzyeBWE3Oi8XV5KsQrznyRTBPvrf99E4mA=="], + + "turbo-darwin-64": ["turbo-darwin-64@2.5.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ah6YnH2dErojhFooxEzmvsoZQTMImaruZhFPfMKPBq8sb+hALRdvBNLqfc8NWlZq576FkfRZ/MSi4SHvVFT9PQ=="], + + "turbo-darwin-arm64": ["turbo-darwin-arm64@2.5.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2+Nx6LAyuXw2MdXb7pxqle3MYignLvS7OwtsP9SgtSBaMlnNlxl9BovzqdYAgkUW3AsYiQMJ/wBRb7d+xemM5A=="], + + "turbo-linux-64": ["turbo-linux-64@2.5.4", "", { "os": "linux", "cpu": "x64" }, "sha512-5May2kjWbc8w4XxswGAl74GZ5eM4Gr6IiroqdLhXeXyfvWEdm2mFYCSWOzz0/z5cAgqyGidF1jt1qzUR8hTmOA=="], + + "turbo-linux-arm64": ["turbo-linux-arm64@2.5.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-/2yqFaS3TbfxV3P5yG2JUI79P7OUQKOUvAnx4MV9Bdz6jqHsHwc9WZPpO4QseQm+NvmgY6ICORnoVPODxGUiJg=="], + + "turbo-windows-64": ["turbo-windows-64@2.5.4", "", { "os": "win32", "cpu": "x64" }, "sha512-EQUO4SmaCDhO6zYohxIjJpOKRN3wlfU7jMAj3CgcyTPvQR/UFLEKAYHqJOnJtymbQmiiM/ihX6c6W6Uq0yC7mA=="], + + "turbo-windows-arm64": ["turbo-windows-arm64@2.5.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-oQ8RrK1VS8lrxkLriotFq+PiF7iiGgkZtfLKF4DDKsmdbPo0O9R2mQxm7jHLuXraRCuIQDWMIw6dpcr7Iykf4A=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "typescript-eslint": ["typescript-eslint@8.33.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.33.0", "@typescript-eslint/parser": "8.33.0", "@typescript-eslint/utils": "8.33.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ=="], + + "undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="], + + "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="], + + "vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "ws": ["ws@8.18.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "@headlessui/react/@floating-ui/react": ["@floating-ui/react@0.26.28", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.2", "@floating-ui/utils": "^0.2.8", "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw=="], + + "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="], + + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@typescript-eslint/typescript-estree/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + + "@headlessui/react/@floating-ui/react/@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.2", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + } +} diff --git a/database/postgres/init/01-init-schemas.sql b/database/postgres/init/01-init-schemas.sql new file mode 100644 index 0000000..42821fb --- /dev/null +++ b/database/postgres/init/01-init-schemas.sql @@ -0,0 +1,20 @@ +-- Trading Bot Database Schema Initialization + +-- Create schemas +CREATE SCHEMA IF NOT EXISTS trading; +CREATE SCHEMA IF NOT EXISTS strategy; +CREATE SCHEMA IF NOT EXISTS risk; +CREATE SCHEMA IF NOT EXISTS audit; + +-- Set search path for the database +ALTER DATABASE trading_bot SET search_path TO trading, strategy, risk, audit, public; + +-- Create extensions +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "btree_gin"; +CREATE EXTENSION IF NOT EXISTS "pg_stat_statements"; + +-- Create a read-only user for analytics +CREATE USER trading_reader WITH PASSWORD 'reader_pass_dev'; +GRANT CONNECT ON DATABASE trading_bot TO trading_reader; +GRANT USAGE ON SCHEMA trading, strategy, risk, audit TO trading_reader; diff --git a/database/postgres/init/02-trading-tables.sql b/database/postgres/init/02-trading-tables.sql new file mode 100644 index 0000000..41d18f3 --- /dev/null +++ b/database/postgres/init/02-trading-tables.sql @@ -0,0 +1,93 @@ +-- Core trading tables + +-- Symbols and instruments +CREATE TABLE trading.symbols ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + symbol VARCHAR(20) NOT NULL UNIQUE, + name VARCHAR(255), + exchange VARCHAR(50), + asset_type VARCHAR(20) DEFAULT 'equity', + sector VARCHAR(100), + is_active BOOLEAN DEFAULT true, + metadata JSONB DEFAULT '{}', + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Orders +CREATE TABLE trading.orders ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + symbol_id UUID REFERENCES trading.symbols(id), + strategy_id UUID, + order_type VARCHAR(20) NOT NULL, -- 'market', 'limit', 'stop', etc. + side VARCHAR(10) NOT NULL CHECK (side IN ('buy', 'sell')), + quantity DECIMAL(18,8) NOT NULL CHECK (quantity > 0), + price DECIMAL(18,8), + stop_price DECIMAL(18,8), + status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'submitted', 'filled', 'cancelled', 'rejected')), + broker_order_id VARCHAR(100), + filled_quantity DECIMAL(18,8) DEFAULT 0, + avg_fill_price DECIMAL(18,8), + commission DECIMAL(18,8) DEFAULT 0, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + CONSTRAINT valid_prices CHECK ( + (order_type = 'market') OR + (order_type = 'limit' AND price IS NOT NULL) OR + (order_type = 'stop' AND stop_price IS NOT NULL) + ) +); + +-- Positions +CREATE TABLE trading.positions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + symbol_id UUID REFERENCES trading.symbols(id), + strategy_id UUID, + quantity DECIMAL(18,8) NOT NULL, + avg_cost DECIMAL(18,8) NOT NULL, + market_value DECIMAL(18,8), + unrealized_pnl DECIMAL(18,8), + realized_pnl DECIMAL(18,8) DEFAULT 0, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(symbol_id, strategy_id) +); + +-- Executions/Fills +CREATE TABLE trading.executions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + order_id UUID REFERENCES trading.orders(id), + symbol_id UUID REFERENCES trading.symbols(id), + side VARCHAR(10) NOT NULL CHECK (side IN ('buy', 'sell')), + quantity DECIMAL(18,8) NOT NULL, + price DECIMAL(18,8) NOT NULL, + commission DECIMAL(18,8) DEFAULT 0, + broker_execution_id VARCHAR(100), + executed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Accounts/Portfolios +CREATE TABLE trading.accounts ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + name VARCHAR(255) NOT NULL, + account_type VARCHAR(50) DEFAULT 'paper', -- 'paper', 'live' + broker VARCHAR(50), + cash_balance DECIMAL(18,2) DEFAULT 0, + buying_power DECIMAL(18,2) DEFAULT 0, + total_value DECIMAL(18,2) DEFAULT 0, + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Create indexes for performance +CREATE INDEX idx_orders_symbol_created ON trading.orders(symbol_id, created_at); +CREATE INDEX idx_orders_status ON trading.orders(status); +CREATE INDEX idx_orders_strategy ON trading.orders(strategy_id); +CREATE INDEX idx_positions_strategy ON trading.positions(strategy_id); +CREATE INDEX idx_executions_order ON trading.executions(order_id); +CREATE INDEX idx_executions_symbol_time ON trading.executions(symbol_id, executed_at); + +-- Grant permissions to reader +GRANT SELECT ON ALL TABLES IN SCHEMA trading TO trading_reader; diff --git a/database/postgres/init/03-strategy-risk-tables.sql b/database/postgres/init/03-strategy-risk-tables.sql new file mode 100644 index 0000000..62b88d2 --- /dev/null +++ b/database/postgres/init/03-strategy-risk-tables.sql @@ -0,0 +1,105 @@ +-- Strategy and Risk Management Tables + +-- Strategies +CREATE TABLE strategy.strategies ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + name VARCHAR(255) NOT NULL, + description TEXT, + version VARCHAR(20) DEFAULT '1.0.0', + config JSONB DEFAULT '{}', + parameters JSONB DEFAULT '{}', + is_active BOOLEAN DEFAULT false, + is_enabled BOOLEAN DEFAULT true, + created_by VARCHAR(255), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Strategy executions/runs +CREATE TABLE strategy.executions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + strategy_id UUID REFERENCES strategy.strategies(id), + status VARCHAR(20) DEFAULT 'running' CHECK (status IN ('running', 'stopped', 'error', 'completed')), + started_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + stopped_at TIMESTAMP WITH TIME ZONE, + error_message TEXT, + execution_stats JSONB DEFAULT '{}', + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Strategy signals +CREATE TABLE strategy.signals ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + strategy_id UUID REFERENCES strategy.strategies(id), + symbol_id UUID REFERENCES trading.symbols(id), + signal_type VARCHAR(20) NOT NULL CHECK (signal_type IN ('buy', 'sell', 'hold')), + strength DECIMAL(3,2) CHECK (strength >= 0 AND strength <= 1), -- 0.0 to 1.0 + confidence DECIMAL(3,2) CHECK (confidence >= 0 AND confidence <= 1), + target_price DECIMAL(18,8), + stop_loss DECIMAL(18,8), + take_profit DECIMAL(18,8), + metadata JSONB DEFAULT '{}', + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Risk limits +CREATE TABLE risk.limits ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + strategy_id UUID REFERENCES strategy.strategies(id), + account_id UUID REFERENCES trading.accounts(id), + limit_type VARCHAR(50) NOT NULL, -- 'max_position_size', 'max_daily_loss', 'max_drawdown', etc. + limit_value DECIMAL(18,8) NOT NULL, + current_value DECIMAL(18,8) DEFAULT 0, + threshold_warning DECIMAL(18,8), -- Warning at X% of limit + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Risk events/alerts +CREATE TABLE risk.events ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + limit_id UUID REFERENCES risk.limits(id), + strategy_id UUID, + event_type VARCHAR(50) NOT NULL, -- 'warning', 'breach', 'resolved' + severity VARCHAR(20) DEFAULT 'medium' CHECK (severity IN ('low', 'medium', 'high', 'critical')), + message TEXT NOT NULL, + current_value DECIMAL(18,8), + limit_value DECIMAL(18,8), + metadata JSONB DEFAULT '{}', + acknowledged BOOLEAN DEFAULT false, + acknowledged_by VARCHAR(255), + acknowledged_at TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Performance tracking +CREATE TABLE strategy.performance ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + strategy_id UUID REFERENCES strategy.strategies(id), + date DATE NOT NULL, + total_return DECIMAL(10,4), + daily_return DECIMAL(10,4), + sharpe_ratio DECIMAL(10,4), + max_drawdown DECIMAL(10,4), + win_rate DECIMAL(5,4), + profit_factor DECIMAL(10,4), + total_trades INTEGER DEFAULT 0, + winning_trades INTEGER DEFAULT 0, + losing_trades INTEGER DEFAULT 0, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(strategy_id, date) +); + +-- Create indexes +CREATE INDEX idx_strategies_active ON strategy.strategies(is_active, is_enabled); +CREATE INDEX idx_executions_strategy ON strategy.executions(strategy_id); +CREATE INDEX idx_signals_strategy_time ON strategy.signals(strategy_id, created_at); +CREATE INDEX idx_signals_symbol ON strategy.signals(symbol_id); +CREATE INDEX idx_limits_strategy ON risk.limits(strategy_id); +CREATE INDEX idx_risk_events_severity ON risk.events(severity, created_at); +CREATE INDEX idx_performance_strategy_date ON strategy.performance(strategy_id, date); + +-- Grant permissions +GRANT SELECT ON ALL TABLES IN SCHEMA strategy TO trading_reader; +GRANT SELECT ON ALL TABLES IN SCHEMA risk TO trading_reader; diff --git a/database/postgres/init/04-audit-tables.sql b/database/postgres/init/04-audit-tables.sql new file mode 100644 index 0000000..3f1d073 --- /dev/null +++ b/database/postgres/init/04-audit-tables.sql @@ -0,0 +1,59 @@ +-- Audit and System Tables + +-- System events audit +CREATE TABLE audit.system_events ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + service_name VARCHAR(100) NOT NULL, + event_type VARCHAR(50) NOT NULL, + event_data JSONB DEFAULT '{}', + user_id VARCHAR(255), + ip_address INET, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Trading events audit +CREATE TABLE audit.trading_events ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + event_type VARCHAR(50) NOT NULL, -- 'order_created', 'order_filled', 'position_opened', etc. + entity_type VARCHAR(50) NOT NULL, -- 'order', 'position', 'execution' + entity_id UUID NOT NULL, + old_values JSONB, + new_values JSONB, + changed_by VARCHAR(255), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Service health monitoring +CREATE TABLE audit.service_health ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + service_name VARCHAR(100) NOT NULL, + status VARCHAR(20) NOT NULL CHECK (status IN ('healthy', 'unhealthy', 'degraded')), + version VARCHAR(50), + uptime_seconds INTEGER, + memory_usage_mb INTEGER, + cpu_usage_percent DECIMAL(5,2), + last_health_check TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + metadata JSONB DEFAULT '{}', + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Configuration changes +CREATE TABLE audit.config_changes ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + config_key VARCHAR(255) NOT NULL, + old_value TEXT, + new_value TEXT, + changed_by VARCHAR(255), + reason TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- Create indexes +CREATE INDEX idx_system_events_service_time ON audit.system_events(service_name, created_at); +CREATE INDEX idx_trading_events_type_time ON audit.trading_events(event_type, created_at); +CREATE INDEX idx_trading_events_entity ON audit.trading_events(entity_type, entity_id); +CREATE INDEX idx_service_health_name_time ON audit.service_health(service_name, created_at); +CREATE INDEX idx_config_changes_key_time ON audit.config_changes(config_key, created_at); + +-- Grant permissions +GRANT SELECT ON ALL TABLES IN SCHEMA audit TO trading_reader; diff --git a/database/postgres/init/05-initial-data.sql b/database/postgres/init/05-initial-data.sql new file mode 100644 index 0000000..47bfc35 --- /dev/null +++ b/database/postgres/init/05-initial-data.sql @@ -0,0 +1,55 @@ +-- Insert initial reference data + +-- Insert common symbols +INSERT INTO trading.symbols (symbol, name, exchange, asset_type, sector) VALUES +('AAPL', 'Apple Inc.', 'NASDAQ', 'equity', 'Technology'), +('GOOGL', 'Alphabet Inc.', 'NASDAQ', 'equity', 'Technology'), +('MSFT', 'Microsoft Corporation', 'NASDAQ', 'equity', 'Technology'), +('AMZN', 'Amazon.com Inc.', 'NASDAQ', 'equity', 'Consumer Discretionary'), +('TSLA', 'Tesla Inc.', 'NASDAQ', 'equity', 'Consumer Discretionary'), +('NVDA', 'NVIDIA Corporation', 'NASDAQ', 'equity', 'Technology'), +('META', 'Meta Platforms Inc.', 'NASDAQ', 'equity', 'Technology'), +('NFLX', 'Netflix Inc.', 'NASDAQ', 'equity', 'Communication Services'), +('SPY', 'SPDR S&P 500 ETF Trust', 'NYSE', 'etf', 'Broad Market'), +('QQQ', 'Invesco QQQ Trust', 'NASDAQ', 'etf', 'Technology'), +('BTC-USD', 'Bitcoin USD', 'CRYPTO', 'cryptocurrency', 'Digital Assets'), +('ETH-USD', 'Ethereum USD', 'CRYPTO', 'cryptocurrency', 'Digital Assets'); + +-- Insert default trading account +INSERT INTO trading.accounts (name, account_type, broker, cash_balance, buying_power, total_value) VALUES +('Demo Account', 'paper', 'demo', 100000.00, 100000.00, 100000.00); + +-- Insert demo strategy +INSERT INTO strategy.strategies (name, description, config, parameters, is_active) VALUES +('Demo Mean Reversion', 'Simple mean reversion strategy for demonstration', + '{"timeframe": "1h", "lookback_period": 20}', + '{"rsi_oversold": 30, "rsi_overbought": 70, "position_size": 0.1}', + false); + +-- Insert basic risk limits +INSERT INTO risk.limits (strategy_id, limit_type, limit_value, threshold_warning) +SELECT s.id, 'max_position_size', 10000.00, 8000.00 +FROM strategy.strategies s +WHERE s.name = 'Demo Mean Reversion'; + +INSERT INTO risk.limits (strategy_id, limit_type, limit_value, threshold_warning) +SELECT s.id, 'max_daily_loss', 5000.00, 4000.00 +FROM strategy.strategies s +WHERE s.name = 'Demo Mean Reversion'; + +-- Create updated_at trigger function +CREATE OR REPLACE FUNCTION update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ language 'plpgsql'; + +-- Apply updated_at triggers +CREATE TRIGGER update_symbols_updated_at BEFORE UPDATE ON trading.symbols FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); +CREATE TRIGGER update_orders_updated_at BEFORE UPDATE ON trading.orders FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); +CREATE TRIGGER update_positions_updated_at BEFORE UPDATE ON trading.positions FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); +CREATE TRIGGER update_accounts_updated_at BEFORE UPDATE ON trading.accounts FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); +CREATE TRIGGER update_strategies_updated_at BEFORE UPDATE ON strategy.strategies FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); +CREATE TRIGGER update_limits_updated_at BEFORE UPDATE ON risk.limits FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..3930443 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,34 @@ +# Development override for Docker Compose +# Use: docker-compose -f docker-compose.yml -f docker-compose.dev.yml up + +version: '3.8' + +services: + # Development overrides for faster feedback + dragonfly: + command: > + dragonfly + --logtostderr + --cache_mode=true + --maxmemory=512mb + --save_schedule="" + --bind=0.0.0.0 + ports: + - "6379:6379" + + postgres: + environment: + POSTGRES_DB: trading_bot_dev + POSTGRES_USER: dev_user + POSTGRES_PASSWORD: dev_pass + ports: + - "5432:5432" + + # Disable monitoring in development to save resources + prometheus: + profiles: + - monitoring + + grafana: + profiles: + - monitoring diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8882871 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,151 @@ +services: + # Dragonfly - Redis replacement for caching and events + dragonfly: + image: docker.dragonflydb.io/dragonflydb/dragonfly:latest + container_name: trading-bot-dragonfly + ports: + - "6379:6379" + command: + - dragonfly + - --logtostderr + - --cache_mode=true + - --maxmemory=2gb + - --proactor_threads=8 + - --bind=0.0.0.0 + volumes: + - dragonfly_data:/data + restart: unless-stopped + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - trading-bot-network + + # PostgreSQL - Operational data (orders, positions, strategies) + postgres: + image: postgres:16-alpine + container_name: trading-bot-postgres + environment: + POSTGRES_DB: trading_bot + POSTGRES_USER: trading_user + POSTGRES_PASSWORD: trading_pass_dev + POSTGRES_INITDB_ARGS: "--encoding=UTF-8" + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./database/postgres/init:/docker-entrypoint-initdb.d + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U trading_user -d trading_bot"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - trading-bot-network + + # QuestDB - Time-series data (OHLCV, indicators, performance) + questdb: + image: questdb/questdb:latest + container_name: trading-bot-questdb + ports: + - "9000:9000" # Web console + - "8812:8812" # PostgreSQL wire protocol + - "9009:9009" # InfluxDB line protocol + volumes: + - questdb_data:/var/lib/questdb + environment: + - QDB_TELEMETRY_ENABLED=false + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/status"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - trading-bot-network + + # Redis Insight - GUI for Dragonfly debugging + redis-insight: + image: redislabs/redisinsight:latest + container_name: trading-bot-redis-insight + ports: + - "8001:8001" + environment: + - REDIS_HOSTS=local:dragonfly:6379 + depends_on: + - dragonfly + restart: unless-stopped + networks: + - trading-bot-network + + # PgAdmin - PostgreSQL GUI + pgadmin: + image: dpage/pgadmin4:latest + container_name: trading-bot-pgadmin + environment: + PGADMIN_DEFAULT_EMAIL: admin@example.com + PGADMIN_DEFAULT_PASSWORD: admin123 + PGADMIN_CONFIG_SERVER_MODE: 'False' + PGADMIN_DISABLE_POSTFIX: 'true' + ports: + - "8080:80" + volumes: + - pgadmin_data:/var/lib/pgadmin + depends_on: + - postgres + restart: unless-stopped + networks: + - trading-bot-network + + # Prometheus - Metrics collection (optional) + prometheus: + image: prom/prometheus:latest + container_name: trading-bot-prometheus + ports: + - "9090:9090" + volumes: + - ./monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + - prometheus_data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.console.libraries=/etc/prometheus/console_libraries' + - '--web.console.templates=/etc/prometheus/consoles' + - '--web.enable-lifecycle' + restart: unless-stopped + networks: + - trading-bot-network + + # Grafana - Metrics visualization (optional) + grafana: + image: grafana/grafana:latest + container_name: trading-bot-grafana + ports: + - "3000:3000" + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin123 + - GF_SECURITY_ADMIN_USER=admin + - GF_USERS_ALLOW_SIGN_UP=false + volumes: + - grafana_data:/var/lib/grafana + - ./monitoring/grafana/provisioning:/etc/grafana/provisioning + depends_on: + - prometheus + restart: unless-stopped + networks: + - trading-bot-network + +volumes: + postgres_data: + questdb_data: + dragonfly_data: + pgadmin_data: + prometheus_data: + grafana_data: + +networks: + trading-bot-network: + driver: bridge diff --git a/docs/current-system-flow.md b/docs/current-system-flow.md new file mode 100644 index 0000000..709695e --- /dev/null +++ b/docs/current-system-flow.md @@ -0,0 +1,233 @@ +# 🔄 Current System Communication Flow + +## Active Services (Currently Running) + +```mermaid +graph TB + %% External Data Sources + subgraph "External APIs" + EXT[Demo Data Generation
Alpha Vantage Ready
Yahoo Finance Ready] + end + + %% Currently Active Services + subgraph "Active Services" + MDG[Market Data Gateway
Port 3001
✅ Running] + TD[Trading Dashboard
Port 5173
✅ Running] + end + + %% Storage Layer + subgraph "Storage" + DRAGONFLY[(Dragonfly
📡 Events & Cache
🔧 Configured)] + end + + %% Data Flow + EXT -->|HTTP Fetch| MDG + MDG -->|REST API| TD + MDG -->|WebSocket Stream| TD + MDG -->|Events| DRAGONFLY + + %% Styling + classDef active fill:#90EE90 + classDef storage fill:#FFE4B5 + classDef external fill:#FFB6C1 + + class MDG,TD active + class DRAGONFLY storage + class EXT external +``` + +## Next Phase Services (Ready to Implement) + +```mermaid +graph TB + %% Current Services + subgraph "Current Layer" + MDG[Market Data Gateway
✅ Operational] + TD[Trading Dashboard
✅ Operational] + end + + %% Next Phase Services + subgraph "Next Phase - Priority 1" + SO[Strategy Orchestrator
🚧 Package Created
📋 Ready to Implement] + RG[Risk Guardian
🚧 Package Created
📋 Ready to Implement] + end + + %% Communication Infrastructure + subgraph "Event Infrastructure" + DRAGONFLY[(Dragonfly Streams
✅ Configured)] + WS[WebSocket Server
✅ Active in MDG] + end + + %% Data Flows + MDG -->|Market Data Events| DRAGONFLY + MDG -->|Real-time Stream| WS + WS -->|Live Updates| TD + DRAGONFLY -->|Market Events| SO + DRAGONFLY -->|All Events| RG + + SO -->|Strategy Events| DRAGONFLY + SO -->|Risk Check| RG + RG -->|Risk Alerts| DRAGONFLY + + %% Styling + classDef current fill:#90EE90 + classDef next fill:#FFE4B5 + classDef infrastructure fill:#E6E6FA + + class MDG,TD current + class SO,RG next + class DRAGONFLY,WS infrastructure +``` + +## Detailed Communication Patterns + +### 1. **Current System (Working)** + +``` +┌─────────────────┐ HTTP REST ┌─────────────────┐ +│ Trading │ ←──────────────→ │ Market Data │ +│ Dashboard │ │ Gateway │ +│ (React/Tremor) │ ←──────────────→ │ (Hono/Bun) │ +└─────────────────┘ WebSocket └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ Dragonfly Events │ + │ (Configured) │ + └─────────────────┘ +``` + +### 2. **Next Phase Implementation** + +``` +┌─────────────────┐ +│ Strategy │ +│ Orchestrator │ ──┐ +└─────────────────┘ │ + ▼ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Market Data │→│ Dragonfly Event │←│ Risk Guardian │ +│ Gateway │ │ Stream │ │ │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ │ │ + ▼ ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Trading │ │ WebSocket │ │ Alert Manager │ +│ Dashboard │ │ Real-time │ │ (Future) │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +## Event Flow Diagram + +```mermaid +sequenceDiagram + participant TD as Trading Dashboard + participant MDG as Market Data Gateway + participant DRAGONFLY as Dragonfly Events + participant SO as Strategy Orchestrator (Next) + participant RG as Risk Guardian (Next) + + Note over TD,DRAGONFLY: Current System - Working + + MDG->>MDG: Generate demo market data + MDG->>TD: WebSocket real-time updates + MDG->>DRAGONFLY: Publish MARKET_DATA events + TD->>MDG: HTTP API requests + MDG->>TD: JSON responses + + Note over SO,RG: Next Phase - To Implement + + DRAGONFLY->>SO: Subscribe to MARKET_DATA events + SO->>SO: Analyze market conditions + SO->>DRAGONFLY: Publish SIGNAL_GENERATED events + + DRAGONFLY->>RG: Subscribe to ALL events + RG->>RG: Monitor risk thresholds + RG->>DRAGONFLY: Publish RISK_ALERT events + + DRAGONFLY->>TD: All events via WebSocket + TD->>TD: Update dashboard with alerts +``` + +## Service Dependencies + +### **Current Dependencies (Satisfied)** +``` +Market Data Gateway +├── ✅ Hono (Web framework) +├── ✅ ioredis (Redis client) +├── ✅ @stock-bot/config (Workspace package) +├── ✅ @stock-bot/shared-types (Workspace package) +└── ✅ ws (WebSocket library) + +Trading Dashboard +├── ✅ React + TypeScript +├── ✅ Tremor UI (Financial components) +├── ✅ Vite (Build tool) +└── ✅ WebSocket client +``` + +### **Next Phase Dependencies (Ready)** +``` +Strategy Orchestrator +├── ✅ Package.json created +├── ✅ Dependencies specified +├── 📋 Implementation needed +└── 🔧 node-cron (Strategy scheduling) + +Risk Guardian +├── ✅ Package.json created +├── ✅ Dependencies specified +├── 📋 Implementation needed +└── 🛡️ Risk monitoring logic +``` + +## Port & Endpoint Map + +| Service | Port | Endpoints | Status | +|---------|------|-----------|--------| +| **Market Data Gateway** | 3001 | `/health`, `/api/market-data/:symbol`, `/api/ohlcv/:symbol`, WebSocket | ✅ Active | +| **Trading Dashboard** | 5173 | Vite dev server | ✅ Active | +| **Strategy Orchestrator** | 4001 | `/health`, `/api/strategies`, `/api/signals` | 📋 Planned | +| **Risk Guardian** | 3002 | `/health`, `/api/risk-checks`, `/api/limits` | 📋 Planned | + +## Data Types & Events + +### **Market Data Event** +```typescript +interface MarketDataEvent { + type: 'MARKET_DATA'; + data: { + symbol: string; + price: number; + bid: number; + ask: number; + volume: number; + timestamp: Date; + }; + timestamp: Date; +} +``` + +### **Strategy Event (Next Phase)** +```typescript +interface StrategyEvent { + type: 'SIGNAL_GENERATED' | 'STRATEGY_START' | 'STRATEGY_STOP'; + strategyId: string; + signal?: TradingSignal; + timestamp: Date; +} +``` + +### **Risk Event (Next Phase)** +```typescript +interface RiskEvent { + type: 'RISK_ALERT' | 'RISK_CHECK'; + severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL'; + message: string; + data: any; + timestamp: Date; +} +``` + +This architecture shows how your current working system will expand into a comprehensive trading platform with clear communication patterns and event flows. diff --git a/docs/system-architecture.md b/docs/system-architecture.md new file mode 100644 index 0000000..85d8e7f --- /dev/null +++ b/docs/system-architecture.md @@ -0,0 +1,358 @@ +# 🏗️ Stock Bot System Architecture + +## System Communication Flow Diagram + +```mermaid +graph TB + %% External Systems + subgraph "External APIs" + AV[Alpha Vantage API] + YF[Yahoo Finance API] + AL[Alpaca Broker API] + IB[Interactive Brokers] + NEWS[News APIs] + end + + %% Core Services Layer + subgraph "Core Services" + MDG[Market Data Gateway
:3001] + RG[Risk Guardian
:3002] + EE[Execution Engine
:3003] + PM[Portfolio Manager
:3004] + end + + %% Intelligence Services Layer + subgraph "Intelligence Services" + SO[Strategy Orchestrator
:4001] + SG[Signal Generator
:4002] + BA[Backtesting Engine
:4003] + ML[ML Pipeline
:4004] + end + + %% Data Services Layer + subgraph "Data Services" + HDS[Historical Data Service
:5001] + AS[Analytics Service
:5002] + DQS[Data Quality Service
:5003] + ETLS[ETL Service
:5004] + end + + %% Platform Services Layer + subgraph "Platform Services" + LM[Log Manager
:6001] + CM[Config Manager
:6002] + AM[Alert Manager
:6003] + SM[Service Monitor
:6004] + end + + %% Integration Services Layer + subgraph "Integration Services" + BAS[Broker Adapter
:7001] + DAS[Data Adapter
:7002] + NS[Notification Service
:7003] + WHS[Webhook Service
:7004] + end + + %% Interface Services Layer + subgraph "Interface Services" + TD[Trading Dashboard
:5173] + API[REST API Gateway
:8001] + WS[WebSocket Server
Embedded] + end + + %% Storage Layer + subgraph "Storage Layer" + DRAGONFLY[(Dragonfly
Events & Cache)] + QDB[(QuestDB
Time Series)] + PGDB[(PostgreSQL
Relational)] + FS[(File System
Logs & Config)] + end + + %% Communication Flows + + %% External to Core + AV --> MDG + YF --> MDG + AL --> BAS + IB --> BAS + NEWS --> DAS + + %% Core Service Communications + MDG -->|Market Data Events| DRAGONFLY + MDG -->|Real-time Stream| WS + MDG -->|Cache| DRAGONFLY + + RG -->|Risk Events| DRAGONFLY + RG -->|Risk Alerts| AM + RG -->|Position Limits| PM + + EE -->|Order Events| DRAGONFLY + EE -->|Trade Execution| BAS + EE -->|Order Status| PM + + PM -->|Portfolio Events| DRAGONFLY + PM -->|P&L Updates| TD + PM -->|Position Data| RG + + %% Intelligence Communications + SO -->|Strategy Events| DRAGONFLY + SO -->|Signal Requests| SG + SO -->|Execution Orders| EE + SO -->|Risk Check| RG + + SG -->|Trading Signals| SO + SG -->|ML Requests| ML + SG -->|Market Data| DRAGONFLY + + BA -->|Backtest Results| SO + BA -->|Historical Data| HDS + + ML -->|Predictions| SG + ML -->|Training Data| HDS + + %% Data Service Communications + HDS -->|Store Data| QDB + HDS -->|Query Data| QDB + HDS -->|Data Events| DRAGONFLY + + AS -->|Analytics| QDB + AS -->|Metrics| SM + AS -->|Reports| TD + + DQS -->|Data Quality| DRAGONFLY + DQS -->|Alerts| AM + + ETLS -->|Raw Data| DAS + ETLS -->|Processed Data| HDS + + %% Platform Communications + LM -->|Logs| FS + LM -->|Log Events| DRAGONFLY + + CM -->|Config| FS + CM -->|Config Updates| DRAGONFLY + + AM -->|Alerts| NS + AM -->|Alert Events| DRAGONFLY + + SM -->|Health Checks| DRAGONFLY + SM -->|Metrics| QDB + + %% Integration Communications + BAS -->|Orders| AL + BAS -->|Orders| IB + BAS -->|Order Updates| EE + + DAS -->|Data Feed| MDG + DAS -->|External Data| HDS + + NS -->|Notifications| WHS + NS -->|Alerts| TD + + WHS -->|Webhooks| External + + %% Interface Communications + TD -->|API Calls| API + TD -->|WebSocket| WS + TD -->|Dashboard Data| PM + + API -->|Service Calls| SO + API -->|Data Queries| HDS + API -->|System Status| SM + + WS -->|Real-time Data| TD + WS -->|Events| DRAGONFLY + + %% Storage Access DRAGONFLY -.->|Events| SO + DRAGONFLY -.->|Events| RG + DRAGONFLY -.->|Events| PM + DRAGONFLY -.->|Cache| MDG + + QDB -.->|Time Series| HDS + QDB -.->|Analytics| AS + QDB -.->|Metrics| SM + + PGDB -.->|Relational| PM + PGDB -.->|Config| CM + PGDB -.->|Users| API + + %% Styling + classDef external fill:#ff9999 + classDef core fill:#99ccff + classDef intelligence fill:#99ff99 + classDef data fill:#ffcc99 + classDef platform fill:#cc99ff + classDef integration fill:#ffff99 + classDef interface fill:#ff99cc + classDef storage fill:#cccccc + + class AV,YF,AL,IB,NEWS external + class MDG,RG,EE,PM core + class SO,SG,BA,ML intelligence + class HDS,AS,DQS,ETLS data + class LM,CM,AM,SM platform + class BAS,DAS,NS,WHS integration + class TD,API,WS interface + class DRAGONFLY,QDB,PGDB,FS storage +``` + +## Communication Patterns + +### 1. **Event-Driven Architecture (Dragonfly Streams)** +``` +┌─────────────┐ Dragonfly Events ┌─────────────┐ +│ Service │ ─────────────────→ │ Service │ +│ A │ │ B │ +└─────────────┘ └─────────────┘ +``` + +**Event Types:** +- `MARKET_DATA` - Real-time price updates +- `ORDER_CREATED/FILLED/CANCELLED` - Order lifecycle +- `SIGNAL_GENERATED` - Trading signals +- `RISK_ALERT` - Risk threshold violations +- `PORTFOLIO_UPDATE` - Position changes +- `STRATEGY_START/STOP` - Strategy lifecycle + +### 2. **Request-Response (HTTP/REST)** +``` +┌─────────────┐ HTTP Request ┌─────────────┐ +│ Client │ ─────────────────→ │ Service │ +│ │ ←───────────────── │ │ +└─────────────┘ HTTP Response └─────────────┘ +``` + +**API Endpoints:** +- `/api/market-data/:symbol` - Current market data +- `/api/portfolio/positions` - Portfolio positions +- `/api/strategies` - Strategy management +- `/api/orders` - Order management +- `/api/health` - Service health checks + +### 3. **Real-time Streaming (WebSocket)** +``` +┌─────────────┐ WebSocket ┌─────────────┐ +│ Client │ ←═════════════════→ │ Server │ +│ │ Bidirectional │ │ +└─────────────┘ └─────────────┘ +``` + +**WebSocket Messages:** +- Market data subscriptions +- Portfolio updates +- Trading signals +- Risk alerts +- System notifications + +### 4. **Data Persistence** +``` +┌─────────────┐ Store/Query ┌─────────────┐ +│ Service │ ─────────────────→ │ Database │ +│ │ ←───────────────── │ │ +└─────────────┘ └─────────────┘ +``` + +**Storage Types:** +- **Dragonfly**: Events, cache, sessions +- **QuestDB**: Time-series data, metrics +- **PostgreSQL**: Configuration, users, metadata +- **File System**: Logs, configurations + +## Service Communication Matrix + +| Service | Publishes Events | Subscribes to Events | HTTP APIs | WebSocket | Storage | +|---------|-----------------|---------------------|-----------|-----------|---------| +| Market Data Gateway | ✅ Market Data | - | ✅ REST | ✅ Server | Dragonfly Cache | +| Risk Guardian | ✅ Risk Alerts | ✅ All Events | ✅ REST | - | PostgreSQL | +| Strategy Orchestrator | ✅ Strategy Events | ✅ Market Data, Signals | ✅ REST | - | PostgreSQL | +| Execution Engine | ✅ Order Events | ✅ Strategy Events | ✅ REST | - | PostgreSQL | +| Portfolio Manager | ✅ Portfolio Events | ✅ Order Events | ✅ REST | - | PostgreSQL | +| Trading Dashboard | - | ✅ All Events | ✅ Client | ✅ Client | - | + +## Data Flow Example: Trade Execution + +```mermaid +sequenceDiagram + participant TD as Trading Dashboard + participant SO as Strategy Orchestrator + participant SG as Signal Generator + participant RG as Risk Guardian + participant EE as Execution Engine + participant BAS as Broker Adapter + participant PM as Portfolio Manager + participant DRAGONFLY as Dragonfly Events + + Note over TD,DRAGONFLY: User starts a trading strategy + + TD->>SO: POST /api/strategies/start + SO->>DRAGONFLY: Publish STRATEGY_START event + + Note over SO,SG: Strategy generates signals + + SO->>SG: Request signals for AAPL + SG->>SO: Return BUY signal + SO->>DRAGONFLY: Publish SIGNAL_GENERATED event + + Note over SO,RG: Risk check before execution + + SO->>RG: Check risk limits + RG->>SO: Risk approved + + Note over SO,EE: Execute the trade + + SO->>EE: Submit order + EE->>DRAGONFLY: Publish ORDER_CREATED event + EE->>BAS: Send order to broker + BAS->>EE: Order filled + EE->>DRAGONFLY: Publish ORDER_FILLED event + + Note over PM,TD: Update portfolio and notify user + + PM->>DRAGONFLY: Subscribe to ORDER_FILLED + PM->>PM: Update positions PM->>DRAGONFLY: Publish PORTFOLIO_UPDATE + TD->>DRAGONFLY: Subscribe to PORTFOLIO_UPDATE + TD->>TD: Update dashboard +``` + +## Port Allocation + +| Service Category | Port Range | Services | +|-----------------|------------|----------| +| Core Services | 3001-3099 | Market Data Gateway (3001), Risk Guardian (3002) | +| Intelligence Services | 4001-4099 | Strategy Orchestrator (4001), Signal Generator (4002) | +| Data Services | 5001-5099 | Historical Data (5001), Analytics (5002) | +| Platform Services | 6001-6099 | Log Manager (6001), Config Manager (6002) | +| Integration Services | 7001-7099 | Broker Adapter (7001), Data Adapter (7002) | +| Interface Services | 8001-8099 | API Gateway (8001), Dashboard (5173-Vite) | + +## Security & Authentication + +```mermaid +graph LR + subgraph "Security Layer" + JWT[JWT Tokens] + API_KEY[API Keys] + TLS[TLS/HTTPS] + RBAC[Role-Based Access] + end + + subgraph "External Security" + BROKER_AUTH[Broker Authentication] + DATA_AUTH[Data Provider Auth] + WEBHOOK_SIG[Webhook Signatures] + end + + JWT --> API_KEY + API_KEY --> TLS + TLS --> RBAC + RBAC --> BROKER_AUTH + BROKER_AUTH --> DATA_AUTH + DATA_AUTH --> WEBHOOK_SIG +``` + +This architecture provides: +- **Scalability**: Services can be scaled independently +- **Reliability**: Event-driven communication with retry mechanisms +- **Maintainability**: Clear separation of concerns +- **Observability**: Centralized logging and monitoring +- **Security**: Multiple layers of authentication and authorization diff --git a/docs/system-communication-chart.md b/docs/system-communication-chart.md new file mode 100644 index 0000000..c3f3c50 --- /dev/null +++ b/docs/system-communication-chart.md @@ -0,0 +1,140 @@ +# 📊 Stock Bot System Communication - Quick Reference + +## Current System (Active) +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ TRADING BOT SYSTEM │ +└─────────────────────────────────────────────────────────────────────┘ + +External APIs Core Services Interface Services +┌─────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Demo Data │──────▶│ Market Data │◀──────▶│ Trading │ +│ Alpha Vant. │ │ Gateway │ │ Dashboard │ +│ Yahoo Fin. │ │ Port: 3001 │ │ Port: 5173 │ +└─────────────┘ │ Status: ✅ LIVE │ │ Status: ✅ LIVE │ + └─────────────────┘ └─────────────────┘ + │ ▲ + ▼ │ + ┌─────────────────┐ │ + │ Dragonfly Events │─────────────────┘ + │ Cache & Streams │ + │ Status: ✅ READY│ + └─────────────────┘ +``` + +## Next Phase (Ready to Implement) +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ EXPANDED TRADING SYSTEM │ +└─────────────────────────────────────────────────────────────────────┘ + +Intelligence Services Core Services Interface Services +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Strategy │◀────▶│ Market Data │◀───▶│ Trading │ +│ Orchestrator │ │ Gateway │ │ Dashboard │ +│ Port: 4001 │ │ Port: 3001 │ │ Port: 5173 │ +│ Status: 📋 PLAN │ │ Status: ✅ LIVE │ │ Status: ✅ LIVE │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + ▲ │ ▲ + │ ▼ │ + │ ┌─────────────────┐ │ + └──────────────▶│ Dragonfly Event │◀─────────────┘ + │ Stream Hub │ + │ Status: ✅ READY│ + └─────────────────┘ + ▲ + │ + ┌─────────────────┐ + │ Risk Guardian │ + │ Port: 3002 │ + │ Status: 📋 PLAN │ + └─────────────────┘ +``` + +## Communication Protocols + +### HTTP REST API +``` +Client ──── GET/POST ───▶ Server + ◀─── JSON ──────── +``` + +### WebSocket Real-time +``` +Client ◀═══ Stream ═══▶ Server + ◀═══ Events ══▶ +``` + +### Dragonfly Event Bus +``` +Service A ──── Publish ───▶ Dragonfly ──── Subscribe ───▶ Service B + ◀─── Confirm ──── ◀─── Events ──── +``` + +## Event Types + +| Event Type | Publisher | Subscribers | Frequency | +|------------|-----------|-------------|-----------| +| `MARKET_DATA` | Market Data Gateway | Dashboard, Strategy Orchestrator | Every 5s | +| `SIGNAL_GENERATED` | Strategy Orchestrator | Risk Guardian, Execution Engine | As needed | +| `RISK_ALERT` | Risk Guardian | Dashboard, Alert Manager | As needed | +| `PORTFOLIO_UPDATE` | Portfolio Manager | Dashboard, Risk Guardian | On trades | + +## Service Status Matrix + +| Service | Port | Status | Dependencies | Ready to Implement | +|---------|------|--------|--------------|-------------------| +| Market Data Gateway | 3001 | ✅ Running | Dragonfly, Config | ✅ Complete | +| Trading Dashboard | 5173 | ✅ Running | MDG API | ✅ Complete | +| Strategy Orchestrator | 4001 | 📋 Planned | Dragonfly, MDG | ✅ Package Ready | +| Risk Guardian | 3002 | 📋 Planned | Dragonfly, Config | ✅ Package Ready | +| Portfolio Manager | 3004 | ⏳ Future | Database, Orders | ❌ Not Started | +| Execution Engine | 3003 | ⏳ Future | Brokers, Portfolio | ❌ Not Started | + +## Data Flow Summary + +1. **Market Data Flow** + ``` + External APIs → Market Data Gateway → Dragonfly Events → Dashboard + → Strategy Orchestrator + ``` + +2. **Trading Signal Flow** + ``` + Market Data → Strategy Orchestrator → Trading Signals → Risk Guardian + → Execution Engine + ``` + +3. **Risk Management Flow** + ``` + All Events → Risk Guardian → Risk Alerts → Alert Manager + → Risk Blocks → Strategy Orchestrator + ``` + +4. **User Interface Flow** + ``` + WebSocket ← Dashboard → REST API → Services + Events ← → Commands → + ``` + +## Implementation Priority + +### Phase 1 (Current) ✅ +- [x] Market Data Gateway +- [x] Trading Dashboard +- [x] Dragonfly Infrastructure +- [x] WebSocket Communication + +### Phase 2 (Next) 📋 +- [ ] Strategy Orchestrator +- [ ] Risk Guardian +- [ ] Event-driven Strategy Execution +- [ ] Risk Monitoring & Alerts + +### Phase 3 (Future) ⏳ +- [ ] Portfolio Manager +- [ ] Execution Engine +- [ ] Broker Integration +- [ ] Database Persistence + +The system is designed for incremental development where each service can be implemented and tested independently while maintaining full system functionality. diff --git a/monitoring/grafana/provisioning/datasources/prometheus.yml b/monitoring/grafana/provisioning/datasources/prometheus.yml new file mode 100644 index 0000000..1a57b69 --- /dev/null +++ b/monitoring/grafana/provisioning/datasources/prometheus.yml @@ -0,0 +1,9 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + editable: true diff --git a/monitoring/prometheus.yml b/monitoring/prometheus.yml new file mode 100644 index 0000000..21f6a74 --- /dev/null +++ b/monitoring/prometheus.yml @@ -0,0 +1,45 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + +rule_files: + # - "first_rules.yml" + # - "second_rules.yml" + +scrape_configs: + # Prometheus itself + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090'] + + # Trading Bot Services + - job_name: 'market-data-gateway' + static_configs: + - targets: ['host.docker.internal:3001'] + metrics_path: '/metrics' + scrape_interval: 5s + + - job_name: 'strategy-orchestrator' + static_configs: + - targets: ['host.docker.internal:4001'] + metrics_path: '/metrics' + scrape_interval: 10s + + - job_name: 'risk-guardian' + static_configs: + - targets: ['host.docker.internal:3002'] + metrics_path: '/metrics' + scrape_interval: 10s + + # Infrastructure + - job_name: 'dragonfly' + static_configs: + - targets: ['dragonfly:6379'] + + - job_name: 'postgres' + static_configs: + - targets: ['postgres:5432'] + + - job_name: 'questdb' + static_configs: + - targets: ['questdb:9000'] diff --git a/monitoring/prometheus/prometheus.yml b/monitoring/prometheus/prometheus.yml new file mode 100644 index 0000000..aec7c80 --- /dev/null +++ b/monitoring/prometheus/prometheus.yml @@ -0,0 +1,26 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + +rule_files: + # - "first_rules.yml" + # - "second_rules.yml" + +scrape_configs: + # The job name is added as a label `job=` to any timeseries scraped from this config. + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090'] + + # Add other services as they become available + # - job_name: 'trading-bot' + # static_configs: + # - targets: ['localhost:3001'] + + # - job_name: 'market-data-gateway' + # static_configs: + # - targets: ['localhost:3002'] + + # - job_name: 'risk-guardian' + # static_configs: + # - targets: ['localhost:3003'] diff --git a/package.json b/package.json new file mode 100644 index 0000000..8b273fa --- /dev/null +++ b/package.json @@ -0,0 +1,43 @@ +{ + "name": "stock-bot", + "private": true, + "version": "1.0.0", + "description": "Advanced trading bot with microservice architecture", "scripts": { + "dev": "turbo run dev", + "build": "turbo run build", + "test": "turbo run test", + "lint": "turbo run lint", + "clean": "turbo run clean", + "start": "turbo run start", + "backtest": "turbo run backtest", + + "docker:start": "pwsh ./scripts/docker.ps1 start", + "docker:stop": "pwsh ./scripts/docker.ps1 stop", + "docker:restart": "pwsh ./scripts/docker.ps1 restart", + "docker:status": "pwsh ./scripts/docker.ps1 status", + "docker:logs": "pwsh ./scripts/docker.ps1 logs", + "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:down": "docker-compose down", + "infra:reset": "docker-compose down -v && docker-compose up -d dragonfly postgres questdb", + + "dev:full": "npm run infra:up && npm run docker:admin && turbo run dev", + "dev:clean": "npm run infra:reset && npm run dev:full" + },"workspaces": [ + "packages/*", + "apps/*/*" + ], + "devDependencies": { + "@types/node": "^20.12.12", + "turbo": "^2.0.5", + "typescript": "^5.4.5" + }, + "packageManager": "bun@1.1.12", + "engines": { + "node": ">=18.0.0", + "bun": ">=1.1.0" + } +} diff --git a/packages/config/package.json b/packages/config/package.json new file mode 100644 index 0000000..9f6fc77 --- /dev/null +++ b/packages/config/package.json @@ -0,0 +1,21 @@ +{ + "name": "@stock-bot/config", + "version": "1.0.0", + "description": "Shared configuration for the trading bot", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "clean": "rm -rf dist", + "test": "echo \"No tests yet\"" + }, + "dependencies": { + "@stock-bot/shared-types": "workspace:*", + "dotenv": "^16.4.5" + }, + "devDependencies": { + "typescript": "^5.4.5", + "@types/node": "^20.12.12" + } +} diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts new file mode 100644 index 0000000..e7ebf30 --- /dev/null +++ b/packages/config/src/index.ts @@ -0,0 +1,156 @@ +import { config } from 'dotenv'; +import type { DatabaseConfig, BrokerConfig, DataProviderConfig } from '@stock-bot/shared-types'; + +// Load environment variables +config(); + +export const env = { + NODE_ENV: process.env.NODE_ENV || 'development', + PORT: parseInt(process.env.PORT || '3000'), + // Database URLs + QUESTDB_URL: process.env.QUESTDB_URL || 'postgresql://admin:quest@localhost:8812/qdb', + POSTGRES_URL: process.env.POSTGRES_URL || 'postgresql://postgres:password@localhost:5432/stockbot', + DRAGONFLY_URL: process.env.DRAGONFLY_URL || 'redis://localhost:6379', + + // API Keys + ALPHA_VANTAGE_API_KEY: process.env.ALPHA_VANTAGE_API_KEY || '', + ALPACA_API_KEY: process.env.ALPACA_API_KEY || '', + ALPACA_SECRET_KEY: process.env.ALPACA_SECRET_KEY || '', + + // Trading Configuration + PAPER_TRADING: process.env.PAPER_TRADING === 'true', + MAX_POSITION_SIZE: parseFloat(process.env.MAX_POSITION_SIZE || '0.1'), + MAX_DAILY_LOSS: parseFloat(process.env.MAX_DAILY_LOSS || '1000'), + + // Logging + LOG_LEVEL: process.env.LOG_LEVEL || 'info', + + // Feature Flags + ENABLE_ML_SIGNALS: process.env.ENABLE_ML_SIGNALS === 'true', + ENABLE_SENTIMENT_ANALYSIS: process.env.ENABLE_SENTIMENT_ANALYSIS === 'true', +} as const; + +export const databaseConfig: DatabaseConfig = { + questdb: { + host: process.env.QUESTDB_HOST || 'localhost', + port: parseInt(process.env.QUESTDB_PORT || '9000'), + database: process.env.QUESTDB_DATABASE || 'qdb', + }, + postgres: { + host: process.env.POSTGRES_HOST || 'localhost', + port: parseInt(process.env.POSTGRES_PORT || '5432'), + database: process.env.POSTGRES_DATABASE || 'stockbot', + username: process.env.POSTGRES_USERNAME || 'postgres', + password: process.env.POSTGRES_PASSWORD || 'password', + }, dragonfly: { + host: process.env.DRAGONFLY_HOST || 'localhost', + port: parseInt(process.env.DRAGONFLY_PORT || '6379'), + password: process.env.DRAGONFLY_PASSWORD, + }, +}; + +export const brokerConfigs: Record = { + alpaca: { + name: 'Alpaca', + apiKey: env.ALPACA_API_KEY, + secretKey: env.ALPACA_SECRET_KEY, + baseUrl: env.PAPER_TRADING + ? 'https://paper-api.alpaca.markets' + : 'https://api.alpaca.markets', + sandbox: env.PAPER_TRADING, + }, +}; + +export const dataProviderConfigs: Record = { + alphaVantage: { + name: 'Alpha Vantage', + apiKey: env.ALPHA_VANTAGE_API_KEY, + baseUrl: 'https://www.alphavantage.co', + rateLimits: { + requestsPerSecond: 5, + requestsPerDay: 500, + }, + }, +}; + +export const serviceDefaults = { + healthCheckInterval: 30000, // 30 seconds + retryAttempts: 3, + requestTimeout: 10000, // 10 seconds + circuitBreakerThreshold: 5, + circuitBreakerTimeout: 60000, // 1 minute +}; + +export const tradingHours = { + market: { + open: '09:30', + close: '16:00', + timezone: 'America/New_York', + }, + premarket: { + open: '04:00', + close: '09:30', + timezone: 'America/New_York', + }, + afterHours: { + open: '16:00', + close: '20:00', + timezone: 'America/New_York', + }, +}; + +export const riskDefaults = { + maxPositionSize: 0.1, // 10% of portfolio + maxDailyLoss: 0.02, // 2% of portfolio + maxDrawdown: 0.1, // 10% of portfolio + stopLossPercent: 0.05, // 5% + takeProfitPercent: 0.15, // 15% +}; + +// Environment-specific configurations +export const getConfig = () => { + const base = { + env: env.NODE_ENV, + port: env.PORT, + database: databaseConfig, + brokers: brokerConfigs, + dataProviders: dataProviderConfigs, + services: serviceDefaults, + trading: { + hours: tradingHours, + risk: riskDefaults, + paperTrading: env.PAPER_TRADING, + }, + }; + + switch (env.NODE_ENV) { + case 'development': + return { + ...base, + logging: { + level: 'debug', + console: true, + file: false, + }, + cache: { + ttl: 300, // 5 minutes + }, + }; + + case 'production': + return { + ...base, + logging: { + level: 'info', + console: false, + file: true, + }, + cache: { + ttl: 60, // 1 minute + }, + }; + + default: + return base; + } +}; diff --git a/packages/config/tsconfig.json b/packages/config/tsconfig.json new file mode 100644 index 0000000..62d9619 --- /dev/null +++ b/packages/config/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "declaration": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/shared-types/package.json b/packages/shared-types/package.json new file mode 100644 index 0000000..b131355 --- /dev/null +++ b/packages/shared-types/package.json @@ -0,0 +1,23 @@ +{ + "name": "@stock-bot/shared-types", + "version": "1.0.0", + "description": "Shared TypeScript definitions for the trading bot", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "clean": "rm -rf dist", + "test": "echo \"No tests yet\"" + }, + "devDependencies": { + "typescript": "^5.4.5" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.js" + } + } +} diff --git a/packages/shared-types/src/index.ts b/packages/shared-types/src/index.ts new file mode 100644 index 0000000..47eeda8 --- /dev/null +++ b/packages/shared-types/src/index.ts @@ -0,0 +1,204 @@ +// Market Data Types +export interface OHLCV { + symbol: string; + timestamp: Date; + open: number; + high: number; + low: number; + close: number; + volume: number; +} + +export interface MarketData { + symbol: string; + price: number; + bid: number; + ask: number; + volume: number; + timestamp: Date; +} + +export interface OrderBook { + symbol: string; + bids: [number, number][]; // [price, size] + asks: [number, number][]; // [price, size] + timestamp: Date; +} + +// Trading Types +export type OrderSide = 'BUY' | 'SELL'; +export type OrderType = 'MARKET' | 'LIMIT' | 'STOP' | 'STOP_LIMIT'; +export type OrderStatus = 'PENDING' | 'FILLED' | 'PARTIALLY_FILLED' | 'CANCELLED' | 'REJECTED'; + +export interface Order { + id: string; + symbol: string; + side: OrderSide; + type: OrderType; + quantity: number; + price?: number; + stopPrice?: number; + status: OrderStatus; + timestamp: Date; + strategyId: string; +} + +export interface Position { + symbol: string; + quantity: number; + averagePrice: number; + marketValue: number; + unrealizedPnL: number; + timestamp: Date; +} + +export interface Portfolio { + cash: number; + totalValue: number; + positions: Position[]; + dayPnL: number; + totalPnL: number; + timestamp: Date; +} + +// Strategy Types +export type SignalType = 'BUY' | 'SELL' | 'HOLD'; + +export interface TradingSignal { + symbol: string; + type: SignalType; + strength: number; // 0-1 + price: number; + timestamp: Date; + strategyId: string; + metadata?: Record; +} + +export interface Strategy { + id: string; + name: string; + description: string; + isActive: boolean; + riskLimits: RiskLimits; + parameters: Record; +} + +export interface RiskLimits { + maxPositionSize: number; + maxDailyLoss: number; + maxDrawdown: number; + allowedSymbols?: string[]; +} + +// Event Types +export interface MarketDataEvent { + type: 'MARKET_DATA'; + data: MarketData; + timestamp: Date; +} + +export interface OrderEvent { + type: 'ORDER_CREATED' | 'ORDER_FILLED' | 'ORDER_CANCELLED'; + order: Order; + timestamp: Date; +} + +export interface SignalEvent { + type: 'SIGNAL_GENERATED'; + signal: TradingSignal; + timestamp: Date; +} + +export type TradingEvent = MarketDataEvent | OrderEvent | SignalEvent; + +// Service Types +export interface ServiceConfig { + name: string; + version: string; + environment: 'development' | 'staging' | 'production'; + port?: number; + dependencies?: string[]; +} + +export interface HealthStatus { + service: string; + status: 'healthy' | 'unhealthy' | 'degraded'; + timestamp: Date; + details?: Record; +} + +// API Response Types +export interface ApiResponse { + success: boolean; + data?: T; + error?: string; + timestamp: Date; +} + +export interface PaginatedResponse { + data: T[]; + total: number; + page: number; + limit: number; + hasNext: boolean; +} + +// Fundamental Data Types +export interface CompanyFundamentals { + symbol: string; + marketCap: number; + peRatio?: number; + pbRatio?: number; + roe?: number; + revenue: number; + netIncome: number; + lastUpdated: Date; +} + +export interface NewsItem { + id: string; + headline: string; + summary: string; + sentiment: number; // -1 to 1 + symbols: string[]; + source: string; + publishedAt: Date; +} + +// Configuration Types +export interface DatabaseConfig { + questdb: { + host: string; + port: number; + database: string; + }; + postgres: { + host: string; + port: number; + database: string; + username: string; + password: string; + }; dragonfly: { + host: string; + port: number; + password?: string; + }; +} + +export interface BrokerConfig { + name: string; + apiKey: string; + secretKey: string; + baseUrl: string; + sandbox: boolean; +} + +export interface DataProviderConfig { + name: string; + apiKey: string; + baseUrl: string; + rateLimits: { + requestsPerSecond: number; + requestsPerDay: number; + }; +} diff --git a/packages/shared-types/tsconfig.json b/packages/shared-types/tsconfig.json new file mode 100644 index 0000000..62d9619 --- /dev/null +++ b/packages/shared-types/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "declaration": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/scripts/docker.ps1 b/scripts/docker.ps1 new file mode 100644 index 0000000..a6a7132 --- /dev/null +++ b/scripts/docker.ps1 @@ -0,0 +1,143 @@ +#!/usr/bin/env pwsh + +# Trading Bot Docker Management Script + +param( + [Parameter(Mandatory=$true)] + [ValidateSet("start", "stop", "restart", "status", "logs", "reset", "admin", "monitoring", "help")] + [string]$Action, + + [Parameter(Mandatory=$false)] + [string]$Service = "", + + [Parameter(Mandatory=$false)] + [switch]$Dev = $false +) + +$ComposeFiles = if ($Dev) { + "-f docker-compose.yml -f docker-compose.dev.yml" +} else { + "-f docker-compose.yml" +} + +switch ($Action) { + "start" { + Write-Host "🚀 Starting Trading Bot infrastructure..." -ForegroundColor Green + if ($Service) { + Invoke-Expression "docker-compose $ComposeFiles up -d $Service" + } else { + Invoke-Expression "docker-compose $ComposeFiles up -d dragonfly postgres questdb" + } + Write-Host "✅ Infrastructure started!" -ForegroundColor Green + Write-Host "" + Write-Host "🔗 Access Points:" -ForegroundColor Cyan + Write-Host " Dragonfly: localhost:6379" + Write-Host " PostgreSQL: localhost:5432" + Write-Host " QuestDB Console: http://localhost:9000" + Write-Host "" + Write-Host "💡 Use './scripts/docker.ps1 admin' to start admin interfaces" + } + + "stop" { + Write-Host "🛑 Stopping Trading Bot infrastructure..." -ForegroundColor Yellow + if ($Service) { + Invoke-Expression "docker-compose $ComposeFiles stop $Service" + } else { + Invoke-Expression "docker-compose $ComposeFiles down" + } + Write-Host "✅ Infrastructure stopped!" -ForegroundColor Green + } + + "restart" { + Write-Host "🔄 Restarting Trading Bot infrastructure..." -ForegroundColor Yellow + if ($Service) { + Invoke-Expression "docker-compose $ComposeFiles restart $Service" + } else { + Invoke-Expression "docker-compose $ComposeFiles restart" + } + Write-Host "✅ Infrastructure restarted!" -ForegroundColor Green + } + + "status" { + Write-Host "📊 Trading Bot Infrastructure Status:" -ForegroundColor Cyan + Invoke-Expression "docker-compose $ComposeFiles ps" + Write-Host "" + Write-Host "🔍 Health Checks:" -ForegroundColor Cyan + docker ps --filter "name=trading-bot" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" + } + + "logs" { + if ($Service) { + Write-Host "📋 Logs for $Service:" -ForegroundColor Cyan + Invoke-Expression "docker-compose $ComposeFiles logs -f $Service" + } else { + Write-Host "📋 All service logs:" -ForegroundColor Cyan + Invoke-Expression "docker-compose $ComposeFiles logs -f" + } + } + + "reset" { + Write-Host "⚠️ Resetting Trading Bot infrastructure (will delete all data)..." -ForegroundColor Red + $confirm = Read-Host "Are you sure? Type 'yes' to confirm" + if ($confirm -eq "yes") { + Invoke-Expression "docker-compose $ComposeFiles down -v" + Write-Host "🗑️ Volumes removed" + Invoke-Expression "docker-compose $ComposeFiles up -d dragonfly postgres questdb" + Write-Host "✅ Infrastructure reset complete!" -ForegroundColor Green + } else { + Write-Host "❌ Reset cancelled" -ForegroundColor Yellow + } + } + + "admin" { + Write-Host "🔧 Starting admin interfaces..." -ForegroundColor Green + Invoke-Expression "docker-compose $ComposeFiles up -d redis-insight pgadmin" + Write-Host "✅ Admin interfaces started!" -ForegroundColor Green + Write-Host "" + Write-Host "🔗 Admin Access:" -ForegroundColor Cyan + Write-Host " Redis Insight: http://localhost:8001" + Write-Host " PgAdmin: http://localhost:8080" + Write-Host " Email: admin@tradingbot.local" + Write-Host " Password: admin123" + } + + "monitoring" { + Write-Host "📊 Starting monitoring stack..." -ForegroundColor Green + Invoke-Expression "docker-compose $ComposeFiles --profile monitoring up -d" + Write-Host "✅ Monitoring started!" -ForegroundColor Green + Write-Host "" + Write-Host "🔗 Monitoring Access:" -ForegroundColor Cyan + Write-Host " Prometheus: http://localhost:9090" + Write-Host " Grafana: http://localhost:3000" + Write-Host " Username: admin" + Write-Host " Password: admin123" + } + + "help" { + Write-Host "" + Write-Host "🤖 Trading Bot Docker Management" -ForegroundColor Green + Write-Host "" + Write-Host "Usage: ./scripts/docker.ps1 [options]" -ForegroundColor Cyan + Write-Host "" + Write-Host "Actions:" -ForegroundColor Yellow + Write-Host " start Start infrastructure services" + Write-Host " stop Stop infrastructure services" + Write-Host " restart Restart infrastructure services" + Write-Host " status Show service status" + Write-Host " logs Show service logs" + Write-Host " reset Reset all data (destructive)" + Write-Host " admin Start admin interfaces" + Write-Host " monitoring Start monitoring stack" + Write-Host " help Show this help" + Write-Host "" + Write-Host "Options:" -ForegroundColor Yellow + Write-Host " -Service Specify a specific service" + Write-Host " -Dev Use development configuration" + Write-Host "" + Write-Host "Examples:" -ForegroundColor Cyan + Write-Host " ./scripts/docker.ps1 start" + Write-Host " ./scripts/docker.ps1 start -Dev" + Write-Host " ./scripts/docker.ps1 logs -Service dragonfly" + Write-Host " ./scripts/docker.ps1 admin" + } +} diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..8a8b159 --- /dev/null +++ b/turbo.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://turbo.build/schema.json", + "ui": "tui", + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**", ".next/**", "!.next/cache/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "test": { + "dependsOn": ["build"], + "outputs": ["coverage/**"] + }, + "lint": { + "dependsOn": ["^lint"] + }, + "clean": { + "cache": false + }, + "start": { + "dependsOn": ["build"], + "cache": false, + "persistent": true + }, + "backtest": { + "dependsOn": ["build"], + "cache": false + } + } +}