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": {
|
||||
"@stock-bot/cache": "*",
|
||||
"@stock-bot/config": "*",
|
||||
"@stock-bot/stock-config": "*",
|
||||
"@stock-bot/di": "*",
|
||||
"@stock-bot/handlers": "*",
|
||||
"@stock-bot/logger": "*",
|
||||
|
|
@ -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 [];
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
{ "path": "../../libs/data/questdb" },
|
||||
{ "path": "../../libs/services/queue" },
|
||||
{ "path": "../../libs/services/shutdown" },
|
||||
{ "path": "../../libs/utils" }
|
||||
{ "path": "../../libs/utils" },
|
||||
{ "path": "../config" }
|
||||
]
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
"dependencies": {
|
||||
"@stock-bot/cache": "*",
|
||||
"@stock-bot/config": "*",
|
||||
"@stock-bot/stock-config": "*",
|
||||
"@stock-bot/logger": "*",
|
||||
"@stock-bot/mongodb": "*",
|
||||
"@stock-bot/postgres": "*",
|
||||
|
|
@ -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
|
||||
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": {
|
||||
"@stock-bot/config": "*",
|
||||
"@stock-bot/stock-config": "*",
|
||||
"@stock-bot/logger": "*",
|
||||
"@stock-bot/mongodb": "*",
|
||||
"@stock-bot/postgres": "*",
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue