adding data-services
This commit is contained in:
parent
e3bfd05b90
commit
405b818c86
139 changed files with 55943 additions and 416 deletions
68
libs/api-client/README.md
Normal file
68
libs/api-client/README.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# API Client Library
|
||||
|
||||
Type-safe HTTP clients for inter-service communication in the stock-bot project.
|
||||
|
||||
## Available Clients
|
||||
|
||||
### BacktestClient
|
||||
|
||||
Client for interacting with the Backtest Engine service:
|
||||
|
||||
```typescript
|
||||
import { createBacktestClient } from '@stock-bot/api-client';
|
||||
|
||||
// Create a client instance
|
||||
const backtestClient = createBacktestClient('http://localhost:4002');
|
||||
|
||||
// Run a backtest
|
||||
const result = await backtestClient.runBacktest({
|
||||
strategyId: '123',
|
||||
startDate: new Date('2023-01-01'),
|
||||
endDate: new Date('2023-12-31'),
|
||||
symbols: ['AAPL', 'MSFT', 'GOOG'],
|
||||
initialCapital: 100000,
|
||||
parameters: {
|
||||
riskFactor: 0.5,
|
||||
positionSizeLimit: 0.1
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### StrategyClient
|
||||
|
||||
Client for interacting with the Strategy Orchestrator service:
|
||||
|
||||
```typescript
|
||||
import { createStrategyClient } from '@stock-bot/api-client';
|
||||
|
||||
// Create a client instance
|
||||
const strategyClient = createStrategyClient('http://localhost:4001');
|
||||
|
||||
// Get a strategy by ID
|
||||
const strategy = await strategyClient.getStrategy('123');
|
||||
|
||||
// Update a strategy
|
||||
await strategyClient.updateStrategy('123', {
|
||||
parameters: {
|
||||
lookbackPeriod: 20,
|
||||
threshold: 0.02
|
||||
}
|
||||
});
|
||||
|
||||
// Enable a strategy
|
||||
await strategyClient.enableStrategy('123');
|
||||
```
|
||||
|
||||
## Creating Custom Clients
|
||||
|
||||
Extend the `BaseApiClient` to create clients for other services:
|
||||
|
||||
```typescript
|
||||
import { BaseApiClient } from '@stock-bot/api-client';
|
||||
|
||||
class RiskGuardianClient extends BaseApiClient {
|
||||
async getRiskLimits(portfolioId: string) {
|
||||
return this.client.get(`/api/risk/limits/${portfolioId}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
22
libs/api-client/package.json
Normal file
22
libs/api-client/package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "@stock-bot/api-client",
|
||||
"version": "1.0.0",
|
||||
"description": "API clients for inter-service communication",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"clean": "rm -rf dist",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stock-bot/shared-types": "workspace:*",
|
||||
"axios": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.2",
|
||||
"jest": "^29.5.0",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
30
libs/api-client/src/BacktestClient.ts
Normal file
30
libs/api-client/src/BacktestClient.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { BaseApiClient } from './BaseApiClient';
|
||||
import { ApiResponse, BacktestConfig, BacktestResult } from '@stock-bot/shared-types';
|
||||
|
||||
/**
|
||||
* Client for interacting with the Backtest Engine service
|
||||
*/
|
||||
export class BacktestClient extends BaseApiClient {
|
||||
/**
|
||||
* Run a backtest
|
||||
*/
|
||||
async runBacktest(config: BacktestConfig): Promise<ApiResponse<BacktestResult>> {
|
||||
return this.client.post('/api/backtest/run', config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a backtest by ID
|
||||
*/
|
||||
async getBacktest(id: string): Promise<ApiResponse<BacktestResult>> {
|
||||
return this.client.get(`/api/backtest/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all backtests for a strategy
|
||||
*/
|
||||
async listBacktests(strategyId: string): Promise<ApiResponse<BacktestResult[]>> {
|
||||
return this.client.get(`/api/backtest`, {
|
||||
params: { strategyId }
|
||||
});
|
||||
}
|
||||
}
|
||||
39
libs/api-client/src/BaseApiClient.ts
Normal file
39
libs/api-client/src/BaseApiClient.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
|
||||
import { ApiResponse } from '@stock-bot/shared-types';
|
||||
|
||||
/**
|
||||
* Base API client that all service clients extend
|
||||
*/
|
||||
export abstract class BaseApiClient {
|
||||
protected client: AxiosInstance;
|
||||
|
||||
constructor(baseURL: string, config?: AxiosRequestConfig) {
|
||||
this.client = axios.create({
|
||||
baseURL,
|
||||
timeout: 10000, // 10 seconds timeout
|
||||
...config
|
||||
});
|
||||
|
||||
// Add response interceptor for consistent error handling
|
||||
this.client.interceptors.response.use(
|
||||
(response) => response.data,
|
||||
(error) => {
|
||||
// Format error for consistent error handling
|
||||
const formattedError = {
|
||||
status: error.response?.status || 500,
|
||||
message: error.response?.data?.error || error.message || 'Unknown error',
|
||||
originalError: error
|
||||
};
|
||||
|
||||
return Promise.reject(formattedError);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the health status of a service
|
||||
*/
|
||||
async getHealth(): Promise<ApiResponse<{ status: string }>> {
|
||||
return this.client.get('/api/health');
|
||||
}
|
||||
}
|
||||
56
libs/api-client/src/StrategyClient.ts
Normal file
56
libs/api-client/src/StrategyClient.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { BaseApiClient } from './BaseApiClient';
|
||||
import { ApiResponse, Strategy } from '@stock-bot/shared-types';
|
||||
|
||||
/**
|
||||
* Client for interacting with the Strategy Orchestrator service
|
||||
*/
|
||||
export class StrategyClient extends BaseApiClient {
|
||||
/**
|
||||
* Get a strategy by ID
|
||||
*/
|
||||
async getStrategy(id: string): Promise<ApiResponse<Strategy>> {
|
||||
return this.client.get(`/api/strategy/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all strategies
|
||||
*/
|
||||
async listStrategies(): Promise<ApiResponse<Strategy[]>> {
|
||||
return this.client.get('/api/strategy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new strategy
|
||||
*/
|
||||
async createStrategy(strategy: Omit<Strategy, 'id'>): Promise<ApiResponse<Strategy>> {
|
||||
return this.client.post('/api/strategy', strategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a strategy
|
||||
*/
|
||||
async updateStrategy(id: string, strategy: Partial<Strategy>): Promise<ApiResponse<Strategy>> {
|
||||
return this.client.put(`/api/strategy/${id}`, strategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a strategy
|
||||
*/
|
||||
async deleteStrategy(id: string): Promise<ApiResponse<void>> {
|
||||
return this.client.delete(`/api/strategy/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable a strategy
|
||||
*/
|
||||
async enableStrategy(id: string): Promise<ApiResponse<Strategy>> {
|
||||
return this.client.post(`/api/strategy/${id}/enable`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a strategy
|
||||
*/
|
||||
async disableStrategy(id: string): Promise<ApiResponse<Strategy>> {
|
||||
return this.client.post(`/api/strategy/${id}/disable`);
|
||||
}
|
||||
}
|
||||
14
libs/api-client/src/index.ts
Normal file
14
libs/api-client/src/index.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { BaseApiClient } from './BaseApiClient';
|
||||
import { BacktestClient } from './BacktestClient';
|
||||
import { StrategyClient } from './StrategyClient';
|
||||
|
||||
export { BaseApiClient, BacktestClient, StrategyClient };
|
||||
|
||||
// Factory functions
|
||||
export function createBacktestClient(baseUrl: string = 'http://localhost:4002'): BacktestClient {
|
||||
return new BacktestClient(baseUrl);
|
||||
}
|
||||
|
||||
export function createStrategyClient(baseUrl: string = 'http://localhost:4001'): StrategyClient {
|
||||
return new StrategyClient(baseUrl);
|
||||
}
|
||||
13
libs/api-client/tsconfig.json
Normal file
13
libs/api-client/tsconfig.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
42
libs/event-bus/README.md
Normal file
42
libs/event-bus/README.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Event Bus Library
|
||||
|
||||
A Redis-based event bus implementation for inter-service communication in the stock-bot project.
|
||||
|
||||
## Features
|
||||
|
||||
- Publish/subscribe pattern for asynchronous messaging
|
||||
- Support for typed events based on `@stock-bot/shared-types`
|
||||
- Reliable message delivery
|
||||
- Channel-based subscriptions
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
import { createEventBus } from '@stock-bot/event-bus';
|
||||
import { MarketDataEvent } from '@stock-bot/shared-types';
|
||||
|
||||
// Create an event bus instance
|
||||
const eventBus = createEventBus({
|
||||
redisHost: 'localhost',
|
||||
redisPort: 6379
|
||||
});
|
||||
|
||||
// Subscribe to market data events
|
||||
eventBus.subscribe('market.data', async (event: MarketDataEvent) => {
|
||||
console.log(`Received price update for ${event.data.symbol}: ${event.data.price}`);
|
||||
});
|
||||
|
||||
// Publish an event
|
||||
await eventBus.publish('market.data', {
|
||||
type: 'MARKET_DATA',
|
||||
data: {
|
||||
symbol: 'AAPL',
|
||||
price: 150.25,
|
||||
bid: 150.20,
|
||||
ask: 150.30,
|
||||
volume: 1000000,
|
||||
timestamp: new Date()
|
||||
},
|
||||
timestamp: new Date()
|
||||
});
|
||||
```
|
||||
22
libs/event-bus/package.json
Normal file
22
libs/event-bus/package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "@stock-bot/event-bus",
|
||||
"version": "1.0.0",
|
||||
"description": "Event bus implementation for inter-service communication",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"clean": "rm -rf dist",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stock-bot/shared-types": "workspace:*",
|
||||
"ioredis": "^5.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.2",
|
||||
"jest": "^29.5.0",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
131
libs/event-bus/src/EventBus.ts
Normal file
131
libs/event-bus/src/EventBus.ts
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import Redis from 'ioredis';
|
||||
import { Event } from '@stock-bot/shared-types';
|
||||
|
||||
export type EventHandler<T extends Event = Event> = (event: T) => Promise<void> | void;
|
||||
|
||||
export interface EventBusConfig {
|
||||
redisHost: string;
|
||||
redisPort: number;
|
||||
redisPassword?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event Bus for publishing and subscribing to events in the system
|
||||
* Provides reliable message delivery and pattern-based subscriptions
|
||||
*/
|
||||
export class EventBus {
|
||||
private publisher: Redis;
|
||||
private subscriber: Redis;
|
||||
private handlers: Map<string, EventHandler[]>;
|
||||
private isConnected: boolean = false;
|
||||
|
||||
constructor(private config: EventBusConfig) {
|
||||
this.publisher = new Redis({
|
||||
host: config.redisHost,
|
||||
port: config.redisPort,
|
||||
password: config.redisPassword,
|
||||
});
|
||||
|
||||
this.subscriber = new Redis({
|
||||
host: config.redisHost,
|
||||
port: config.redisPort,
|
||||
password: config.redisPassword,
|
||||
});
|
||||
|
||||
this.handlers = new Map<string, EventHandler[]>();
|
||||
this.setupConnectionHandlers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up Redis connection event handlers
|
||||
*/
|
||||
private setupConnectionHandlers(): void {
|
||||
this.publisher.on('connect', () => {
|
||||
console.log('Publisher connected to Redis');
|
||||
this.isConnected = true;
|
||||
});
|
||||
|
||||
this.publisher.on('error', (err) => {
|
||||
console.error('Publisher Redis error', err);
|
||||
this.isConnected = false;
|
||||
});
|
||||
|
||||
this.subscriber.on('connect', () => {
|
||||
console.log('Subscriber connected to Redis');
|
||||
});
|
||||
|
||||
this.subscriber.on('error', (err) => {
|
||||
console.error('Subscriber Redis error', err);
|
||||
});
|
||||
|
||||
this.subscriber.on('message', (channel, message) => {
|
||||
try {
|
||||
const event = JSON.parse(message) as Event;
|
||||
this.processEvent(channel, event);
|
||||
} catch (err) {
|
||||
console.error('Error processing event', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming event by calling all registered handlers
|
||||
*/
|
||||
private processEvent(channel: string, event: Event): void {
|
||||
const handlers = this.handlers.get(channel) || [];
|
||||
|
||||
handlers.forEach(handler => {
|
||||
try {
|
||||
handler(event);
|
||||
} catch (err) {
|
||||
console.error(`Error in event handler for ${channel}:`, err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish an event to the specified channel
|
||||
*/
|
||||
public async publish(channel: string, event: Event): Promise<void> {
|
||||
if (!this.isConnected) {
|
||||
throw new Error('Not connected to Redis');
|
||||
}
|
||||
|
||||
await this.publisher.publish(channel, JSON.stringify(event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to events on a specific channel
|
||||
*/
|
||||
public subscribe(channel: string, handler: EventHandler): () => void {
|
||||
if (!this.handlers.has(channel)) {
|
||||
this.handlers.set(channel, []);
|
||||
this.subscriber.subscribe(channel);
|
||||
}
|
||||
|
||||
const handlers = this.handlers.get(channel)!;
|
||||
handlers.push(handler);
|
||||
|
||||
// Return unsubscribe function
|
||||
return () => {
|
||||
const index = handlers.indexOf(handler);
|
||||
if (index >= 0) {
|
||||
handlers.splice(index, 1);
|
||||
}
|
||||
|
||||
if (handlers.length === 0) {
|
||||
this.handlers.delete(channel);
|
||||
this.subscriber.unsubscribe(channel);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Close connections
|
||||
*/
|
||||
public async close(): Promise<void> {
|
||||
await this.publisher.quit();
|
||||
await this.subscriber.quit();
|
||||
this.isConnected = false;
|
||||
}
|
||||
}
|
||||
8
libs/event-bus/src/index.ts
Normal file
8
libs/event-bus/src/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { EventBus, EventBusConfig, EventHandler } from './EventBus';
|
||||
|
||||
export { EventBus, EventBusConfig, EventHandler };
|
||||
|
||||
// Convenience function to create an event bus with the default configuration
|
||||
export function createEventBus(config: EventBusConfig): EventBus {
|
||||
return new EventBus(config);
|
||||
}
|
||||
13
libs/event-bus/tsconfig.json
Normal file
13
libs/event-bus/tsconfig.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
28
libs/shared-types/README.md
Normal file
28
libs/shared-types/README.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Shared Types Library
|
||||
|
||||
This library contains domain-specific TypeScript type definitions used across the stock-bot project.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
- `market/` - Market data structures (OHLCV, OrderBook, etc.)
|
||||
- `trading/` - Trading types (Orders, Positions, etc.)
|
||||
- `strategy/` - Strategy and signal types
|
||||
- `events/` - Event definitions for the event bus
|
||||
- `api/` - Common API request/response types
|
||||
- `config/` - Configuration type definitions
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
import { OHLCV, MarketData } from '@stock-bot/shared-types';
|
||||
|
||||
// Use the types
|
||||
const marketData: MarketData = {
|
||||
symbol: 'AAPL',
|
||||
price: 150.25,
|
||||
bid: 150.20,
|
||||
ask: 150.30,
|
||||
volume: 1000000,
|
||||
timestamp: new Date()
|
||||
};
|
||||
```
|
||||
23
libs/shared-types/package.json
Normal file
23
libs/shared-types/package.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "@stock-bot/shared-types",
|
||||
"version": "1.0.0",
|
||||
"description": "Domain-specific shared TypeScript definitions for the trading bot",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"clean": "rm -rf dist",
|
||||
"test": "echo \"No tests yet\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
44
libs/shared-types/src/api/api.ts
Normal file
44
libs/shared-types/src/api/api.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// Service Types
|
||||
export interface ServiceConfig {
|
||||
name: string;
|
||||
version: string;
|
||||
environment: 'development' | 'staging' | 'production';
|
||||
port?: number;
|
||||
dependencies?: string[];
|
||||
}
|
||||
|
||||
export interface HealthStatus {
|
||||
service: string;
|
||||
status: 'healthy' | 'unhealthy' | 'degraded';
|
||||
timestamp: Date;
|
||||
details?: Record<string, any>;
|
||||
}
|
||||
|
||||
// API Response Types
|
||||
export interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> {
|
||||
data: T[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
hasNext: boolean;
|
||||
}
|
||||
|
||||
// HTTP Response standards
|
||||
export interface ErrorResponse {
|
||||
status: number;
|
||||
code: string;
|
||||
message: string;
|
||||
details?: any;
|
||||
}
|
||||
|
||||
export interface SuccessResponse<T> {
|
||||
status: number;
|
||||
data: T;
|
||||
}
|
||||
69
libs/shared-types/src/config/config.ts
Normal file
69
libs/shared-types/src/config/config.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// Configuration Types
|
||||
export interface DatabaseConfig {
|
||||
questdb: {
|
||||
host: string;
|
||||
port: number;
|
||||
database: string;
|
||||
};
|
||||
postgres: {
|
||||
host: string;
|
||||
port: number;
|
||||
database: string;
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
dragonfly: {
|
||||
host: string;
|
||||
port: number;
|
||||
password?: string;
|
||||
};
|
||||
mongodb?: {
|
||||
uri: string;
|
||||
database: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BrokerConfig {
|
||||
name: string;
|
||||
apiKey: string;
|
||||
secretKey: string;
|
||||
baseUrl: string;
|
||||
sandbox: boolean;
|
||||
rateLimit?: {
|
||||
maxRequestsPerSecond: number;
|
||||
maxRequestsPerMinute: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DataProviderConfig {
|
||||
name: string;
|
||||
apiKey: string;
|
||||
baseUrl: string;
|
||||
rateLimits: {
|
||||
requestsPerSecond: number;
|
||||
requestsPerDay: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LoggingConfig {
|
||||
level: 'debug' | 'info' | 'warn' | 'error';
|
||||
format: 'json' | 'text';
|
||||
destination: 'console' | 'file' | 'both';
|
||||
filePath?: string;
|
||||
}
|
||||
|
||||
export interface SystemConfig {
|
||||
databases: DatabaseConfig;
|
||||
brokers: BrokerConfig[];
|
||||
dataProviders: DataProviderConfig[];
|
||||
logging: LoggingConfig;
|
||||
services: {
|
||||
[key: string]: ServiceConfig;
|
||||
};
|
||||
}
|
||||
|
||||
interface ServiceConfig {
|
||||
port: number;
|
||||
host: string;
|
||||
dependencies: string[];
|
||||
}
|
||||
42
libs/shared-types/src/events/events.ts
Normal file
42
libs/shared-types/src/events/events.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { MarketData } from '../market/market-data';
|
||||
import { Order } from '../trading/orders';
|
||||
import { TradingSignal } from '../strategy/strategy';
|
||||
|
||||
// Event Types
|
||||
export interface MarketDataEvent {
|
||||
type: 'MARKET_DATA';
|
||||
data: MarketData;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export interface OrderEvent {
|
||||
type: 'ORDER_CREATED' | 'ORDER_FILLED' | 'ORDER_CANCELLED';
|
||||
order: Order;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export interface SignalEvent {
|
||||
type: 'SIGNAL_GENERATED';
|
||||
signal: TradingSignal;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
// Define more specific events
|
||||
export interface RiskAlertEvent {
|
||||
type: 'RISK_ALERT';
|
||||
alertLevel: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
||||
message: string;
|
||||
portfolioId?: string;
|
||||
strategyId?: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export interface SystemEvent {
|
||||
type: 'SYSTEM_STARTUP' | 'SYSTEM_SHUTDOWN' | 'SERVICE_ERROR';
|
||||
serviceName: string;
|
||||
message?: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export type TradingEvent = MarketDataEvent | OrderEvent | SignalEvent;
|
||||
export type Event = TradingEvent | RiskAlertEvent | SystemEvent;
|
||||
20
libs/shared-types/src/index.ts
Normal file
20
libs/shared-types/src/index.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// Export all types from their respective domains
|
||||
|
||||
// Market Data Types
|
||||
export * from './market/market-data';
|
||||
|
||||
// Trading Types
|
||||
export * from './trading/orders';
|
||||
|
||||
// Strategy Types
|
||||
export * from './strategy/strategy';
|
||||
export * from './strategy/backtest';
|
||||
|
||||
// Events
|
||||
export * from './events/events';
|
||||
|
||||
// API Types
|
||||
export * from './api/api';
|
||||
|
||||
// Configuration Types
|
||||
export * from './config/config';
|
||||
48
libs/shared-types/src/market/market-data.ts
Normal file
48
libs/shared-types/src/market/market-data.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// Market Data Types
|
||||
export interface OHLCV {
|
||||
symbol: string;
|
||||
timestamp: Date;
|
||||
open: number;
|
||||
high: number;
|
||||
low: number;
|
||||
close: number;
|
||||
volume: number;
|
||||
}
|
||||
|
||||
export interface MarketData {
|
||||
symbol: string;
|
||||
price: number;
|
||||
bid: number;
|
||||
ask: number;
|
||||
volume: number;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export interface OrderBook {
|
||||
symbol: string;
|
||||
bids: [number, number][]; // [price, size]
|
||||
asks: [number, number][]; // [price, size]
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
// Fundamental Data Types
|
||||
export interface CompanyFundamentals {
|
||||
symbol: string;
|
||||
marketCap: number;
|
||||
peRatio?: number;
|
||||
pbRatio?: number;
|
||||
roe?: number;
|
||||
revenue: number;
|
||||
netIncome: number;
|
||||
lastUpdated: Date;
|
||||
}
|
||||
|
||||
export interface NewsItem {
|
||||
id: string;
|
||||
headline: string;
|
||||
summary: string;
|
||||
sentiment: number; // -1 to 1
|
||||
symbols: string[];
|
||||
source: string;
|
||||
publishedAt: Date;
|
||||
}
|
||||
53
libs/shared-types/src/strategy/backtest.ts
Normal file
53
libs/shared-types/src/strategy/backtest.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// Trading Signal Types
|
||||
import { SignalType } from './strategy';
|
||||
|
||||
export interface Signal {
|
||||
symbol: string;
|
||||
type: SignalType;
|
||||
strength: number; // 0-1
|
||||
price: number;
|
||||
timestamp: Date;
|
||||
strategyId: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface BacktestConfig {
|
||||
strategyId: string;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
symbols: string[];
|
||||
initialCapital: number;
|
||||
parameters: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface BacktestResult {
|
||||
id: string;
|
||||
strategyId: string;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
symbols: string[];
|
||||
initialCapital: number;
|
||||
finalCapital: number;
|
||||
totalReturn: number;
|
||||
annualizedReturn: number;
|
||||
maxDrawdown: number;
|
||||
sharpeRatio: number;
|
||||
trades: BacktestTrade[];
|
||||
dailyPerformance: DailyPerformance[];
|
||||
}
|
||||
|
||||
export interface BacktestTrade {
|
||||
symbol: string;
|
||||
side: 'BUY' | 'SELL';
|
||||
quantity: number;
|
||||
price: number;
|
||||
timestamp: Date;
|
||||
pnl?: number;
|
||||
}
|
||||
|
||||
export interface DailyPerformance {
|
||||
date: Date;
|
||||
portfolioValue: number;
|
||||
dailyPnL: number;
|
||||
dailyReturn: number;
|
||||
}
|
||||
28
libs/shared-types/src/strategy/strategy.ts
Normal file
28
libs/shared-types/src/strategy/strategy.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Strategy Types
|
||||
export type SignalType = 'BUY' | 'SELL' | 'HOLD';
|
||||
|
||||
export interface TradingSignal {
|
||||
symbol: string;
|
||||
type: SignalType;
|
||||
strength: number; // 0-1
|
||||
price: number;
|
||||
timestamp: Date;
|
||||
strategyId: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface Strategy {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
isActive: boolean;
|
||||
riskLimits: RiskLimits;
|
||||
parameters: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface RiskLimits {
|
||||
maxPositionSize: number;
|
||||
maxDailyLoss: number;
|
||||
maxDrawdown: number;
|
||||
allowedSymbols?: string[];
|
||||
}
|
||||
35
libs/shared-types/src/trading/orders.ts
Normal file
35
libs/shared-types/src/trading/orders.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// Trading Types
|
||||
export type OrderSide = 'BUY' | 'SELL';
|
||||
export type OrderType = 'MARKET' | 'LIMIT' | 'STOP' | 'STOP_LIMIT';
|
||||
export type OrderStatus = 'PENDING' | 'FILLED' | 'PARTIALLY_FILLED' | 'CANCELLED' | 'REJECTED';
|
||||
|
||||
export interface Order {
|
||||
id: string;
|
||||
symbol: string;
|
||||
side: OrderSide;
|
||||
type: OrderType;
|
||||
quantity: number;
|
||||
price?: number;
|
||||
stopPrice?: number;
|
||||
status: OrderStatus;
|
||||
timestamp: Date;
|
||||
strategyId: string;
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
symbol: string;
|
||||
quantity: number;
|
||||
averagePrice: number;
|
||||
marketValue: number;
|
||||
unrealizedPnL: number;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
export interface Portfolio {
|
||||
cash: number;
|
||||
totalValue: number;
|
||||
positions: Position[];
|
||||
dayPnL: number;
|
||||
totalPnL: number;
|
||||
timestamp: Date;
|
||||
}
|
||||
18
libs/shared-types/tsconfig.json
Normal file
18
libs/shared-types/tsconfig.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
51
libs/utils/README.md
Normal file
51
libs/utils/README.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Utils Library
|
||||
|
||||
Common utility functions shared across services in the stock-bot project.
|
||||
|
||||
## Included Utilities
|
||||
|
||||
### Date Utilities
|
||||
|
||||
Helper functions for working with market dates:
|
||||
|
||||
```typescript
|
||||
import { dateUtils } from '@stock-bot/utils';
|
||||
|
||||
// Check if a date is a trading day
|
||||
const isTradingDay = dateUtils.isTradingDay(new Date());
|
||||
|
||||
// Get the next trading day
|
||||
const nextTradingDay = dateUtils.getNextTradingDay(new Date());
|
||||
```
|
||||
|
||||
### Financial Utilities
|
||||
|
||||
Mathematical functions for financial calculations:
|
||||
|
||||
```typescript
|
||||
import { financialUtils } from '@stock-bot/utils';
|
||||
|
||||
// Calculate Sharpe ratio
|
||||
const returns = [0.05, 0.03, -0.01, 0.04, 0.02];
|
||||
const sharpeRatio = financialUtils.calculateSharpeRatio(returns, 0.02);
|
||||
|
||||
// Calculate maximum drawdown
|
||||
const equityCurve = [10000, 10500, 10200, 10800, 10300];
|
||||
const maxDrawdown = financialUtils.calculateMaxDrawdown(equityCurve);
|
||||
```
|
||||
|
||||
### Logger
|
||||
|
||||
Standardized logging service:
|
||||
|
||||
```typescript
|
||||
import { createLogger, LogLevel } from '@stock-bot/utils';
|
||||
|
||||
// Create a logger for your service
|
||||
const logger = createLogger('strategy-orchestrator', LogLevel.INFO);
|
||||
|
||||
// Log at different levels
|
||||
logger.info('Strategy initialized');
|
||||
logger.warn('Position size exceeds recommended limit');
|
||||
logger.error('Failed to execute order', { orderId: '123', reason: 'Insufficient funds' });
|
||||
```
|
||||
22
libs/utils/package.json
Normal file
22
libs/utils/package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "@stock-bot/utils",
|
||||
"version": "1.0.0",
|
||||
"description": "Common utility functions for stock-bot services",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"clean": "rm -rf dist",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stock-bot/shared-types": "workspace:*",
|
||||
"date-fns": "^2.30.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.2",
|
||||
"jest": "^29.5.0",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
55
libs/utils/src/dateUtils.ts
Normal file
55
libs/utils/src/dateUtils.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* Date and time utilities for working with market data
|
||||
*/
|
||||
export const dateUtils = {
|
||||
/**
|
||||
* Check if a date is a trading day (Monday-Friday, non-holiday)
|
||||
* This is a simplified implementation - a real version would check market holidays
|
||||
*/
|
||||
isTradingDay(date: Date): boolean {
|
||||
const day = date.getDay();
|
||||
return day > 0 && day < 6; // Mon-Fri
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the next trading day from a given date
|
||||
*/
|
||||
getNextTradingDay(date: Date): Date {
|
||||
const nextDay = new Date(date);
|
||||
nextDay.setDate(nextDay.getDate() + 1);
|
||||
|
||||
while (!this.isTradingDay(nextDay)) {
|
||||
nextDay.setDate(nextDay.getDate() + 1);
|
||||
}
|
||||
|
||||
return nextDay;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the previous trading day from a given date
|
||||
*/
|
||||
getPreviousTradingDay(date: Date): Date {
|
||||
const prevDay = new Date(date);
|
||||
prevDay.setDate(prevDay.getDate() - 1);
|
||||
|
||||
while (!this.isTradingDay(prevDay)) {
|
||||
prevDay.setDate(prevDay.getDate() - 1);
|
||||
}
|
||||
|
||||
return prevDay;
|
||||
},
|
||||
|
||||
/**
|
||||
* Format a date as YYYY-MM-DD
|
||||
*/
|
||||
formatDate(date: Date): string {
|
||||
return date.toISOString().split('T')[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse a date string in YYYY-MM-DD format
|
||||
*/
|
||||
parseDate(dateStr: string): Date {
|
||||
return new Date(dateStr);
|
||||
}
|
||||
};
|
||||
67
libs/utils/src/financialUtils.ts
Normal file
67
libs/utils/src/financialUtils.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Financial calculation utilities
|
||||
*/
|
||||
export const financialUtils = {
|
||||
/**
|
||||
* Calculate the Sharpe ratio
|
||||
* @param returns Array of period returns
|
||||
* @param riskFreeRate The risk-free rate (e.g. 0.02 for 2%)
|
||||
*/
|
||||
calculateSharpeRatio(returns: number[], riskFreeRate: number = 0.02): number {
|
||||
if (returns.length < 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate average return
|
||||
const avgReturn = returns.reduce((sum, val) => sum + val, 0) / returns.length;
|
||||
|
||||
// Calculate standard deviation
|
||||
const squaredDiffs = returns.map(val => Math.pow(val - avgReturn, 2));
|
||||
const avgSquaredDiff = squaredDiffs.reduce((sum, val) => sum + val, 0) / squaredDiffs.length;
|
||||
const stdDev = Math.sqrt(avgSquaredDiff);
|
||||
|
||||
// Avoid division by zero
|
||||
if (stdDev === 0) return 0;
|
||||
|
||||
// Calculate Sharpe ratio
|
||||
return (avgReturn - riskFreeRate) / stdDev;
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculate the maximum drawdown
|
||||
* @param equityCurve Array of equity values over time
|
||||
*/
|
||||
calculateMaxDrawdown(equityCurve: number[]): number {
|
||||
if (equityCurve.length < 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let maxDrawdown = 0;
|
||||
let peak = equityCurve[0];
|
||||
|
||||
for (let i = 1; i < equityCurve.length; i++) {
|
||||
if (equityCurve[i] > peak) {
|
||||
peak = equityCurve[i];
|
||||
} else {
|
||||
const drawdown = (peak - equityCurve[i]) / peak;
|
||||
maxDrawdown = Math.max(maxDrawdown, drawdown);
|
||||
}
|
||||
}
|
||||
|
||||
return maxDrawdown;
|
||||
},
|
||||
|
||||
/**
|
||||
* Calculate the Compound Annual Growth Rate (CAGR)
|
||||
* @param startValue Initial investment value
|
||||
* @param endValue Final investment value
|
||||
* @param years Number of years
|
||||
*/
|
||||
calculateCAGR(startValue: number, endValue: number, years: number): number {
|
||||
if (years <= 0 || startValue <= 0 || endValue <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.pow(endValue / startValue, 1 / years) - 1;
|
||||
}
|
||||
};
|
||||
3
libs/utils/src/index.ts
Normal file
3
libs/utils/src/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from './dateUtils';
|
||||
export * from './financialUtils';
|
||||
export * from './logger';
|
||||
65
libs/utils/src/logger.ts
Normal file
65
libs/utils/src/logger.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* Logger utility with consistent formatting and log levels
|
||||
*/
|
||||
export class Logger {
|
||||
constructor(private serviceName: string, private level: LogLevel = LogLevel.INFO) {}
|
||||
|
||||
debug(message: string, ...args: any[]): void {
|
||||
this.log(LogLevel.DEBUG, message, ...args);
|
||||
}
|
||||
|
||||
info(message: string, ...args: any[]): void {
|
||||
this.log(LogLevel.INFO, message, ...args);
|
||||
}
|
||||
|
||||
warn(message: string, ...args: any[]): void {
|
||||
this.log(LogLevel.WARN, message, ...args);
|
||||
}
|
||||
|
||||
error(message: string, ...args: any[]): void {
|
||||
this.log(LogLevel.ERROR, message, ...args);
|
||||
}
|
||||
|
||||
private log(level: LogLevel, message: string, ...args: any[]): void {
|
||||
if (level < this.level) return;
|
||||
|
||||
const timestamp = new Date().toISOString();
|
||||
const levelStr = LogLevel[level].padEnd(5);
|
||||
|
||||
const logMessage = `[${timestamp}] [${levelStr}] [${this.serviceName}] ${message}`;
|
||||
|
||||
switch (level) {
|
||||
case LogLevel.ERROR:
|
||||
console.error(logMessage, ...args);
|
||||
break;
|
||||
case LogLevel.WARN:
|
||||
console.warn(logMessage, ...args);
|
||||
break;
|
||||
case LogLevel.INFO:
|
||||
console.info(logMessage, ...args);
|
||||
break;
|
||||
case LogLevel.DEBUG:
|
||||
default:
|
||||
console.debug(logMessage, ...args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setLevel(level: LogLevel): void {
|
||||
this.level = level;
|
||||
}
|
||||
}
|
||||
|
||||
export enum LogLevel {
|
||||
DEBUG = 0,
|
||||
INFO = 1,
|
||||
WARN = 2,
|
||||
ERROR = 3
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new logger instance
|
||||
*/
|
||||
export function createLogger(serviceName: string, level: LogLevel = LogLevel.INFO): Logger {
|
||||
return new Logger(serviceName, level);
|
||||
}
|
||||
13
libs/utils/tsconfig.json
Normal file
13
libs/utils/tsconfig.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue