no idea- added loki and other stuff to market-data-gateway, also added config lib

This commit is contained in:
Bojan Kucera 2025-06-03 11:37:58 -04:00
parent b957fb99aa
commit 1b71fc87ab
72 changed files with 6178 additions and 153 deletions

53
libs/config/.env.example Normal file
View file

@ -0,0 +1,53 @@
# Base environment variables for Stock Bot
# Environment
NODE_ENV=development
# Logging
LOG_LEVEL=debug
# Database configuration
DRAGONFLY_HOST=localhost
DRAGONFLY_PORT=6379
DRAGONFLY_PASSWORD=
DRAGONFLY_MAX_RETRIES_PER_REQUEST=3
TIMESCALE_HOST=localhost
TIMESCALE_PORT=5432
TIMESCALE_DB=stockbot
TIMESCALE_USER=postgres
TIMESCALE_PASSWORD=postgres
# Data providers
DEFAULT_DATA_PROVIDER=alpaca
ALPACA_API_KEY=your_alpaca_key_here
ALPACA_API_SECRET=your_alpaca_secret_here
POLYGON_API_KEY=your_polygon_key_here
# Risk parameters
RISK_MAX_DRAWDOWN=0.05
RISK_MAX_POSITION_SIZE=0.1
RISK_MAX_LEVERAGE=1.5
RISK_STOP_LOSS_DEFAULT=0.02
RISK_TAKE_PROFIT_DEFAULT=0.05
# Market Data Gateway
SERVICE_PORT=4000
WEBSOCKET_ENABLED=true
WEBSOCKET_PATH=/ws/market-data
WEBSOCKET_HEARTBEAT_INTERVAL=30000
THROTTLING_MAX_REQUESTS=300
THROTTLING_MAX_CONNECTIONS=5
CACHING_ENABLED=true
CACHING_TTL_SECONDS=60
# Risk Guardian
RISK_CHECKS_PRE_TRADE=true
RISK_CHECKS_PORTFOLIO=true
RISK_CHECKS_LEVERAGE=true
RISK_CHECKS_CONCENTRATION=true
ALERTING_ENABLED=true
ALERTING_CRITICAL_THRESHOLD=0.8
ALERTING_WARNING_THRESHOLD=0.6
WATCHDOG_ENABLED=true
WATCHDOG_CHECK_INTERVAL=60

View file

@ -0,0 +1,28 @@
# Production environment variables for Stock Bot
# Environment
NODE_ENV=production
# Logging
LOG_LEVEL=info
# Database configuration (use environment-specific values)
DRAGONFLY_HOST=dragonfly.production
DRAGONFLY_PORT=6379
DRAGONFLY_MAX_RETRIES_PER_REQUEST=5
TIMESCALE_HOST=timescale.production
TIMESCALE_PORT=5432
TIMESCALE_DB=stockbot_prod
# Risk parameters (more conservative for production)
RISK_MAX_DRAWDOWN=0.03
RISK_MAX_POSITION_SIZE=0.05
RISK_MAX_LEVERAGE=1.0
# Service settings
WEBSOCKET_HEARTBEAT_INTERVAL=15000
THROTTLING_MAX_REQUESTS=500
THROTTLING_MAX_CONNECTIONS=20
CACHING_ENABLED=true
CACHING_TTL_SECONDS=30

103
libs/config/README.md Normal file
View file

@ -0,0 +1,103 @@
# @stock-bot/config
A configuration management library for the Stock Bot trading platform.
## Overview
This library provides a centralized way to manage configurations across all Stock Bot microservices and components. It includes:
- Environment-based configuration loading
- Strong TypeScript typing and validation using Zod
- Default configurations for services
- Environment variable parsing helpers
- Service-specific configuration modules
## Usage
### Basic Usage
```typescript
import { databaseConfig, dataProviderConfigs, riskConfig } from '@stock-bot/config';
// Access database configuration
const dragonflyHost = databaseConfig.dragonfly.host;
// Access data provider configuration
const alpacaApiKey = dataProviderConfigs.providers.find(p => p.name === 'alpaca')?.apiKey;
// Access risk configuration
const maxPositionSize = riskConfig.maxPositionSize;
```
### Service-Specific Configuration
```typescript
import { marketDataGatewayConfig, riskGuardianConfig } from '@stock-bot/config';
// Access Market Data Gateway configuration
const websocketPath = marketDataGatewayConfig.websocket.path;
// Access Risk Guardian configuration
const preTradeValidation = riskGuardianConfig.riskChecks.preTradeValidation;
```
### Environment Variables
The library automatically loads environment variables from `.env` files. You can create environment-specific files:
- `.env` - Base environment variables
- `.env.development` - Development-specific variables
- `.env.production` - Production-specific variables
- `.env.local` - Local overrides (not to be committed to git)
## Configuration Modules
### Core Configuration
- `Environment` - Enum for different environments
- `loadEnvVariables()` - Load environment variables from .env files
- `getEnvironment()` - Get the current environment
- `validateConfig()` - Validate configuration with Zod schema
### Database Configuration
- `databaseConfig` - Database connection settings (Dragonfly, TimescaleDB)
### Data Provider Configuration
- `dataProviderConfigs` - Settings for market data providers
### Risk Configuration
- `riskConfig` - Risk management parameters (max drawdown, position size, etc.)
### Service-Specific Configuration
- `marketDataGatewayConfig` - Configs for the Market Data Gateway service
- `riskGuardianConfig` - Configs for the Risk Guardian service
## Extending
To add a new service configuration:
1. Create a new file in `src/services/`
2. Define a Zod schema for validation
3. Create loading and default configuration functions
4. Export from `src/services/index.ts`
5. The new configuration will be automatically available from the main package
## Development
```bash
# Install dependencies
bun install
# Run tests
bun test
# Type check
bun run type-check
# Lint
bun run lint
```

37
libs/config/package.json Normal file
View file

@ -0,0 +1,37 @@
{
"name": "@stock-bot/config",
"version": "1.0.0",
"description": "Configuration management library for Stock Bot platform",
"main": "src/index.ts",
"type": "module",
"scripts": {
"build": "tsc",
"test": "bun test",
"lint": "eslint src/**/*.ts",
"type-check": "tsc --noEmit"
},
"dependencies": {
"dotenv": "^16.3.1",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^20.11.0",
"typescript": "^5.3.0",
"eslint": "^8.56.0",
"@typescript-eslint/eslint-plugin": "^6.19.0",
"@typescript-eslint/parser": "^6.19.0",
"bun-types": "^1.2.15"
},
"keywords": [
"configuration",
"settings",
"env",
"stock-bot"
],
"exports": {
".": {
"import": "./src/index.ts",
"require": "./dist/index.js"
}
}
}

24
libs/config/setup.bat Normal file
View file

@ -0,0 +1,24 @@
@echo off
echo Building @stock-bot/config library...
cd /d g:\repos\stock-bot
echo Installing dependencies...
bun install
echo Running type check...
cd /d g:\repos\stock-bot\libs\config
bun run type-check
echo Running tests...
bun test
echo Setting up example configuration...
copy .env.example .env
echo Running example to display configuration...
bun run src/example.ts
echo.
echo Configuration library setup complete!
echo.
echo You can now import @stock-bot/config in your services.

View file

@ -0,0 +1,113 @@
/**
* Tests for the configuration library
*/
import { describe, expect, test, beforeAll, afterAll } from 'bun:test';
import {
getEnvironment,
Environment,
validateConfig,
ConfigurationError,
loadEnvVariables,
getEnvVar,
getNumericEnvVar,
getBooleanEnvVar
} from './core';
import { databaseConfigSchema } from './types';
describe('Core configuration', () => {
// Save original environment variables
const originalEnv = { ...process.env };
// Setup test environment variables
beforeAll(() => {
process.env.NODE_ENV = 'testing';
process.env.TEST_STRING = 'test-value';
process.env.TEST_NUMBER = '42';
process.env.TEST_BOOL_TRUE = 'true';
process.env.TEST_BOOL_FALSE = 'false';
});
// Restore original environment variables
afterAll(() => {
process.env = { ...originalEnv };
});
test('getEnvironment returns correct environment', () => {
expect(getEnvironment()).toBe(Environment.Testing);
// Test different environments
process.env.NODE_ENV = 'development';
expect(getEnvironment()).toBe(Environment.Development);
process.env.NODE_ENV = 'production';
expect(getEnvironment()).toBe(Environment.Production);
process.env.NODE_ENV = 'staging';
expect(getEnvironment()).toBe(Environment.Staging);
// Test default environment
process.env.NODE_ENV = 'unknown';
expect(getEnvironment()).toBe(Environment.Development);
});
test('getEnvVar retrieves environment variables', () => {
expect(getEnvVar('TEST_STRING')).toBe('test-value');
expect(getEnvVar('NON_EXISTENT')).toBeUndefined();
expect(getEnvVar('NON_EXISTENT', false)).toBeUndefined();
// Test required variables
expect(() => getEnvVar('NON_EXISTENT', true)).toThrow(ConfigurationError);
});
test('getNumericEnvVar converts to number', () => {
expect(getNumericEnvVar('TEST_NUMBER')).toBe(42);
expect(getNumericEnvVar('NON_EXISTENT', 100)).toBe(100);
// Test invalid number
process.env.INVALID_NUMBER = 'not-a-number';
expect(() => getNumericEnvVar('INVALID_NUMBER')).toThrow(ConfigurationError);
});
test('getBooleanEnvVar converts to boolean', () => {
expect(getBooleanEnvVar('TEST_BOOL_TRUE')).toBe(true);
expect(getBooleanEnvVar('TEST_BOOL_FALSE')).toBe(false);
expect(getBooleanEnvVar('NON_EXISTENT', true)).toBe(true);
});
test('validateConfig validates against schema', () => {
// Valid config
const validConfig = {
dragonfly: {
host: 'localhost',
port: 6379,
maxRetriesPerRequest: 3
},
timescaleDB: {
host: 'localhost',
port: 5432,
database: 'stockbot',
user: 'postgres'
}
};
expect(() => validateConfig(validConfig, databaseConfigSchema)).not.toThrow();
// Invalid config (missing required field)
const invalidConfig = {
dragonfly: {
host: 'localhost',
// missing port
maxRetriesPerRequest: 3
},
timescaleDB: {
host: 'localhost',
port: 5432,
database: 'stockbot',
user: 'postgres'
}
};
expect(() => validateConfig(invalidConfig, databaseConfigSchema)).toThrow(ConfigurationError);
});
});

162
libs/config/src/core.ts Normal file
View file

@ -0,0 +1,162 @@
/**
* Core configuration module for the Stock Bot platform
*/
import { config as dotenvConfig } from 'dotenv';
import path from 'path';
import { z } from 'zod';
import { Environment } from './types';
/**
* Represents an error related to configuration validation
*/
export class ConfigurationError extends Error {
constructor(message: string) {
super(message);
this.name = 'ConfigurationError';
}
}
/**
* Loads environment variables from .env files based on the current environment
*/
export function loadEnvVariables(envOverride?: string): void {
const env = envOverride || process.env.NODE_ENV || 'development';
// Order of loading:
// 1. .env (base environment variables)
// 2. .env.{environment} (environment-specific variables)
// 3. .env.local (local overrides, not to be committed)
const envFiles = [
'.env',
`.env.${env}`,
'.env.local'
];
for (const file of envFiles) {
dotenvConfig({ path: path.resolve(process.cwd(), file) });
}
}
/**
* Gets the current environment from process.env.NODE_ENV
*/
export function getEnvironment(): Environment {
const env = process.env.NODE_ENV?.toLowerCase() || 'development';
switch (env) {
case 'development':
return Environment.Development;
case 'testing':
return Environment.Testing;
case 'staging':
return Environment.Staging;
case 'production':
return Environment.Production;
default:
return Environment.Development;
}
}
/**
* Validates configuration using Zod schema
*/
export function validateConfig<T>(config: unknown, schema: z.ZodSchema<T>): T {
try {
return schema.parse(config);
} catch (error) {
if (error instanceof z.ZodError) {
const issues = error.issues.map(issue =>
`${issue.path.join('.')}: ${issue.message}`
).join('\n');
throw new ConfigurationError(`Configuration validation failed:\n${issues}`);
}
throw new ConfigurationError('Invalid configuration');
}
}
/**
* Retrieves an environment variable with validation
*/
export function getEnvVar(key: string, required: boolean = false): string | undefined {
const value = process.env[key];
if (required && (value === undefined || value === '')) {
throw new ConfigurationError(`Required environment variable ${key} is missing`);
}
return value;
}
/**
* Retrieves a numeric environment variable with validation
*/
export function getNumericEnvVar(key: string, defaultValue?: number): number {
const value = process.env[key];
if (value === undefined || value === '') {
if (defaultValue !== undefined) {
return defaultValue;
}
throw new ConfigurationError(`Required numeric environment variable ${key} is missing`);
}
const numValue = Number(value);
if (isNaN(numValue)) {
throw new ConfigurationError(`Environment variable ${key} is not a valid number`);
}
return numValue;
}
/**
* Retrieves a boolean environment variable with validation
*/
export function getBooleanEnvVar(key: string, defaultValue?: boolean): boolean {
const value = process.env[key];
if (value === undefined || value === '') {
if (defaultValue !== undefined) {
return defaultValue;
}
throw new ConfigurationError(`Required boolean environment variable ${key} is missing`);
}
return value.toLowerCase() === 'true' || value === '1';
}
/**
* Creates a typed dynamic configuration loader for a specific service
*/
export function createConfigLoader<T>(
serviceName: string,
schema: z.ZodSchema<T>,
defaultConfig: Partial<T> = {}
): () => T {
return (): T => {
try {
loadEnvVariables();
const configEnvVar = `${serviceName.toUpperCase()}_CONFIG`;
let config = { ...defaultConfig } as unknown as T;
// Try to load JSON from environment variable if available
const configJson = process.env[configEnvVar];
if (configJson) {
try {
const parsedConfig = JSON.parse(configJson);
config = { ...config, ...parsedConfig };
} catch (error) {
throw new ConfigurationError(`Invalid JSON in ${configEnvVar} environment variable`);
}
}
// Validate and return the config
return validateConfig(config, schema);
} catch (error) {
if (error instanceof ConfigurationError) {
throw error;
}
throw new ConfigurationError(`Failed to load configuration for service ${serviceName}: ${error}`);
}
};
}

View file

@ -0,0 +1,73 @@
/**
* Data provider configurations for market data
*/
import { getEnvVar, validateConfig } from './core';
import { dataProvidersConfigSchema, DataProvidersConfig, DataProviderConfig } from './types';
/**
* Default data provider configurations
*/
const defaultDataProviders: DataProviderConfig[] = [
{
name: 'alpaca',
type: 'rest',
baseUrl: 'https://data.alpaca.markets/v1beta1',
apiKey: '',
apiSecret: '',
rateLimits: {
maxRequestsPerMinute: 200
}
},
{
name: 'polygon',
type: 'rest',
baseUrl: 'https://api.polygon.io/v2',
apiKey: '',
rateLimits: {
maxRequestsPerMinute: 5
}
},
{
name: 'alpaca-websocket',
type: 'websocket',
wsUrl: 'wss://stream.data.alpaca.markets/v2/iex',
apiKey: '',
apiSecret: ''
}
];
/**
* Load data provider configurations from environment variables
*/
export function loadDataProviderConfigs(): DataProvidersConfig {
// Get provider specific environment variables
const providers = defaultDataProviders.map(provider => {
const nameUpper = provider.name.toUpperCase().replace('-', '_');
const updatedProvider: DataProviderConfig = {
...provider,
apiKey: getEnvVar(`${nameUpper}_API_KEY`) || provider.apiKey || '',
};
if (provider.apiSecret !== undefined) {
updatedProvider.apiSecret = getEnvVar(`${nameUpper}_API_SECRET`) || provider.apiSecret || '';
}
return updatedProvider;
});
// Load default provider from environment
const defaultProvider = getEnvVar('DEFAULT_DATA_PROVIDER') || 'alpaca';
const config: DataProvidersConfig = {
providers,
defaultProvider
};
return validateConfig(config, dataProvidersConfigSchema);
}
/**
* Singleton data provider configurations
*/
export const dataProviderConfigs = loadDataProviderConfigs();

View file

@ -0,0 +1,52 @@
/**
* Database configuration for Stock Bot services
*/
import { z } from 'zod';
import { getEnvVar, getNumericEnvVar, validateConfig } from './core';
import { databaseConfigSchema, DatabaseConfig } from './types';
/**
* Default database configuration
*/
const defaultDatabaseConfig: DatabaseConfig = {
dragonfly: {
host: 'localhost',
port: 6379,
maxRetriesPerRequest: 3
},
timescaleDB: {
host: 'localhost',
port: 5432,
database: 'stockbot',
user: 'postgres'
}
};
/**
* Load database configuration from environment variables
*/
export function loadDatabaseConfig(): DatabaseConfig {
const config: DatabaseConfig = {
dragonfly: {
host: getEnvVar('DRAGONFLY_HOST') || defaultDatabaseConfig.dragonfly.host,
port: getNumericEnvVar('DRAGONFLY_PORT', defaultDatabaseConfig.dragonfly.port),
password: getEnvVar('DRAGONFLY_PASSWORD'),
maxRetriesPerRequest: getNumericEnvVar('DRAGONFLY_MAX_RETRIES_PER_REQUEST',
defaultDatabaseConfig.dragonfly.maxRetriesPerRequest)
},
timescaleDB: {
host: getEnvVar('TIMESCALE_HOST') || defaultDatabaseConfig.timescaleDB.host,
port: getNumericEnvVar('TIMESCALE_PORT', defaultDatabaseConfig.timescaleDB.port),
database: getEnvVar('TIMESCALE_DB') || defaultDatabaseConfig.timescaleDB.database,
user: getEnvVar('TIMESCALE_USER') || defaultDatabaseConfig.timescaleDB.user,
password: getEnvVar('TIMESCALE_PASSWORD')
}
};
return validateConfig(config, databaseConfigSchema);
}
/**
* Singleton database configuration
*/
export const databaseConfig = loadDatabaseConfig();

