refactored monorepo for more projects
This commit is contained in:
parent
4632c174dc
commit
9492f1b15e
180 changed files with 1438 additions and 424 deletions
|
|
@ -1,97 +0,0 @@
|
||||||
{
|
|
||||||
"service": {
|
|
||||||
"name": "data-ingestion",
|
|
||||||
"port": 2001,
|
|
||||||
"host": "0.0.0.0",
|
|
||||||
"healthCheckPath": "/health",
|
|
||||||
"metricsPath": "/metrics",
|
|
||||||
"shutdownTimeout": 30000,
|
|
||||||
"cors": {
|
|
||||||
"enabled": true,
|
|
||||||
"origin": "*",
|
|
||||||
"credentials": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"log": {
|
|
||||||
"level": "info",
|
|
||||||
"format": "json",
|
|
||||||
"hideObject": false,
|
|
||||||
"loki": {
|
|
||||||
"enabled": false,
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 3100,
|
|
||||||
"labels": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"database": {
|
|
||||||
"postgres": {
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 5432,
|
|
||||||
"database": "trading_bot",
|
|
||||||
"user": "trading_user",
|
|
||||||
"password": "trading_pass_dev",
|
|
||||||
"ssl": false,
|
|
||||||
"poolSize": 20,
|
|
||||||
"connectionTimeout": 30000,
|
|
||||||
"idleTimeout": 10000
|
|
||||||
},
|
|
||||||
"questdb": {
|
|
||||||
"host": "localhost",
|
|
||||||
"ilpPort": 9009,
|
|
||||||
"httpPort": 9000,
|
|
||||||
"pgPort": 8812,
|
|
||||||
"database": "questdb",
|
|
||||||
"user": "admin",
|
|
||||||
"password": "quest",
|
|
||||||
"bufferSize": 65536,
|
|
||||||
"flushInterval": 1000
|
|
||||||
},
|
|
||||||
"mongodb": {
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 27017,
|
|
||||||
"database": "stock",
|
|
||||||
"user": "trading_admin",
|
|
||||||
"password": "trading_mongo_dev",
|
|
||||||
"authSource": "admin",
|
|
||||||
"poolSize": 20
|
|
||||||
},
|
|
||||||
"dragonfly": {
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 6379,
|
|
||||||
"db": 0,
|
|
||||||
"maxRetries": 3,
|
|
||||||
"retryDelay": 100
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"queue": {
|
|
||||||
"redis": {
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 6379,
|
|
||||||
"db": 0
|
|
||||||
},
|
|
||||||
"defaultJobOptions": {
|
|
||||||
"attempts": 3,
|
|
||||||
"backoff": {
|
|
||||||
"type": "exponential",
|
|
||||||
"delay": 1000
|
|
||||||
},
|
|
||||||
"removeOnComplete": 100,
|
|
||||||
"removeOnFail": 50
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"webshare": {
|
|
||||||
"apiKey": "",
|
|
||||||
"apiUrl": "https://proxy.webshare.io/api/v2/"
|
|
||||||
},
|
|
||||||
"http": {
|
|
||||||
"timeout": 30000,
|
|
||||||
"retries": 3,
|
|
||||||
"retryDelay": 1000,
|
|
||||||
"userAgent": "StockBot/1.0",
|
|
||||||
"rateLimit": {
|
|
||||||
"enabled": false,
|
|
||||||
"requestsPerSecond": 10,
|
|
||||||
"burstSize": 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"service": {
|
|
||||||
"name": "data-pipeline",
|
|
||||||
"port": 3005,
|
|
||||||
"host": "0.0.0.0",
|
|
||||||
"healthCheckPath": "/health",
|
|
||||||
"metricsPath": "/metrics",
|
|
||||||
"shutdownTimeout": 30000,
|
|
||||||
"cors": {
|
|
||||||
"enabled": true,
|
|
||||||
"origin": "*",
|
|
||||||
"credentials": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
124
apps/stock/README.md
Normal file
124
apps/stock/README.md
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
# Stock Trading Bot Application
|
||||||
|
|
||||||
|
A comprehensive stock trading bot application with multiple microservices for data ingestion, processing, and API access.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The stock bot consists of the following services:
|
||||||
|
|
||||||
|
- **Config**: Centralized configuration management
|
||||||
|
- **Data Ingestion**: Handles real-time and historical data collection
|
||||||
|
- **Data Pipeline**: Processes and transforms market data
|
||||||
|
- **Web API**: RESTful API for accessing stock data
|
||||||
|
- **Web App**: Frontend user interface
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Node.js >= 18.0.0
|
||||||
|
- Bun >= 1.1.0
|
||||||
|
- Turbo
|
||||||
|
- PostgreSQL, MongoDB, QuestDB, and Redis/Dragonfly running locally
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install all dependencies
|
||||||
|
bun install
|
||||||
|
|
||||||
|
# Build the configuration package first
|
||||||
|
bun run build:config
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all services in development mode (using Turbo)
|
||||||
|
bun run dev
|
||||||
|
|
||||||
|
# Run only backend services
|
||||||
|
bun run dev:backend
|
||||||
|
|
||||||
|
# Run only frontend
|
||||||
|
bun run dev:frontend
|
||||||
|
|
||||||
|
# Run specific service
|
||||||
|
bun run dev:ingestion
|
||||||
|
bun run dev:pipeline
|
||||||
|
bun run dev:api
|
||||||
|
bun run dev:web
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build all services (using Turbo)
|
||||||
|
bun run build
|
||||||
|
|
||||||
|
# Start with PM2
|
||||||
|
bun run pm2:start
|
||||||
|
|
||||||
|
# Check status
|
||||||
|
bun run pm2:status
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
bun run pm2:logs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
Configuration is managed centrally in the `config` package.
|
||||||
|
|
||||||
|
- Default config: `config/config/default.json`
|
||||||
|
- Environment-specific: `config/config/[environment].json`
|
||||||
|
- Environment variables: Can override any config value
|
||||||
|
|
||||||
|
### Health Checks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check all services health
|
||||||
|
bun run health:check
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run migrations
|
||||||
|
bun run db:migrate
|
||||||
|
|
||||||
|
# Seed database
|
||||||
|
bun run db:seed
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
| Script | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `dev` | Run all services in development mode |
|
||||||
|
| `build` | Build all services |
|
||||||
|
| `start` | Start all backend services |
|
||||||
|
| `test` | Run tests for all services |
|
||||||
|
| `lint` | Lint all services |
|
||||||
|
| `clean` | Clean build artifacts and dependencies |
|
||||||
|
| `docker:build` | Build Docker images |
|
||||||
|
| `pm2:start` | Start services with PM2 |
|
||||||
|
| `health:check` | Check health of all services |
|
||||||
|
|
||||||
|
## Service Ports
|
||||||
|
|
||||||
|
- Data Ingestion: 2001
|
||||||
|
- Data Pipeline: 2002
|
||||||
|
- Web API: 2003
|
||||||
|
- Web App: 3000 (or next available)
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Key environment variables:
|
||||||
|
|
||||||
|
- `NODE_ENV`: development, test, or production
|
||||||
|
- `PORT`: Override default service port
|
||||||
|
- Database connection strings
|
||||||
|
- API keys for data providers
|
||||||
|
|
||||||
|
See `config/config/default.json` for full configuration options.
|
||||||
223
apps/stock/config/config/default.json
Normal file
223
apps/stock/config/config/default.json
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
{
|
||||||
|
"name": "stock-bot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"environment": "development",
|
||||||
|
"service": {
|
||||||
|
"name": "stock-bot",
|
||||||
|
"port": 3000,
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"healthCheckPath": "/health",
|
||||||
|
"metricsPath": "/metrics",
|
||||||
|
"shutdownTimeout": 30000,
|
||||||
|
"cors": {
|
||||||
|
"enabled": true,
|
||||||
|
"origin": "*",
|
||||||
|
"credentials": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"database": {
|
||||||
|
"postgres": {
|
||||||
|
"enabled": true,
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 5432,
|
||||||
|
"database": "trading_bot",
|
||||||
|
"user": "trading_user",
|
||||||
|
"password": "trading_pass_dev",
|
||||||
|
"ssl": false,
|
||||||
|
"poolSize": 20,
|
||||||
|
"connectionTimeout": 30000,
|
||||||
|
"idleTimeout": 10000
|
||||||
|
},
|
||||||
|
"questdb": {
|
||||||
|
"host": "localhost",
|
||||||
|
"ilpPort": 9009,
|
||||||
|
"httpPort": 9000,
|
||||||
|
"pgPort": 8812,
|
||||||
|
"database": "questdb",
|
||||||
|
"user": "admin",
|
||||||
|
"password": "quest",
|
||||||
|
"bufferSize": 65536,
|
||||||
|
"flushInterval": 1000
|
||||||
|
},
|
||||||
|
"mongodb": {
|
||||||
|
"uri": "mongodb://trading_admin:trading_mongo_dev@localhost:27017/stock?authSource=admin",
|
||||||
|
"database": "stock",
|
||||||
|
"poolSize": 20
|
||||||
|
},
|
||||||
|
"dragonfly": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 6379,
|
||||||
|
"db": 0,
|
||||||
|
"keyPrefix": "stock-bot:",
|
||||||
|
"maxRetries": 3,
|
||||||
|
"retryDelay": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"log": {
|
||||||
|
"level": "info",
|
||||||
|
"format": "json",
|
||||||
|
"hideObject": false,
|
||||||
|
"loki": {
|
||||||
|
"enabled": false,
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 3100,
|
||||||
|
"labels": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"redis": {
|
||||||
|
"enabled": true,
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 6379,
|
||||||
|
"db": 0
|
||||||
|
},
|
||||||
|
"queue": {
|
||||||
|
"enabled": true,
|
||||||
|
"redis": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 6379,
|
||||||
|
"db": 1
|
||||||
|
},
|
||||||
|
"workers": 5,
|
||||||
|
"concurrency": 2,
|
||||||
|
"enableScheduledJobs": true,
|
||||||
|
"delayWorkerStart": false,
|
||||||
|
"defaultJobOptions": {
|
||||||
|
"attempts": 3,
|
||||||
|
"backoff": {
|
||||||
|
"type": "exponential",
|
||||||
|
"delay": 1000
|
||||||
|
},
|
||||||
|
"removeOnComplete": 100,
|
||||||
|
"removeOnFail": 50,
|
||||||
|
"timeout": 300000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"http": {
|
||||||
|
"timeout": 30000,
|
||||||
|
"retries": 3,
|
||||||
|
"retryDelay": 1000,
|
||||||
|
"userAgent": "StockBot/1.0",
|
||||||
|
"proxy": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webshare": {
|
||||||
|
"apiKey": "",
|
||||||
|
"apiUrl": "https://proxy.webshare.io/api/v2/",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"browser": {
|
||||||
|
"headless": true,
|
||||||
|
"timeout": 30000
|
||||||
|
},
|
||||||
|
"proxy": {
|
||||||
|
"cachePrefix": "proxy:",
|
||||||
|
"ttl": 3600
|
||||||
|
},
|
||||||
|
"providers": {
|
||||||
|
"yahoo": {
|
||||||
|
"name": "yahoo",
|
||||||
|
"enabled": true,
|
||||||
|
"priority": 1,
|
||||||
|
"rateLimit": {
|
||||||
|
"maxRequests": 5,
|
||||||
|
"windowMs": 60000
|
||||||
|
},
|
||||||
|
"timeout": 30000,
|
||||||
|
"baseUrl": "https://query1.finance.yahoo.com"
|
||||||
|
},
|
||||||
|
"qm": {
|
||||||
|
"name": "qm",
|
||||||
|
"enabled": false,
|
||||||
|
"priority": 2,
|
||||||
|
"username": "",
|
||||||
|
"password": "",
|
||||||
|
"baseUrl": "https://app.quotemedia.com/quotetools",
|
||||||
|
"webmasterId": ""
|
||||||
|
},
|
||||||
|
"ib": {
|
||||||
|
"name": "ib",
|
||||||
|
"enabled": false,
|
||||||
|
"priority": 3,
|
||||||
|
"gateway": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 5000,
|
||||||
|
"clientId": 1
|
||||||
|
},
|
||||||
|
"marketDataType": "delayed"
|
||||||
|
},
|
||||||
|
"eod": {
|
||||||
|
"name": "eod",
|
||||||
|
"enabled": false,
|
||||||
|
"priority": 4,
|
||||||
|
"apiKey": "",
|
||||||
|
"baseUrl": "https://eodhistoricaldata.com/api",
|
||||||
|
"tier": "free"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"realtime": true,
|
||||||
|
"backtesting": true,
|
||||||
|
"paperTrading": true,
|
||||||
|
"autoTrading": false,
|
||||||
|
"historicalData": true,
|
||||||
|
"realtimeData": true,
|
||||||
|
"fundamentalData": true,
|
||||||
|
"newsAnalysis": false,
|
||||||
|
"notifications": false,
|
||||||
|
"emailAlerts": false,
|
||||||
|
"smsAlerts": false,
|
||||||
|
"webhookAlerts": false,
|
||||||
|
"technicalAnalysis": true,
|
||||||
|
"sentimentAnalysis": false,
|
||||||
|
"patternRecognition": false,
|
||||||
|
"riskManagement": true,
|
||||||
|
"positionSizing": true,
|
||||||
|
"stopLoss": true,
|
||||||
|
"takeProfit": true
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"dataIngestion": {
|
||||||
|
"port": 2001,
|
||||||
|
"workers": 4,
|
||||||
|
"queues": {
|
||||||
|
"ceo": { "concurrency": 2 },
|
||||||
|
"webshare": { "concurrency": 1 },
|
||||||
|
"qm": { "concurrency": 2 },
|
||||||
|
"ib": { "concurrency": 1 },
|
||||||
|
"proxy": { "concurrency": 1 }
|
||||||
|
},
|
||||||
|
"rateLimit": {
|
||||||
|
"enabled": true,
|
||||||
|
"requestsPerSecond": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dataPipeline": {
|
||||||
|
"port": 2002,
|
||||||
|
"workers": 2,
|
||||||
|
"batchSize": 1000,
|
||||||
|
"processingInterval": 60000,
|
||||||
|
"queues": {
|
||||||
|
"exchanges": { "concurrency": 1 },
|
||||||
|
"symbols": { "concurrency": 2 }
|
||||||
|
},
|
||||||
|
"syncOptions": {
|
||||||
|
"maxRetries": 3,
|
||||||
|
"retryDelay": 5000,
|
||||||
|
"timeout": 300000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webApi": {
|
||||||
|
"port": 2003,
|
||||||
|
"rateLimitPerMinute": 60,
|
||||||
|
"cache": {
|
||||||
|
"ttl": 300,
|
||||||
|
"checkPeriod": 60
|
||||||
|
},
|
||||||
|
"cors": {
|
||||||
|
"origins": ["http://localhost:3000", "http://localhost:4200"],
|
||||||
|
"credentials": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
apps/stock/config/config/development.json
Normal file
11
apps/stock/config/config/development.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"environment": "development",
|
||||||
|
"log": {
|
||||||
|
"level": "debug",
|
||||||
|
"format": "pretty"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"autoTrading": false,
|
||||||
|
"paperTrading": true
|
||||||
|
}
|
||||||
|
}
|
||||||
42
apps/stock/config/config/production.json
Normal file
42
apps/stock/config/config/production.json
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"log": {
|
||||||
|
"level": "warn",
|
||||||
|
"format": "json",
|
||||||
|
"loki": {
|
||||||
|
"enabled": true,
|
||||||
|
"host": "loki.production.example.com",
|
||||||
|
"port": 3100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"database": {
|
||||||
|
"postgres": {
|
||||||
|
"host": "postgres.production.example.com",
|
||||||
|
"ssl": true,
|
||||||
|
"poolSize": 50
|
||||||
|
},
|
||||||
|
"questdb": {
|
||||||
|
"host": "questdb.production.example.com"
|
||||||
|
},
|
||||||
|
"mongodb": {
|
||||||
|
"uri": "mongodb+srv://prod_user:prod_pass@cluster.mongodb.net/stock?retryWrites=true&w=majority",
|
||||||
|
"poolSize": 50
|
||||||
|
},
|
||||||
|
"dragonfly": {
|
||||||
|
"host": "redis.production.example.com",
|
||||||
|
"password": "production_redis_password"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"queue": {
|
||||||
|
"redis": {
|
||||||
|
"host": "redis.production.example.com",
|
||||||
|
"password": "production_redis_password"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"autoTrading": true,
|
||||||
|
"notifications": true,
|
||||||
|
"emailAlerts": true,
|
||||||
|
"webhookAlerts": true
|
||||||
|
}
|
||||||
|
}
|
||||||
22
apps/stock/config/package.json
Normal file
22
apps/stock/config/package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "@stock-bot/stock-config",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Stock trading bot configuration",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"clean": "rm -rf dist",
|
||||||
|
"dev": "tsc --watch",
|
||||||
|
"test": "jest",
|
||||||
|
"lint": "eslint src --ext .ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@stock-bot/config": "*",
|
||||||
|
"zod": "^3.22.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
83
apps/stock/config/src/config-instance.ts
Normal file
83
apps/stock/config/src/config-instance.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { ConfigManager, createAppConfig } from '@stock-bot/config';
|
||||||
|
import { stockAppSchema, type StockAppConfig } from './schemas';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
let configInstance: ConfigManager<StockAppConfig> | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the stock application configuration
|
||||||
|
* @param serviceName - Optional service name to override port configuration
|
||||||
|
*/
|
||||||
|
export function initializeStockConfig(serviceName?: 'dataIngestion' | 'dataPipeline' | 'webApi'): StockAppConfig {
|
||||||
|
try {
|
||||||
|
if (!configInstance) {
|
||||||
|
configInstance = createAppConfig(stockAppSchema, {
|
||||||
|
configPath: path.join(__dirname, '../config'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = configInstance.initialize(stockAppSchema);
|
||||||
|
|
||||||
|
// If a service name is provided, override the service port
|
||||||
|
if (serviceName && config.services?.[serviceName]) {
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
service: {
|
||||||
|
...config.service,
|
||||||
|
port: config.services[serviceName].port,
|
||||||
|
name: serviceName.replace(/([A-Z])/g, '-$1').toLowerCase() // Convert camelCase to kebab-case
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Failed to initialize stock configuration:', error.message);
|
||||||
|
if (error.errors) {
|
||||||
|
console.error('Validation errors:', JSON.stringify(error.errors, null, 2));
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current stock configuration
|
||||||
|
*/
|
||||||
|
export function getStockConfig(): StockAppConfig {
|
||||||
|
if (!configInstance) {
|
||||||
|
// Auto-initialize if not already done
|
||||||
|
return initializeStockConfig();
|
||||||
|
}
|
||||||
|
return configInstance.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get configuration for a specific service
|
||||||
|
*/
|
||||||
|
export function getServiceConfig(service: 'dataIngestion' | 'dataPipeline' | 'webApi') {
|
||||||
|
const config = getStockConfig();
|
||||||
|
return config.services?.[service];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get configuration for a specific provider
|
||||||
|
*/
|
||||||
|
export function getProviderConfig(provider: 'eod' | 'ib' | 'qm' | 'yahoo') {
|
||||||
|
const config = getStockConfig();
|
||||||
|
return config.providers[provider];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a feature is enabled
|
||||||
|
*/
|
||||||
|
export function isFeatureEnabled(feature: keyof StockAppConfig['features']): boolean {
|
||||||
|
const config = getStockConfig();
|
||||||
|
return config.features[feature];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset configuration (useful for testing)
|
||||||
|
*/
|
||||||
|
export function resetStockConfig(): void {
|
||||||
|
configInstance = null;
|
||||||
|
}
|
||||||
15
apps/stock/config/src/index.ts
Normal file
15
apps/stock/config/src/index.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// Export schemas
|
||||||
|
export * from './schemas';
|
||||||
|
|
||||||
|
// Export config instance functions
|
||||||
|
export {
|
||||||
|
initializeStockConfig,
|
||||||
|
getStockConfig,
|
||||||
|
getServiceConfig,
|
||||||
|
getProviderConfig,
|
||||||
|
isFeatureEnabled,
|
||||||
|
resetStockConfig,
|
||||||
|
} from './config-instance';
|
||||||
|
|
||||||
|
// Re-export type for convenience
|
||||||
|
export type { StockAppConfig } from './schemas/stock-app.schema';
|
||||||
35
apps/stock/config/src/schemas/features.schema.ts
Normal file
35
apps/stock/config/src/schemas/features.schema.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature flags for the stock trading application
|
||||||
|
*/
|
||||||
|
export const featuresSchema = z.object({
|
||||||
|
// Trading features
|
||||||
|
realtime: z.boolean().default(true),
|
||||||
|
backtesting: z.boolean().default(true),
|
||||||
|
paperTrading: z.boolean().default(true),
|
||||||
|
autoTrading: z.boolean().default(false),
|
||||||
|
|
||||||
|
// Data features
|
||||||
|
historicalData: z.boolean().default(true),
|
||||||
|
realtimeData: z.boolean().default(true),
|
||||||
|
fundamentalData: z.boolean().default(true),
|
||||||
|
newsAnalysis: z.boolean().default(false),
|
||||||
|
|
||||||
|
// Notification features
|
||||||
|
notifications: z.boolean().default(false),
|
||||||
|
emailAlerts: z.boolean().default(false),
|
||||||
|
smsAlerts: z.boolean().default(false),
|
||||||
|
webhookAlerts: z.boolean().default(false),
|
||||||
|
|
||||||
|
// Analysis features
|
||||||
|
technicalAnalysis: z.boolean().default(true),
|
||||||
|
sentimentAnalysis: z.boolean().default(false),
|
||||||
|
patternRecognition: z.boolean().default(false),
|
||||||
|
|
||||||
|
// Risk management
|
||||||
|
riskManagement: z.boolean().default(true),
|
||||||
|
positionSizing: z.boolean().default(true),
|
||||||
|
stopLoss: z.boolean().default(true),
|
||||||
|
takeProfit: z.boolean().default(true),
|
||||||
|
});
|
||||||
3
apps/stock/config/src/schemas/index.ts
Normal file
3
apps/stock/config/src/schemas/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './stock-app.schema';
|
||||||
|
export * from './providers.schema';
|
||||||
|
export * from './features.schema';
|
||||||
67
apps/stock/config/src/schemas/providers.schema.ts
Normal file
67
apps/stock/config/src/schemas/providers.schema.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
// Base provider configuration
|
||||||
|
export const baseProviderConfigSchema = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
priority: z.number().default(0),
|
||||||
|
rateLimit: z
|
||||||
|
.object({
|
||||||
|
maxRequests: z.number().default(100),
|
||||||
|
windowMs: z.number().default(60000),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
timeout: z.number().default(30000),
|
||||||
|
retries: z.number().default(3),
|
||||||
|
});
|
||||||
|
|
||||||
|
// EOD Historical Data provider
|
||||||
|
export const eodProviderConfigSchema = baseProviderConfigSchema.extend({
|
||||||
|
apiKey: z.string(),
|
||||||
|
baseUrl: z.string().default('https://eodhistoricaldata.com/api'),
|
||||||
|
tier: z.enum(['free', 'fundamentals', 'all-in-one']).default('free'),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Interactive Brokers provider
|
||||||
|
export const ibProviderConfigSchema = baseProviderConfigSchema.extend({
|
||||||
|
gateway: z.object({
|
||||||
|
host: z.string().default('localhost'),
|
||||||
|
port: z.number().default(5000),
|
||||||
|
clientId: z.number().default(1),
|
||||||
|
}),
|
||||||
|
account: z.string().optional(),
|
||||||
|
marketDataType: z.enum(['live', 'delayed', 'frozen']).default('delayed'),
|
||||||
|
});
|
||||||
|
|
||||||
|
// QuoteMedia provider
|
||||||
|
export const qmProviderConfigSchema = baseProviderConfigSchema.extend({
|
||||||
|
username: z.string(),
|
||||||
|
password: z.string(),
|
||||||
|
baseUrl: z.string().default('https://app.quotemedia.com/quotetools'),
|
||||||
|
webmasterId: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Yahoo Finance provider
|
||||||
|
export const yahooProviderConfigSchema = baseProviderConfigSchema.extend({
|
||||||
|
baseUrl: z.string().default('https://query1.finance.yahoo.com'),
|
||||||
|
cookieJar: z.boolean().default(true),
|
||||||
|
crumb: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Combined provider configuration
|
||||||
|
export const providersSchema = z.object({
|
||||||
|
eod: eodProviderConfigSchema.optional(),
|
||||||
|
ib: ibProviderConfigSchema.optional(),
|
||||||
|
qm: qmProviderConfigSchema.optional(),
|
||||||
|
yahoo: yahooProviderConfigSchema.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Dynamic provider configuration type
|
||||||
|
export type ProviderName = 'eod' | 'ib' | 'qm' | 'yahoo';
|
||||||
|
|
||||||
|
export const providerSchemas = {
|
||||||
|
eod: eodProviderConfigSchema,
|
||||||
|
ib: ibProviderConfigSchema,
|
||||||
|
qm: qmProviderConfigSchema,
|
||||||
|
yahoo: yahooProviderConfigSchema,
|
||||||
|
} as const;
|
||||||
72
apps/stock/config/src/schemas/stock-app.schema.ts
Normal file
72
apps/stock/config/src/schemas/stock-app.schema.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { z } from 'zod';
|
||||||
|
import {
|
||||||
|
baseAppSchema,
|
||||||
|
postgresConfigSchema,
|
||||||
|
mongodbConfigSchema,
|
||||||
|
questdbConfigSchema,
|
||||||
|
dragonflyConfigSchema
|
||||||
|
} from '@stock-bot/config';
|
||||||
|
import { providersSchema } from './providers.schema';
|
||||||
|
import { featuresSchema } from './features.schema';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stock trading application configuration schema
|
||||||
|
*/
|
||||||
|
export const stockAppSchema = baseAppSchema.extend({
|
||||||
|
// Stock app uses all databases
|
||||||
|
database: z.object({
|
||||||
|
postgres: postgresConfigSchema,
|
||||||
|
mongodb: mongodbConfigSchema,
|
||||||
|
questdb: questdbConfigSchema,
|
||||||
|
dragonfly: dragonflyConfigSchema,
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Stock-specific providers
|
||||||
|
providers: providersSchema,
|
||||||
|
|
||||||
|
// Feature flags
|
||||||
|
features: featuresSchema,
|
||||||
|
|
||||||
|
// Service-specific configurations
|
||||||
|
services: z.object({
|
||||||
|
dataIngestion: z.object({
|
||||||
|
port: z.number().default(2001),
|
||||||
|
workers: z.number().default(4),
|
||||||
|
queues: z.record(z.object({
|
||||||
|
concurrency: z.number().default(1),
|
||||||
|
})).optional(),
|
||||||
|
rateLimit: z.object({
|
||||||
|
enabled: z.boolean().default(true),
|
||||||
|
requestsPerSecond: z.number().default(10),
|
||||||
|
}).optional(),
|
||||||
|
}).optional(),
|
||||||
|
dataPipeline: z.object({
|
||||||
|
port: z.number().default(2002),
|
||||||
|
workers: z.number().default(2),
|
||||||
|
batchSize: z.number().default(1000),
|
||||||
|
processingInterval: z.number().default(60000),
|
||||||
|
queues: z.record(z.object({
|
||||||
|
concurrency: z.number().default(1),
|
||||||
|
})).optional(),
|
||||||
|
syncOptions: z.object({
|
||||||
|
maxRetries: z.number().default(3),
|
||||||
|
retryDelay: z.number().default(5000),
|
||||||
|
timeout: z.number().default(300000),
|
||||||
|
}).optional(),
|
||||||
|
}).optional(),
|
||||||
|
webApi: z.object({
|
||||||
|
port: z.number().default(2003),
|
||||||
|
rateLimitPerMinute: z.number().default(60),
|
||||||
|
cache: z.object({
|
||||||
|
ttl: z.number().default(300),
|
||||||
|
checkPeriod: z.number().default(60),
|
||||||
|
}).optional(),
|
||||||
|
cors: z.object({
|
||||||
|
origins: z.array(z.string()).default(['http://localhost:3000']),
|
||||||
|
credentials: z.boolean().default(true),
|
||||||
|
}).optional(),
|
||||||
|
}).optional(),
|
||||||
|
}).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type StockAppConfig = z.infer<typeof stockAppSchema>;
|
||||||
15
apps/stock/config/tsconfig.json
Normal file
15
apps/stock/config/tsconfig.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"extends": "../../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"composite": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist", "**/*.test.ts"],
|
||||||
|
"references": [
|
||||||
|
{ "path": "../../../libs/core/config" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@stock-bot/cache": "*",
|
"@stock-bot/cache": "*",
|
||||||
"@stock-bot/config": "*",
|
"@stock-bot/config": "*",
|
||||||
|
"@stock-bot/stock-config": "*",
|
||||||
"@stock-bot/di": "*",
|
"@stock-bot/di": "*",
|
||||||
"@stock-bot/handlers": "*",
|
"@stock-bot/handlers": "*",
|
||||||
"@stock-bot/logger": "*",
|
"@stock-bot/logger": "*",
|
||||||
|
|
@ -12,17 +12,35 @@ export async function fetchWebShareProxies(): Promise<ProxyInfo[]> {
|
||||||
const ctx = OperationContext.create('webshare', 'fetch-proxies');
|
const ctx = OperationContext.create('webshare', 'fetch-proxies');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get configuration from config system
|
// Get configuration from stock config system - ensure it's initialized
|
||||||
const { getConfig } = await import('@stock-bot/config');
|
const { getStockConfig, initializeStockConfig } = await import('@stock-bot/stock-config');
|
||||||
const config = getConfig();
|
|
||||||
|
// Try to get existing config, or initialize if needed
|
||||||
|
let config;
|
||||||
|
try {
|
||||||
|
config = getStockConfig();
|
||||||
|
} catch (error) {
|
||||||
|
// Config not initialized yet, initialize it
|
||||||
|
config = initializeStockConfig('dataIngestion');
|
||||||
|
}
|
||||||
|
|
||||||
const apiKey = config.webshare?.apiKey;
|
const apiKey = config.webshare?.apiKey;
|
||||||
const apiUrl = config.webshare?.apiUrl;
|
const apiUrl = config.webshare?.apiUrl;
|
||||||
|
|
||||||
|
ctx.logger.debug('WebShare config loaded', {
|
||||||
|
hasConfig: !!config,
|
||||||
|
hasWebshare: !!config.webshare,
|
||||||
|
webshareConfig: config.webshare,
|
||||||
|
apiKeyLength: apiKey?.length || 0,
|
||||||
|
apiUrl: apiUrl,
|
||||||
|
envApiKey: process.env.WEBSHARE_API_KEY ? 'SET' : 'NOT_SET',
|
||||||
|
});
|
||||||
|
|
||||||
if (!apiKey || !apiUrl) {
|
if (!apiKey || !apiUrl) {
|
||||||
ctx.logger.error('Missing WebShare configuration', {
|
ctx.logger.error('Missing WebShare configuration', {
|
||||||
hasApiKey: !!apiKey,
|
hasApiKey: !!apiKey,
|
||||||
hasApiUrl: !!apiUrl,
|
hasApiUrl: !!apiUrl,
|
||||||
|
apiKeyValue: apiKey ? `${apiKey.substring(0, 5)}...` : 'NOT_SET',
|
||||||
});
|
});
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* Simplified entry point using ServiceApplication framework
|
* Simplified entry point using ServiceApplication framework
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { initializeServiceConfig } from '@stock-bot/config';
|
import { initializeStockConfig } from '@stock-bot/stock-config';
|
||||||
import {
|
import {
|
||||||
ServiceApplication,
|
ServiceApplication,
|
||||||
} from '@stock-bot/di';
|
} from '@stock-bot/di';
|
||||||
|
|
@ -13,9 +13,9 @@ import { getLogger } from '@stock-bot/logger';
|
||||||
import { initializeAllHandlers } from './handlers';
|
import { initializeAllHandlers } from './handlers';
|
||||||
import { createRoutes } from './routes/create-routes';
|
import { createRoutes } from './routes/create-routes';
|
||||||
|
|
||||||
// Initialize configuration
|
// Initialize configuration with service-specific overrides
|
||||||
const config = initializeServiceConfig();
|
const config = initializeStockConfig('dataIngestion');
|
||||||
console.log('Data Service Configuration:', JSON.stringify(config, null, 2));
|
console.log('Data Ingestion Service Configuration:', JSON.stringify(config, null, 2));
|
||||||
|
|
||||||
// Create service application
|
// Create service application
|
||||||
const app = new ServiceApplication(
|
const app = new ServiceApplication(
|
||||||
|
|
@ -57,7 +57,7 @@ async function createContainer(config: any) {
|
||||||
.withOptions({
|
.withOptions({
|
||||||
enableQuestDB: false, // Data ingestion doesn't need QuestDB yet
|
enableQuestDB: false, // Data ingestion doesn't need QuestDB yet
|
||||||
enableMongoDB: true,
|
enableMongoDB: true,
|
||||||
enablePostgres: true,
|
enablePostgres: config.database?.postgres?.enabled ?? false,
|
||||||
enableCache: true,
|
enableCache: true,
|
||||||
enableQueue: true,
|
enableQueue: true,
|
||||||
enableBrowser: true, // Data ingestion needs browser for web scraping
|
enableBrowser: true, // Data ingestion needs browser for web scraping
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
{ "path": "../../libs/data/questdb" },
|
{ "path": "../../libs/data/questdb" },
|
||||||
{ "path": "../../libs/services/queue" },
|
{ "path": "../../libs/services/queue" },
|
||||||
{ "path": "../../libs/services/shutdown" },
|
{ "path": "../../libs/services/shutdown" },
|
||||||
{ "path": "../../libs/utils" }
|
{ "path": "../../libs/utils" },
|
||||||
|
{ "path": "../config" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@stock-bot/cache": "*",
|
"@stock-bot/cache": "*",
|
||||||
"@stock-bot/config": "*",
|
"@stock-bot/config": "*",
|
||||||
|
"@stock-bot/stock-config": "*",
|
||||||
"@stock-bot/logger": "*",
|
"@stock-bot/logger": "*",
|
||||||
"@stock-bot/mongodb": "*",
|
"@stock-bot/mongodb": "*",
|
||||||
"@stock-bot/postgres": "*",
|
"@stock-bot/postgres": "*",
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* Simplified entry point using ServiceApplication framework
|
* Simplified entry point using ServiceApplication framework
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { initializeServiceConfig } from '@stock-bot/config';
|
import { initializeStockConfig } from '@stock-bot/stock-config';
|
||||||
import {
|
import {
|
||||||
ServiceApplication,
|
ServiceApplication,
|
||||||
createServiceContainerFromConfig,
|
createServiceContainerFromConfig,
|
||||||
|
|
@ -16,8 +16,8 @@ import { initializeAllHandlers } from './handlers';
|
||||||
import { createRoutes } from './routes/create-routes';
|
import { createRoutes } from './routes/create-routes';
|
||||||
import { setupServiceContainer } from './container-setup';
|
import { setupServiceContainer } from './container-setup';
|
||||||
|
|
||||||
// Initialize configuration
|
// Initialize configuration with service-specific overrides
|
||||||
const config = initializeServiceConfig();
|
const config = initializeStockConfig('dataPipeline');
|
||||||
console.log('Data Pipeline Service Configuration:', JSON.stringify(config, null, 2));
|
console.log('Data Pipeline Service Configuration:', JSON.stringify(config, null, 2));
|
||||||
|
|
||||||
// Create service application
|
// Create service application
|
||||||
72
apps/stock/ecosystem.config.js
Normal file
72
apps/stock/ecosystem.config.js
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: 'stock-ingestion',
|
||||||
|
script: './data-ingestion/dist/index.js',
|
||||||
|
instances: 1,
|
||||||
|
autorestart: true,
|
||||||
|
watch: false,
|
||||||
|
max_memory_restart: '1G',
|
||||||
|
env: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
PORT: 2001
|
||||||
|
},
|
||||||
|
env_development: {
|
||||||
|
NODE_ENV: 'development',
|
||||||
|
PORT: 2001
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'stock-pipeline',
|
||||||
|
script: './data-pipeline/dist/index.js',
|
||||||
|
instances: 1,
|
||||||
|
autorestart: true,
|
||||||
|
watch: false,
|
||||||
|
max_memory_restart: '1G',
|
||||||
|
env: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
PORT: 2002
|
||||||
|
},
|
||||||
|
env_development: {
|
||||||
|
NODE_ENV: 'development',
|
||||||
|
PORT: 2002
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'stock-api',
|
||||||
|
script: './web-api/dist/index.js',
|
||||||
|
instances: 2,
|
||||||
|
autorestart: true,
|
||||||
|
watch: false,
|
||||||
|
max_memory_restart: '1G',
|
||||||
|
exec_mode: 'cluster',
|
||||||
|
env: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
PORT: 2003
|
||||||
|
},
|
||||||
|
env_development: {
|
||||||
|
NODE_ENV: 'development',
|
||||||
|
PORT: 2003
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
deploy: {
|
||||||
|
production: {
|
||||||
|
user: 'deploy',
|
||||||
|
host: 'production-server',
|
||||||
|
ref: 'origin/master',
|
||||||
|
repo: 'git@github.com:username/stock-bot.git',
|
||||||
|
path: '/var/www/stock-bot',
|
||||||
|
'post-deploy': 'cd apps/stock && npm install && npm run build && pm2 reload ecosystem.config.js --env production'
|
||||||
|
},
|
||||||
|
staging: {
|
||||||
|
user: 'deploy',
|
||||||
|
host: 'staging-server',
|
||||||
|
ref: 'origin/develop',
|
||||||
|
repo: 'git@github.com:username/stock-bot.git',
|
||||||
|
path: '/var/www/stock-bot-staging',
|
||||||
|
'post-deploy': 'cd apps/stock && npm install && npm run build && pm2 reload ecosystem.config.js --env development'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
91
apps/stock/package.json
Normal file
91
apps/stock/package.json
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
{
|
||||||
|
"name": "@stock-bot/stock-app",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Stock trading bot application",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "turbo run dev",
|
||||||
|
"dev:ingestion": "cd data-ingestion && bun run dev",
|
||||||
|
"dev:pipeline": "cd data-pipeline && bun run dev",
|
||||||
|
"dev:api": "cd web-api && bun run dev",
|
||||||
|
"dev:web": "cd web-app && bun run dev",
|
||||||
|
"dev:backend": "turbo run dev --filter=\"@stock-bot/data-*\" --filter=\"@stock-bot/web-api\"",
|
||||||
|
"dev:frontend": "turbo run dev --filter=\"@stock-bot/web-app\"",
|
||||||
|
|
||||||
|
"build": "turbo run build",
|
||||||
|
"build:config": "cd config && bun run build",
|
||||||
|
"build:services": "turbo run build --filter=\"@stock-bot/data-*\" --filter=\"@stock-bot/web-*\"",
|
||||||
|
"build:ingestion": "cd data-ingestion && bun run build",
|
||||||
|
"build:pipeline": "cd data-pipeline && bun run build",
|
||||||
|
"build:api": "cd web-api && bun run build",
|
||||||
|
"build:web": "cd web-app && bun run build",
|
||||||
|
|
||||||
|
"start": "turbo run start --filter=\"@stock-bot/data-*\" --filter=\"@stock-bot/web-api\"",
|
||||||
|
"start:all": "turbo run start",
|
||||||
|
"start:ingestion": "cd data-ingestion && bun start",
|
||||||
|
"start:pipeline": "cd data-pipeline && bun start",
|
||||||
|
"start:api": "cd web-api && bun start",
|
||||||
|
|
||||||
|
"clean": "turbo run clean",
|
||||||
|
"clean:all": "turbo run clean && rm -rf node_modules",
|
||||||
|
"clean:ingestion": "cd data-ingestion && rm -rf dist node_modules",
|
||||||
|
"clean:pipeline": "cd data-pipeline && rm -rf dist node_modules",
|
||||||
|
"clean:api": "cd web-api && rm -rf dist node_modules",
|
||||||
|
"clean:web": "cd web-app && rm -rf dist node_modules",
|
||||||
|
"clean:config": "cd config && rm -rf dist node_modules",
|
||||||
|
|
||||||
|
"test": "turbo run test",
|
||||||
|
"test:all": "turbo run test",
|
||||||
|
"test:config": "cd config && bun test",
|
||||||
|
"test:services": "turbo run test --filter=\"@stock-bot/data-*\" --filter=\"@stock-bot/web-*\"",
|
||||||
|
"test:ingestion": "cd data-ingestion && bun test",
|
||||||
|
"test:pipeline": "cd data-pipeline && bun test",
|
||||||
|
"test:api": "cd web-api && bun test",
|
||||||
|
|
||||||
|
"lint": "turbo run lint",
|
||||||
|
"lint:all": "turbo run lint",
|
||||||
|
"lint:config": "cd config && bun run lint",
|
||||||
|
"lint:services": "turbo run lint --filter=\"@stock-bot/data-*\" --filter=\"@stock-bot/web-*\"",
|
||||||
|
"lint:ingestion": "cd data-ingestion && bun run lint",
|
||||||
|
"lint:pipeline": "cd data-pipeline && bun run lint",
|
||||||
|
"lint:api": "cd web-api && bun run lint",
|
||||||
|
"lint:web": "cd web-app && bun run lint",
|
||||||
|
|
||||||
|
"install:all": "bun install",
|
||||||
|
|
||||||
|
"docker:build": "docker-compose build",
|
||||||
|
"docker:up": "docker-compose up",
|
||||||
|
"docker:down": "docker-compose down",
|
||||||
|
|
||||||
|
"pm2:start": "pm2 start ecosystem.config.js",
|
||||||
|
"pm2:stop": "pm2 stop all",
|
||||||
|
"pm2:restart": "pm2 restart all",
|
||||||
|
"pm2:logs": "pm2 logs",
|
||||||
|
"pm2:status": "pm2 status",
|
||||||
|
|
||||||
|
"db:migrate": "cd data-ingestion && bun run db:migrate",
|
||||||
|
"db:seed": "cd data-ingestion && bun run db:seed",
|
||||||
|
|
||||||
|
"health:check": "bun scripts/health-check.js",
|
||||||
|
"monitor": "bun run pm2:logs",
|
||||||
|
"status": "bun run pm2:status"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"pm2": "^5.3.0",
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
|
"turbo": "^2.5.4"
|
||||||
|
},
|
||||||
|
"workspaces": [
|
||||||
|
"config",
|
||||||
|
"data-ingestion",
|
||||||
|
"data-pipeline",
|
||||||
|
"web-api",
|
||||||
|
"web-app"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0",
|
||||||
|
"bun": ">=1.1.0"
|
||||||
|
},
|
||||||
|
"packageManager": "bun@1.1.12"
|
||||||
|
}
|
||||||
60
apps/stock/scripts/health-check.js
Executable file
60
apps/stock/scripts/health-check.js
Executable file
|
|
@ -0,0 +1,60 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const http = require('http');
|
||||||
|
const services = [
|
||||||
|
{ name: 'Data Ingestion', port: 2001 },
|
||||||
|
{ name: 'Data Pipeline', port: 2002 },
|
||||||
|
{ name: 'Web API', port: 2003 },
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('🏥 Stock Bot Health Check\n');
|
||||||
|
|
||||||
|
async function checkService(service) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const options = {
|
||||||
|
hostname: 'localhost',
|
||||||
|
port: service.port,
|
||||||
|
path: '/health',
|
||||||
|
method: 'GET',
|
||||||
|
timeout: 5000,
|
||||||
|
};
|
||||||
|
|
||||||
|
const req = http.request(options, (res) => {
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
resolve({ ...service, status: '✅ Healthy', code: res.statusCode });
|
||||||
|
} else {
|
||||||
|
resolve({ ...service, status: '⚠️ Unhealthy', code: res.statusCode });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', (err) => {
|
||||||
|
resolve({ ...service, status: '❌ Offline', error: err.message });
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('timeout', () => {
|
||||||
|
req.destroy();
|
||||||
|
resolve({ ...service, status: '⏱️ Timeout', error: 'Request timed out' });
|
||||||
|
});
|
||||||
|
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkAllServices() {
|
||||||
|
const results = await Promise.all(services.map(checkService));
|
||||||
|
|
||||||
|
results.forEach((result) => {
|
||||||
|
console.log(`${result.name.padEnd(15)} ${result.status}`);
|
||||||
|
if (result.error) {
|
||||||
|
console.log(` ${result.error}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const allHealthy = results.every(r => r.status === '✅ Healthy');
|
||||||
|
|
||||||
|
console.log('\n' + (allHealthy ? '✅ All services are healthy!' : '⚠️ Some services need attention'));
|
||||||
|
|
||||||
|
process.exit(allHealthy ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAllServices();
|
||||||
18
apps/stock/tsconfig.json
Normal file
18
apps/stock/tsconfig.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "../..",
|
||||||
|
"paths": {
|
||||||
|
"@stock-bot/*": ["libs/*/src"],
|
||||||
|
"@stock-bot/stock-config": ["apps/stock/config/src"],
|
||||||
|
"@stock-bot/stock-config/*": ["apps/stock/config/src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"references": [
|
||||||
|
{ "path": "./config" },
|
||||||
|
{ "path": "./data-ingestion" },
|
||||||
|
{ "path": "./data-pipeline" },
|
||||||
|
{ "path": "./web-api" },
|
||||||
|
{ "path": "./web-app" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@stock-bot/config": "*",
|
"@stock-bot/config": "*",
|
||||||
|
"@stock-bot/stock-config": "*",
|
||||||
"@stock-bot/logger": "*",
|
"@stock-bot/logger": "*",
|
||||||
"@stock-bot/mongodb": "*",
|
"@stock-bot/mongodb": "*",
|
||||||
"@stock-bot/postgres": "*",
|
"@stock-bot/postgres": "*",
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* Simplified entry point using ServiceApplication framework
|
* Simplified entry point using ServiceApplication framework
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { initializeServiceConfig } from '@stock-bot/config';
|
import { initializeStockConfig } from '@stock-bot/stock-config';
|
||||||
import {
|
import {
|
||||||
ServiceApplication,
|
ServiceApplication,
|
||||||
createServiceContainerFromConfig,
|
createServiceContainerFromConfig,
|
||||||
|
|
@ -15,8 +15,8 @@ import { getLogger } from '@stock-bot/logger';
|
||||||
import { createRoutes } from './routes/create-routes';
|
import { createRoutes } from './routes/create-routes';
|
||||||
import { setupServiceContainer } from './container-setup';
|
import { setupServiceContainer } from './container-setup';
|
||||||
|
|
||||||
// Initialize configuration
|
// Initialize configuration with service-specific overrides
|
||||||
const config = initializeServiceConfig();
|
const config = initializeStockConfig('webApi');
|
||||||
console.log('Web API Service Configuration:', JSON.stringify(config, null, 2));
|
console.log('Web API Service Configuration:', JSON.stringify(config, null, 2));
|
||||||
|
|
||||||
// Create service application
|
// Create service application
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue