initial setup

This commit is contained in:
Bojan Kucera 2025-06-02 08:15:20 -04:00
commit 232a63dfe8
61 changed files with 4985 additions and 0 deletions

60
.dockerignore Normal file
View file

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

35
.env.example Normal file
View file

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

106
.gitignore vendored Normal file
View file

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

272
DOCKER.md Normal file
View file

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

180
README.md Normal file
View file

@ -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 <your-repo-url>
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

149
SETUP-COMPLETE.md Normal file
View file

@ -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! 🚀📈**

View file

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

View file

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

View file

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

View file

@ -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<void> {
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<void> {
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<void> {
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<void> {
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<any | null> {
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<void> {
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<void> {
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<void> {
await this.dragonfly.quit();
}
}

View file

@ -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<any> = new Set();
private subscriptions: Map<string, Set<any>> = 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<void> {
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<MarketData> {
// 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<OHLCV[]> {
// 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<string, number> = {
'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<void> {
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');
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View file

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

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

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

View file

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

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -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<DashboardData>({
marketData: [],
ohlcvData: [],
serviceHealth: null,
lastUpdate: null,
});
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [wsConnected, setWsConnected] = useState(false);
const symbols = useMemo(() => ['AAPL', 'GOOGL', 'MSFT', 'TSLA', 'AMZN'], []);
// Memoized fetch functions
const fetchServiceHealth = useCallback(async () => {
try {
const response = await fetch(`${API_BASE}/health`);
const health = await response.json();
return health;
} catch (error) {
console.error('Error fetching service health:', error);
return null;
}
}, []);
const fetchMarketData = useCallback(async () => {
try {
const promises = symbols.map(async (symbol) => {
const response = await fetch(`${API_BASE}/api/market-data/${symbol}`);
const result = await response.json();
return result.success ? result.data : null;
});
const results = await Promise.all(promises);
return results.filter(Boolean);
} catch (error) {
console.error('Error fetching market data:', error);
return [];
}
}, [symbols]);
const fetchOHLCVData = useCallback(async (symbol: string = 'AAPL') => {
try {
const response = await fetch(`${API_BASE}/api/ohlcv/${symbol}?limit=50`);
const result = await response.json();
return result.success ? result.data : [];
} catch (error) {
console.error('Error fetching OHLCV data:', error);
return [];
}
}, []);
// Load all data function
const loadData = useCallback(async () => {
setLoading(true);
try {
const [health, marketData, ohlcvData] = await Promise.all([
fetchServiceHealth(),
fetchMarketData(),
fetchOHLCVData(),
]);
setData({
serviceHealth: health,
marketData,
ohlcvData,
lastUpdate: new Date(),
});
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
}, [fetchServiceHealth, fetchMarketData, fetchOHLCVData]);
// WebSocket connection and data loading
useEffect(() => {
let ws: WebSocket | null = null;
const connectWebSocket = () => {
try {
ws = new WebSocket('ws://localhost:3001');
ws.onopen = () => {
console.log('WebSocket connected');
setWsConnected(true);
};
ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
if (message.type === 'market-data' && message.data) {
// Update specific symbol data
setData(prev => ({
...prev,
marketData: prev.marketData.map(item =>
item.symbol === message.data.symbol ? message.data : item
),
lastUpdate: new Date(),
}));
}
} catch (error) {
console.error('Error parsing WebSocket message:', error);
}
};
ws.onclose = () => {
console.log('WebSocket disconnected');
setWsConnected(false);
// Reconnect after 5 seconds
setTimeout(connectWebSocket, 5000);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
setWsConnected(false);
};
} catch (error) {
console.error('Failed to connect WebSocket:', error);
setWsConnected(false);
}
};
// Initial data load
loadData();
// Set up periodic refresh
const interval = setInterval(loadData, 30000); // Refresh every 30 seconds
// Attempt WebSocket connection
connectWebSocket();
return () => {
clearInterval(interval);
if (ws) {
ws.close();
}
};
}, [loadData]);
// Prepare chart data
const chartData = data.ohlcvData.map((item) => ({
time: new Date(item.timestamp).toLocaleTimeString(),
price: item.close,
volume: item.volume,
}));
// Prepare portfolio allocation data (demo)
const portfolioData = data.marketData.map((item, index) => ({
name: item.symbol,
value: (index + 1) * 20000, // Demo allocation
}));
// Calculate total portfolio value
const totalValue = portfolioData.reduce((sum, item) => sum + item.value, 0);
// Performance metrics (demo)
const performanceData = [
{ date: 'Mon', value: 98000 },
{ date: 'Tue', value: 102000 },
{ date: 'Wed', value: 105000 },
{ date: 'Thu', value: 103000 },
{ date: 'Fri', value: 108000 },
];
if (loading && data.marketData.length === 0) {
return (
<div className="p-8 bg-slate-50 min-h-screen">
<div className="max-w-7xl mx-auto">
<div className="text-center">
<Title>Loading Trading Dashboard...</Title>
<Text className="mt-4">Connecting to market data services</Text>
</div>
</div>
</div>
);
}
return (
<div className="p-8 bg-slate-50 min-h-screen">
<div className="max-w-7xl mx-auto">
{/* Header */}
<div className="mb-8">
<Flex justifyContent="between" alignItems="center">
<div>
<Title className="text-3xl font-bold">🤖 Stock Bot Dashboard</Title>
<Text className="mt-2">Real-time market data monitoring</Text>
</div>
<div className="text-right">
<Button onClick={loadData} disabled={loading}>
{loading ? 'Refreshing...' : 'Refresh Data'}
</Button>
{data.lastUpdate && (
<Text className="mt-2">
Last update: {data.lastUpdate.toLocaleTimeString()}
</Text>
)}
</div>
</Flex>
</div>
{error && (
<Card className="mb-6">
<Text color="red">Error: {error}</Text>
</Card>
)}
{/* Top Metrics */}
<Grid numItems={1} numItemsSm={2} numItemsLg={4} className="gap-6 mb-8">
<Card>
<Text>Portfolio Value</Text>
<Metric>${totalValue.toLocaleString()}</Metric>
<Flex className="mt-4">
<Badge color="emerald">+8.2%</Badge>
</Flex>
</Card>
<Card>
<Text>Service Status</Text>
<Flex alignItems="center" className="mt-2">
<Badge
color={data.serviceHealth?.status === 'healthy' ? 'emerald' : 'red'}
>
{data.serviceHealth?.status || 'Unknown'}
</Badge>
</Flex>
<Flex alignItems="center" className="mt-2">
<Text className="text-sm mr-2">WebSocket:</Text>
<Badge
color={wsConnected ? 'emerald' : 'red'}
size="sm"
>
{wsConnected ? 'Connected' : 'Disconnected'}
</Badge>
</Flex>
</Card>
<Card>
<Text>Active Symbols</Text>
<Metric>{data.marketData.length}</Metric>
</Card>
<Card>
<Text>Daily P&L</Text>
<Metric color="emerald">+$2,450</Metric>
<Flex className="mt-4">
<Badge color="emerald">+2.3%</Badge>
</Flex>
</Card>
</Grid>
{/* Main Content Tabs */}
<TabGroup>
<TabList className="mb-8">
<Tab>Market Data</Tab>
<Tab>Portfolio</Tab>
<Tab>Charts</Tab>
<Tab>Performance</Tab>
</TabList>
<TabPanels>
{/* Market Data Tab */}
<TabPanel>
<Grid numItems={1} numItemsLg={2} className="gap-6">
<Card>
<Title>Live Prices</Title>
<div className="mt-6">
{data.marketData.map((item) => (
<div
key={item.symbol}
className="flex justify-between items-center py-3 border-b border-gray-200 last:border-b-0"
>
<div>
<Text className="font-semibold">{item.symbol}</Text>
<Text className="text-sm text-gray-500">
Vol: {item.volume.toLocaleString()}
</Text>
</div>
<div className="text-right">
<Text className="font-bold text-lg">
${item.price.toFixed(2)}
</Text>
<Text className="text-sm text-gray-500">
Bid: ${item.bid.toFixed(2)} | Ask: ${item.ask.toFixed(2)}
</Text>
</div>
</div>
))}
</div>
</Card>
<Card>
<Title>Market Overview</Title>
<BarChart
className="mt-6"
data={data.marketData}
index="symbol"
categories={["price"]}
colors={["blue"]}
valueFormatter={(value) => `$${value.toFixed(2)}`}
/>
</Card>
</Grid>
</TabPanel>
{/* Portfolio Tab */}
<TabPanel>
<Grid numItems={1} numItemsLg={2} className="gap-6">
<Card>
<Title>Portfolio Allocation</Title>
<DonutChart
className="mt-6"
data={portfolioData}
category="value"
index="name"
valueFormatter={(value) => `$${value.toLocaleString()}`}
colors={["slate", "violet", "indigo", "rose", "cyan"]}
/>
</Card>
<Card>
<Title>Holdings</Title>
<div className="mt-6">
{portfolioData.map((item) => (
<div
key={item.name}
className="flex justify-between items-center py-3 border-b border-gray-200 last:border-b-0"
>
<Text className="font-semibold">{item.name}</Text>
<div className="text-right">
<Text className="font-bold">
${item.value.toLocaleString()}
</Text>
<Text className="text-sm text-gray-500">
{((item.value / totalValue) * 100).toFixed(1)}%
</Text>
</div>
</div>
))}
</div>
</Card>
</Grid>
</TabPanel>
{/* Charts Tab */}
<TabPanel>
<Grid numItems={1} className="gap-6">
<Card>
<Title>AAPL Price Chart</Title>
<LineChart
className="mt-6"
data={chartData}
index="time"
categories={["price"]}
colors={["indigo"]}
valueFormatter={(value) => `$${value.toFixed(2)}`}
yAxisWidth={60}
/>
</Card>
<Card>
<Title>Volume Analysis</Title>
<AreaChart
className="mt-6"
data={chartData}
index="time"
categories={["volume"]}
colors={["emerald"]}
valueFormatter={(value) => value.toLocaleString()}
yAxisWidth={80}
/>
</Card>
</Grid>
</TabPanel>
{/* Performance Tab */}
<TabPanel>
<Grid numItems={1} numItemsLg={2} className="gap-6">
<Card>
<Title>Weekly Performance</Title>
<AreaChart
className="mt-6"
data={performanceData}
index="date"
categories={["value"]}
colors={["emerald"]}
valueFormatter={(value) => `$${value.toLocaleString()}`}
/>
</Card>
<Card>
<Title>Performance Metrics</Title>
<div className="mt-6 space-y-4">
<div className="flex justify-between">
<Text>Total Return</Text>
<Badge color="emerald">+12.5%</Badge>
</div>
<div className="flex justify-between">
<Text>Sharpe Ratio</Text>
<Badge color="blue">1.8</Badge>
</div>
<div className="flex justify-between">
<Text>Max Drawdown</Text>
<Badge color="red">-5.2%</Badge>
</div>
<div className="flex justify-between">
<Text>Win Rate</Text>
<Badge color="emerald">68%</Badge>
</div>
<div className="flex justify-between">
<Text>Avg Trade</Text>
<Badge color="indigo">+$245</Badge>
</div>
</div>
</Card>
</Grid>
</TabPanel>
</TabPanels>
</TabGroup>
</div>
</div>
);
}

View file

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

View file

@ -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(
<StrictMode>
<App />
</StrictMode>,
)

View file

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

View file

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

View file

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

View file

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

View file

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

718
bun.lock Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

34
docker-compose.dev.yml Normal file
View file

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

151
docker-compose.yml Normal file
View file

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

233
docs/current-system-flow.md Normal file
View file

@ -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<br/>Alpha Vantage Ready<br/>Yahoo Finance Ready]
end
%% Currently Active Services
subgraph "Active Services"
MDG[Market Data Gateway<br/>Port 3001<br/>✅ Running]
TD[Trading Dashboard<br/>Port 5173<br/>✅ Running]
end
%% Storage Layer
subgraph "Storage"
DRAGONFLY[(Dragonfly<br/>📡 Events & Cache<br/>🔧 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<br/>✅ Operational]
TD[Trading Dashboard<br/>✅ Operational]
end
%% Next Phase Services
subgraph "Next Phase - Priority 1"
SO[Strategy Orchestrator<br/>🚧 Package Created<br/>📋 Ready to Implement]
RG[Risk Guardian<br/>🚧 Package Created<br/>📋 Ready to Implement]
end
%% Communication Infrastructure
subgraph "Event Infrastructure"
DRAGONFLY[(Dragonfly Streams<br/>✅ Configured)]
WS[WebSocket Server<br/>✅ 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.

358
docs/system-architecture.md Normal file
View file

@ -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<br/>:3001]
RG[Risk Guardian<br/>:3002]
EE[Execution Engine<br/>:3003]
PM[Portfolio Manager<br/>:3004]
end
%% Intelligence Services Layer
subgraph "Intelligence Services"
SO[Strategy Orchestrator<br/>:4001]
SG[Signal Generator<br/>:4002]
BA[Backtesting Engine<br/>:4003]
ML[ML Pipeline<br/>:4004]
end
%% Data Services Layer
subgraph "Data Services"
HDS[Historical Data Service<br/>:5001]
AS[Analytics Service<br/>:5002]
DQS[Data Quality Service<br/>:5003]
ETLS[ETL Service<br/>:5004]
end
%% Platform Services Layer
subgraph "Platform Services"
LM[Log Manager<br/>:6001]
CM[Config Manager<br/>:6002]
AM[Alert Manager<br/>:6003]
SM[Service Monitor<br/>:6004]
end
%% Integration Services Layer
subgraph "Integration Services"
BAS[Broker Adapter<br/>:7001]
DAS[Data Adapter<br/>:7002]
NS[Notification Service<br/>:7003]
WHS[Webhook Service<br/>:7004]
end
%% Interface Services Layer
subgraph "Interface Services"
TD[Trading Dashboard<br/>:5173]
API[REST API Gateway<br/>:8001]
WS[WebSocket Server<br/>Embedded]
end
%% Storage Layer
subgraph "Storage Layer"
DRAGONFLY[(Dragonfly<br/>Events & Cache)]
QDB[(QuestDB<br/>Time Series)]
PGDB[(PostgreSQL<br/>Relational)]
FS[(File System<br/>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

View file

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

View file

@ -0,0 +1,9 @@
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: true

45
monitoring/prometheus.yml Normal file
View file

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

View file

@ -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=<job_name>` 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']

43
package.json Normal file
View file

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

View file

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

View file

@ -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<string, BrokerConfig> = {
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<string, DataProviderConfig> = {
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;
}
};

View file

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

View file

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

View file

@ -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<string, any>;
}
export interface Strategy {
id: string;
name: string;
description: string;
isActive: boolean;
riskLimits: RiskLimits;
parameters: Record<string, any>;
}
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<string, any>;
}
// API Response Types
export interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
timestamp: Date;
}
export interface PaginatedResponse<T> {
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;
};
}

View file

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

143
scripts/docker.ps1 Normal file
View file

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

33
turbo.json Normal file
View file

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