View file

@ -0,0 +1,73 @@
/**
* Example usage of the @stock-bot/config library
*/
import {
databaseConfig,
dataProviderConfigs,
riskConfig,
Environment,
getEnvironment,
marketDataGatewayConfig,
riskGuardianConfig,
ConfigurationError,
validateConfig
} from './index';
/**
* Display current configuration values
*/
export function printCurrentConfig(): void {
console.log('\n=== Stock Bot Configuration ===');
console.log('\nEnvironment:', getEnvironment());
console.log('\n--- Database Config ---');
console.log('Dragonfly Host:', databaseConfig.dragonfly.host);
console.log('Dragonfly Port:', databaseConfig.dragonfly.port);
console.log('TimescaleDB Host:', databaseConfig.timescaleDB.host);
console.log('TimescaleDB Database:', databaseConfig.timescaleDB.database);
console.log('\n--- Data Provider Config ---');
console.log('Default Provider:', dataProviderConfigs.defaultProvider);
console.log('Providers:');
dataProviderConfigs.providers.forEach(provider => {
console.log(` - ${provider.name} (${provider.type})`);
if (provider.baseUrl) console.log(` URL: ${provider.baseUrl}`);
if (provider.wsUrl) console.log(` WebSocket: ${provider.wsUrl}`);
});
console.log('\n--- Risk Config ---');
console.log('Max Drawdown:', riskConfig.maxDrawdown * 100, '%');
console.log('Max Position Size:', riskConfig.maxPositionSize * 100, '%');
console.log('Max Leverage:', riskConfig.maxLeverage, 'x');
console.log('Default Stop Loss:', riskConfig.stopLossDefault * 100, '%');
console.log('Default Take Profit:', riskConfig.takeProfitDefault * 100, '%');
console.log('\n--- Market Data Gateway Config ---');
console.log('Service Port:', marketDataGatewayConfig.service.port);
console.log('WebSocket Enabled:', marketDataGatewayConfig.websocket.enabled);
console.log('WebSocket Path:', marketDataGatewayConfig.websocket.path);
console.log('Caching Enabled:', marketDataGatewayConfig.caching.enabled);
console.log('Caching TTL:', marketDataGatewayConfig.caching.ttlSeconds, 'seconds');
console.log('\n--- Risk Guardian Config ---');
console.log('Service Port:', riskGuardianConfig.service.port);
console.log('Pre-Trade Validation:', riskGuardianConfig.riskChecks.preTradeValidation);
console.log('Portfolio Validation:', riskGuardianConfig.riskChecks.portfolioValidation);
console.log('Alerting Enabled:', riskGuardianConfig.alerting.enabled);
console.log('Critical Threshold:', riskGuardianConfig.alerting.criticalThreshold * 100, '%');
}
// Execute example if this file is run directly
if (require.main === module) {
try {
printCurrentConfig();
} catch (error) {
if (error instanceof ConfigurationError) {
console.error('Configuration Error:', error.message);
} else {
console.error('Error:', error);
}
process.exit(1);
}
}

24
libs/config/src/index.ts Normal file
View file

@ -0,0 +1,24 @@
/**
* @stock-bot/config
*
* Configuration management library for Stock Bot platform
*/
// Core configuration functionality
export * from './core';
export * from './types';
// Database configurations
export * from './database';
// Data provider configurations
export * from './data-providers';
// Risk management configurations
export * from './risk';
// Logging configurations
export * from './logging';
// Service-specific configurations
export * from './services';

View file

