refactored monorepo for more projects

This commit is contained in:
Boki 2025-06-22 23:48:01 -04:00
parent 4632c174dc
commit 9492f1b15e
180 changed files with 1438 additions and 424 deletions

View file

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

View file

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

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

View file

@ -0,0 +1,11 @@
{
"environment": "development",
"log": {
"level": "debug",
"format": "pretty"
},
"features": {
"autoTrading": false,
"paperTrading": true
}
}

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

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

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

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

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

View file

@ -0,0 +1,3 @@
export * from './stock-app.schema';
export * from './providers.schema';
export * from './features.schema';

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

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

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

View file

@ -14,6 +14,7 @@
"dependencies": {
"@stock-bot/cache": "*",
"@stock-bot/config": "*",
"@stock-bot/stock-config": "*",
"@stock-bot/di": "*",
"@stock-bot/handlers": "*",
"@stock-bot/logger": "*",

View file

@ -12,17 +12,35 @@ export async function fetchWebShareProxies(): Promise<ProxyInfo[]> {
const ctx = OperationContext.create('webshare', 'fetch-proxies');
try {
// Get configuration from config system
const { getConfig } = await import('@stock-bot/config');
const config = getConfig();
// Get configuration from stock config system - ensure it's initialized
const { getStockConfig, initializeStockConfig } = await import('@stock-bot/stock-config');
// 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 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) {
ctx.logger.error('Missing WebShare configuration', {
hasApiKey: !!apiKey,
hasApiUrl: !!apiUrl,
apiKeyValue: apiKey ? `${apiKey.substring(0, 5)}...` : 'NOT_SET',
});
return [];
}

View file

@ -3,7 +3,7 @@
* Simplified entry point using ServiceApplication framework
*/
import { initializeServiceConfig } from '@stock-bot/config';
import { initializeStockConfig } from '@stock-bot/stock-config';
import {
ServiceApplication,
} from '@stock-bot/di';
@ -13,9 +13,9 @@ import { getLogger } from '@stock-bot/logger';
import { initializeAllHandlers } from './handlers';
import { createRoutes } from './routes/create-routes';
// Initialize configuration
const config = initializeServiceConfig();
console.log('Data Service Configuration:', JSON.stringify(config, null, 2));
// Initialize configuration with service-specific overrides
const config = initializeStockConfig('dataIngestion');
console.log('Data Ingestion Service Configuration:', JSON.stringify(config, null, 2));
// Create service application
const app = new ServiceApplication(
@ -57,7 +57,7 @@ async function createContainer(config: any) {
.withOptions({
enableQuestDB: false, // Data ingestion doesn't need QuestDB yet
enableMongoDB: true,
enablePostgres: true,
enablePostgres: config.database?.postgres?.enabled ?? false,
enableCache: true,
enableQueue: true,
enableBrowser: true, // Data ingestion needs browser for web scraping

View file

@ -12,6 +12,7 @@
{ "path": "../../libs/data/questdb" },
{ "path": "../../libs/services/queue" },
{ "path": "../../libs/services/shutdown" },
{ "path": "../../libs/utils" }
{ "path": "../../libs/utils" },
{ "path": "../config" }
]
}

View file

@ -14,6 +14,7 @@
"dependencies": {
"@stock-bot/cache": "*",
"@stock-bot/config": "*",
"@stock-bot/stock-config": "*",
"@stock-bot/logger": "*",
"@stock-bot/mongodb": "*",
"@stock-bot/postgres": "*",

View file

@ -3,7 +3,7 @@
* Simplified entry point using ServiceApplication framework
*/
import { initializeServiceConfig } from '@stock-bot/config';
import { initializeStockConfig } from '@stock-bot/stock-config';
import {
ServiceApplication,
createServiceContainerFromConfig,
@ -16,8 +16,8 @@ import { initializeAllHandlers } from './handlers';
import { createRoutes } from './routes/create-routes';
import { setupServiceContainer } from './container-setup';
// Initialize configuration
const config = initializeServiceConfig();
// Initialize configuration with service-specific overrides
const config = initializeStockConfig('dataPipeline');
console.log('Data Pipeline Service Configuration:', JSON.stringify(config, null, 2));
// Create service application

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

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

View file

@ -13,6 +13,7 @@
},
"dependencies": {
"@stock-bot/config": "*",
"@stock-bot/stock-config": "*",
"@stock-bot/logger": "*",
"@stock-bot/mongodb": "*",
"@stock-bot/postgres": "*",

View file

@ -3,7 +3,7 @@
* Simplified entry point using ServiceApplication framework
*/
import { initializeServiceConfig } from '@stock-bot/config';
import { initializeStockConfig } from '@stock-bot/stock-config';
import {
ServiceApplication,
createServiceContainerFromConfig,
@ -15,8 +15,8 @@ import { getLogger } from '@stock-bot/logger';
import { createRoutes } from './routes/create-routes';
import { setupServiceContainer } from './container-setup';
// Initialize configuration
const config = initializeServiceConfig();
// Initialize configuration with service-specific overrides
const config = initializeStockConfig('webApi');
console.log('Web API Service Configuration:', JSON.stringify(config, null, 2));
// Create service application

Some files were not shown because too many files have changed in this diff Show more