@ -0,0 +1,75 @@
/**
* Loki logging configuration for Stock Bot platform
*/
import { z } from 'zod';
import { getEnvVar, getNumericEnvVar, getBooleanEnvVar } from './core';
/**
* Loki configuration schema
*/
export const lokiConfigSchema = z.object({
host: z.string().default('localhost'),
port: z.number().default(3100),
username: z.string().optional(),
password: z.string().optional(),
retentionDays: z.number().default(30),
labels: z.record(z.string()).default({}),
batchSize: z.number().default(100),
flushIntervalMs: z.number().default(5000)
});
export type LokiConfig = z.infer<typeof lokiConfigSchema>;
/**
* Logging configuration schema
*/
export const loggingConfigSchema = z.object({
level: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
console: z.boolean().default(true),
loki: lokiConfigSchema
});
export type LoggingConfig = z.infer<typeof loggingConfigSchema>;
/**
* Parse labels from environment variable string
* Format: key1=value1,key2=value2
*/
function parseLabels(labelsStr?: string): Record<string, string> {
if (!labelsStr) return {};
const labels: Record<string, string> = {};
labelsStr.split(',').forEach(labelPair => {
const [key, value] = labelPair.trim().split('=');
if (key && value) {
labels[key] = value;
}
});
return labels;
}
/**
* Load logging configuration from environment variables
*/
export function loadLoggingConfig(): LoggingConfig {
return {
level: (getEnvVar('LOG_LEVEL') || 'info') as 'debug' | 'info' | 'warn' | 'error',
console: getBooleanEnvVar('LOG_CONSOLE', true),
loki: {
host: getEnvVar('LOKI_HOST') || 'localhost',
port: getNumericEnvVar('LOKI_PORT', 3100),
username: getEnvVar('LOKI_USERNAME'),
password: getEnvVar('LOKI_PASSWORD'),
retentionDays: getNumericEnvVar('LOKI_RETENTION_DAYS', 30),
labels: parseLabels(getEnvVar('LOKI_LABELS')),
batchSize: getNumericEnvVar('LOKI_BATCH_SIZE', 100),
flushIntervalMs: getNumericEnvVar('LOKI_FLUSH_INTERVAL_MS', 5000)
}
};
}
/**
* Singleton logging configuration
*/
export const loggingConfig = loadLoggingConfig();

36
libs/config/src/risk.ts Normal file
View file

@ -0,0 +1,36 @@
/**
* Risk management configuration for trading operations
*/
import { getNumericEnvVar, validateConfig } from './core';
import { riskConfigSchema, RiskConfig } from './types';
/**
* Default risk configuration
*/
const defaultRiskConfig: RiskConfig = {
maxDrawdown: 0.05,
maxPositionSize: 0.1,
maxLeverage: 1,
stopLossDefault: 0.02,
takeProfitDefault: 0.05
};
/**
* Load risk configuration from environment variables
*/
export function loadRiskConfig(): RiskConfig {
const config: RiskConfig = {
maxDrawdown: getNumericEnvVar('RISK_MAX_DRAWDOWN', defaultRiskConfig.maxDrawdown),
maxPositionSize: getNumericEnvVar('RISK_MAX_POSITION_SIZE', defaultRiskConfig.maxPositionSize),
maxLeverage: getNumericEnvVar('RISK_MAX_LEVERAGE', defaultRiskConfig.maxLeverage),
stopLossDefault: getNumericEnvVar('RISK_STOP_LOSS_DEFAULT', defaultRiskConfig.stopLossDefault),
takeProfitDefault: getNumericEnvVar('RISK_TAKE_PROFIT_DEFAULT', defaultRiskConfig.takeProfitDefault)
};
return validateConfig(config, riskConfigSchema);
}
/**
* Singleton risk configuration
*/
export const riskConfig = loadRiskConfig();

View file

@ -0,0 +1,5 @@
/**
* Export all service-specific configurations
*/
export * from './market-data-gateway';
export * from './risk-guardian';

View file

@ -0,0 +1,106 @@
/**
* Market Data Gateway service configuration
*/
import { z } from 'zod';
import { getEnvVar, getNumericEnvVar, getBooleanEnvVar, createConfigLoader } from './core';
import { Environment, BaseConfig } from './types';
import { getEnvironment } from './core';
/**
* Market Data Gateway specific configuration schema
*/
export const marketDataGatewayConfigSchema = z.object({
environment: z.nativeEnum(Environment),
logLevel: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
service: z.object({
name: z.string().default('market-data-gateway'),
version: z.string().default('1.0.0'),
port: z.number().default(4000)
}),
websocket: z.object({
enabled: z.boolean().default(true),
path: z.string().default('/ws/market-data'),
heartbeatInterval: z.number().default(30000)
}),
throttling: z.object({
maxRequestsPerMinute: z.number().default(300),
maxConnectionsPerIP: z.number().default(5)
}),
caching: z.object({
enabled: z.boolean().default(true),
ttlSeconds: z.number().default(60)
})
});
/**
* Market Data Gateway configuration type
*/
export type MarketDataGatewayConfig = z.infer<typeof marketDataGatewayConfigSchema>;
/**
* Default Market Data Gateway configuration
*/
const defaultConfig: Partial<MarketDataGatewayConfig> = {
environment: getEnvironment(),
logLevel: 'info',
service: {
name: 'market-data-gateway',
version: '1.0.0',
port: 4000
},
websocket: {
enabled: true,
path: '/ws/market-data',
heartbeatInterval: 30000 // 30 seconds
},
throttling: {
maxRequestsPerMinute: 300,
maxConnectionsPerIP: 5
},
caching: {
enabled: true,
ttlSeconds: 60
}
};
/**
* Load Market Data Gateway configuration
*/
export function loadMarketDataGatewayConfig(): MarketDataGatewayConfig {
return {
environment: getEnvironment(),
logLevel: (getEnvVar('LOG_LEVEL') || defaultConfig.logLevel) as 'debug' | 'info' | 'warn' | 'error',
service: {
name: getEnvVar('SERVICE_NAME') || defaultConfig.service!.name,
version: getEnvVar('SERVICE_VERSION') || defaultConfig.service!.version,
port: getNumericEnvVar('SERVICE_PORT', defaultConfig.service!.port)
},
websocket: {
enabled: getBooleanEnvVar('WEBSOCKET_ENABLED', defaultConfig.websocket!.enabled),
path: getEnvVar('WEBSOCKET_PATH') || defaultConfig.websocket!.path,
heartbeatInterval: getNumericEnvVar('WEBSOCKET_HEARTBEAT_INTERVAL', defaultConfig.websocket!.heartbeatInterval)
},
throttling: {
maxRequestsPerMinute: getNumericEnvVar('THROTTLING_MAX_REQUESTS', defaultConfig.throttling!.maxRequestsPerMinute),
maxConnectionsPerIP: getNumericEnvVar('THROTTLING_MAX_CONNECTIONS', defaultConfig.throttling!.maxConnectionsPerIP)
},
caching: {
enabled: getBooleanEnvVar('CACHING_ENABLED', defaultConfig.caching!.enabled),
ttlSeconds: getNumericEnvVar('CACHING_TTL_SECONDS', defaultConfig.caching!.ttlSeconds)
}
};
}
/**
* Creates a dynamic configuration loader for the Market Data Gateway
*/
export const createMarketDataGatewayConfig = createConfigLoader<MarketDataGatewayConfig>(
'market-data-gateway',
marketDataGatewayConfigSchema,
defaultConfig
);
/**
* Singleton Market Data Gateway configuration
*/
export const marketDataGatewayConfig = loadMarketDataGatewayConfig();

View file

@ -0,0 +1,112 @@
/**
* Risk Guardian service configuration
*/
import { z } from 'zod';
import { getEnvVar, getNumericEnvVar, getBooleanEnvVar, createConfigLoader } from '../core';
import { Environment, BaseConfig } from '../types';
import { getEnvironment } from '../core';
/**
* Risk Guardian specific configuration schema
*/
export const riskGuardianConfigSchema = z.object({
environment: z.nativeEnum(Environment),
logLevel: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
service: z.object({
name: z.string().default('risk-guardian'),
version: z.string().default('1.0.0'),
port: z.number().default(4001)
}),
riskChecks: z.object({
preTradeValidation: z.boolean().default(true),
portfolioValidation: z.boolean().default(true),
leverageValidation: z.boolean().default(true),
concentrationValidation: z.boolean().default(true)
}),
alerting: z.object({
enabled: z.boolean().default(true),
criticalThreshold: z.number().default(0.8),
warningThreshold: z.number().default(0.6)
}),
watchdog: z.object({
enabled: z.boolean().default(true),
checkIntervalSeconds: z.number().default(60)
})
});
/**
* Risk Guardian configuration type
*/
export type RiskGuardianConfig = z.infer<typeof riskGuardianConfigSchema>;
/**
* Default Risk Guardian configuration
*/
const defaultConfig: Partial<RiskGuardianConfig> = {
environment: getEnvironment(),
logLevel: 'info',
service: {
name: 'risk-guardian',
version: '1.0.0',
port: 4001
},
riskChecks: {
preTradeValidation: true,
portfolioValidation: true,
leverageValidation: true,
concentrationValidation: true
},
alerting: {
enabled: true,
criticalThreshold: 0.8,
warningThreshold: 0.6
},
watchdog: {
enabled: true,
checkIntervalSeconds: 60
}
};
/**
* Load Risk Guardian configuration
*/
export function loadRiskGuardianConfig(): RiskGuardianConfig {
return {
environment: getEnvironment(),
logLevel: (getEnvVar('LOG_LEVEL') || defaultConfig.logLevel) as 'debug' | 'info' | 'warn' | 'error',
service: {
name: getEnvVar('SERVICE_NAME') || defaultConfig.service!.name,
version: getEnvVar('SERVICE_VERSION') || defaultConfig.service!.version,
port: getNumericEnvVar('SERVICE_PORT', defaultConfig.service!.port)
},
riskChecks: {
preTradeValidation: getBooleanEnvVar('RISK_CHECKS_PRE_TRADE', defaultConfig.riskChecks!.preTradeValidation),
portfolioValidation: getBooleanEnvVar('RISK_CHECKS_PORTFOLIO', defaultConfig.riskChecks!.portfolioValidation),
leverageValidation: getBooleanEnvVar('RISK_CHECKS_LEVERAGE', defaultConfig.riskChecks!.leverageValidation),
concentrationValidation: getBooleanEnvVar('RISK_CHECKS_CONCENTRATION', defaultConfig.riskChecks!.concentrationValidation)
},
alerting: {
enabled: getBooleanEnvVar('ALERTING_ENABLED', defaultConfig.alerting!.enabled),
criticalThreshold: getNumericEnvVar('ALERTING_CRITICAL_THRESHOLD', defaultConfig.alerting!.criticalThreshold),
warningThreshold: getNumericEnvVar('ALERTING_WARNING_THRESHOLD', defaultConfig.alerting!.warningThreshold)
},
watchdog: {
enabled: getBooleanEnvVar('WATCHDOG_ENABLED', defaultConfig.watchdog!.enabled),
checkIntervalSeconds: getNumericEnvVar('WATCHDOG_CHECK_INTERVAL', defaultConfig.watchdog!.checkIntervalSeconds)
}
};
}
/**
* Creates a dynamic configuration loader for the Risk Guardian
*/
export const createRiskGuardianConfig = createConfigLoader<RiskGuardianConfig>(
'risk-guardian',
riskGuardianConfigSchema,
defaultConfig
);
/**
* Singleton Risk Guardian configuration
*/
export const riskGuardianConfig = loadRiskGuardianConfig();

87
libs/config/src/types.ts Normal file
View file

@ -0,0 +1,87 @@
/**
* Configuration type definitions for the Stock Bot platform
*/
import { z } from 'zod';
/**
* Environment enum for different deployment environments
*/
export enum Environment {
Development = 'development',
Testing = 'testing',
Staging = 'staging',
Production = 'production'
}
/**
* Common configuration interface for all service configs
*/
export interface BaseConfig {
environment: Environment;
logLevel: 'debug' | 'info' | 'warn' | 'error';
service: {
name: string;
version: string;
port: number;
};
}
/**
* Database configuration schema
*/
export const databaseConfigSchema = z.object({
dragonfly: z.object({
host: z.string().default('localhost'),
port: z.number().default(6379),
password: z.string().optional(),
maxRetriesPerRequest: z.number().default(3)
}),
timescaleDB: z.object({
host: z.string().default('localhost'),
port: z.number().default(5432),
database: z.string().default('stockbot'),
user: z.string().default('postgres'),
password: z.string().optional()
})
});
/**
* Data provider configuration schema
*/
export const dataProviderSchema = z.object({
name: z.string(),
type: z.enum(['rest', 'websocket', 'file']),
baseUrl: z.string().url().optional(),
wsUrl: z.string().url().optional(),
apiKey: z.string().optional(),
apiSecret: z.string().optional(),
refreshInterval: z.number().optional(),
rateLimits: z.object({
maxRequestsPerMinute: z.number().optional(),
maxRequestsPerSecond: z.number().optional()
}).optional()
});
export const dataProvidersConfigSchema = z.object({
providers: z.array(dataProviderSchema),
defaultProvider: z.string()
});
/**
* Risk management configuration schema
*/
export const riskConfigSchema = z.object({
maxDrawdown: z.number().default(0.05),
maxPositionSize: z.number().default(0.1),
maxLeverage: z.number().default(1),
stopLossDefault: z.number().default(0.02),
takeProfitDefault: z.number().default(0.05)
});
/**
* Type definitions based on schemas
*/
export type DatabaseConfig = z.infer<typeof databaseConfigSchema>;
export type DataProviderConfig = z.infer<typeof dataProviderSchema>;
export type DataProvidersConfig = z.infer<typeof dataProvidersConfigSchema>;
export type RiskConfig = z.infer<typeof riskConfigSchema>;

10
libs/config/tsconfig.json Normal file
View file

@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}