clean up
This commit is contained in:
parent
7993148a95
commit
528be93804
38 changed files with 4617 additions and 1081 deletions
27
libs/strategy-engine/package.json
Normal file
27
libs/strategy-engine/package.json
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "@stock-bot/strategy-engine",
|
||||
"version": "1.0.0",
|
||||
"description": "Strategy execution engine with multi-mode support",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"test": "bun test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stock-bot/logger": "workspace:*",
|
||||
"@stock-bot/config": "workspace:*",
|
||||
"@stock-bot/utils": "workspace:*",
|
||||
"@stock-bot/event-bus": "workspace:*",
|
||||
"@stock-bot/data-frame": "workspace:*",
|
||||
"eventemitter3": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.0",
|
||||
"typescript": "^5.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bun-types": "*"
|
||||
}
|
||||
}
|
||||
370
libs/strategy-engine/src/index.ts
Normal file
370
libs/strategy-engine/src/index.ts
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
import { EventEmitter } from 'eventemitter3';
|
||||
import { createLogger, Logger } from '@stock-bot/logger';
|
||||
import { EventBus } from '@stock-bot/event-bus';
|
||||
import { DataFrame } from '@stock-bot/data-frame';
|
||||
|
||||
// Core types
|
||||
export interface MarketData {
|
||||
symbol: string;
|
||||
timestamp: number;
|
||||
open: number;
|
||||
high: number;
|
||||
low: number;
|
||||
close: number;
|
||||
volume: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface TradingSignal {
|
||||
type: 'BUY' | 'SELL' | 'HOLD';
|
||||
symbol: string;
|
||||
timestamp: number;
|
||||
price: number;
|
||||
quantity: number;
|
||||
confidence: number;
|
||||
reason: string;
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface StrategyContext {
|
||||
symbol: string;
|
||||
timeframe: string;
|
||||
data: DataFrame;
|
||||
indicators: Record<string, any>;
|
||||
position?: Position;
|
||||
portfolio: PortfolioSummary;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
symbol: string;
|
||||
quantity: number;
|
||||
averagePrice: number;
|
||||
currentPrice: number;
|
||||
unrealizedPnL: number;
|
||||
side: 'LONG' | 'SHORT';
|
||||
}
|
||||
|
||||
export interface PortfolioSummary {
|
||||
totalValue: number;
|
||||
cash: number;
|
||||
positions: Position[];
|
||||
totalPnL: number;
|
||||
dayPnL: number;
|
||||
}
|
||||
|
||||
export interface StrategyConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
symbols: string[];
|
||||
timeframes: string[];
|
||||
parameters: Record<string, any>;
|
||||
riskLimits: RiskLimits;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface RiskLimits {
|
||||
maxPositionSize: number;
|
||||
maxDailyLoss: number;
|
||||
maxDrawdown: number;
|
||||
stopLoss?: number;
|
||||
takeProfit?: number;
|
||||
}
|
||||
|
||||
// Abstract base strategy class
|
||||
export abstract class BaseStrategy extends EventEmitter {
|
||||
protected logger;
|
||||
protected eventBus: EventBus;
|
||||
protected config: StrategyConfig;
|
||||
protected isActive: boolean = false;
|
||||
|
||||
constructor(config: StrategyConfig, eventBus: EventBus) {
|
||||
super();
|
||||
this.config = config;
|
||||
this.eventBus = eventBus;
|
||||
this.logger = createLogger(`strategy:${config.id}`);
|
||||
}
|
||||
|
||||
// Abstract methods that must be implemented by concrete strategies
|
||||
abstract initialize(): Promise<void>;
|
||||
abstract onMarketData(context: StrategyContext): Promise<TradingSignal[]>;
|
||||
abstract onSignal(signal: TradingSignal): Promise<void>;
|
||||
abstract cleanup(): Promise<void>;
|
||||
|
||||
// Optional lifecycle methods
|
||||
onStart?(): Promise<void>;
|
||||
onStop?(): Promise<void>;
|
||||
onError?(error: Error): Promise<void>;
|
||||
|
||||
// Control methods
|
||||
async start(): Promise<void> {
|
||||
if (this.isActive) {
|
||||
this.logger.warn('Strategy already active');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.initialize();
|
||||
|
||||
if (this.onStart) {
|
||||
await this.onStart();
|
||||
}
|
||||
|
||||
this.isActive = true;
|
||||
this.logger.info('Strategy started', { strategyId: this.config.id });
|
||||
this.emit('started');
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to start strategy', { error, strategyId: this.config.id });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
if (!this.isActive) {
|
||||
this.logger.warn('Strategy not active');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.onStop) {
|
||||
await this.onStop();
|
||||
}
|
||||
|
||||
await this.cleanup();
|
||||
this.isActive = false;
|
||||
this.logger.info('Strategy stopped', { strategyId: this.config.id });
|
||||
this.emit('stopped');
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to stop strategy', { error, strategyId: this.config.id });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Utility methods
|
||||
protected async emitSignal(signal: TradingSignal): Promise<void> {
|
||||
await this.eventBus.publishStrategySignal(this.config.id, signal);
|
||||
this.emit('signal', signal);
|
||||
this.logger.info('Signal generated', {
|
||||
signal: signal.type,
|
||||
symbol: signal.symbol,
|
||||
confidence: signal.confidence
|
||||
});
|
||||
}
|
||||
|
||||
protected checkRiskLimits(signal: TradingSignal, context: StrategyContext): boolean {
|
||||
const limits = this.config.riskLimits;
|
||||
|
||||
// Check position size limit
|
||||
if (signal.quantity > limits.maxPositionSize) {
|
||||
this.logger.warn('Signal exceeds max position size', {
|
||||
requested: signal.quantity,
|
||||
limit: limits.maxPositionSize
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check daily loss limit
|
||||
if (context.portfolio.dayPnL <= -limits.maxDailyLoss) {
|
||||
this.logger.warn('Daily loss limit reached', {
|
||||
dayPnL: context.portfolio.dayPnL,
|
||||
limit: -limits.maxDailyLoss
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Getters
|
||||
get id(): string {
|
||||
return this.config.id;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.config.name;
|
||||
}
|
||||
|
||||
get active(): boolean {
|
||||
return this.isActive;
|
||||
}
|
||||
|
||||
get configuration(): StrategyConfig {
|
||||
return { ...this.config };
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy execution engine
|
||||
export class StrategyEngine extends EventEmitter {
|
||||
private strategies: Map<string, BaseStrategy> = new Map();
|
||||
private logger;
|
||||
private eventBus: EventBus;
|
||||
private isRunning: boolean = false;
|
||||
|
||||
constructor(eventBus: EventBus) {
|
||||
super();
|
||||
this.eventBus = eventBus;
|
||||
this.logger = createLogger('strategy-engine');
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
// Subscribe to market data events
|
||||
await this.eventBus.subscribe('market.data', this.handleMarketData.bind(this));
|
||||
await this.eventBus.subscribe('order.update', this.handleOrderUpdate.bind(this));
|
||||
await this.eventBus.subscribe('portfolio.update', this.handlePortfolioUpdate.bind(this));
|
||||
|
||||
this.logger.info('Strategy engine initialized');
|
||||
}
|
||||
|
||||
async registerStrategy(strategy: BaseStrategy): Promise<void> {
|
||||
if (this.strategies.has(strategy.id)) {
|
||||
throw new Error(`Strategy ${strategy.id} already registered`);
|
||||
}
|
||||
|
||||
this.strategies.set(strategy.id, strategy);
|
||||
|
||||
// Forward strategy events
|
||||
strategy.on('signal', (signal) => this.emit('signal', signal));
|
||||
strategy.on('error', (error) => this.emit('error', error));
|
||||
|
||||
this.logger.info('Strategy registered', { strategyId: strategy.id });
|
||||
}
|
||||
|
||||
async unregisterStrategy(strategyId: string): Promise<void> {
|
||||
const strategy = this.strategies.get(strategyId);
|
||||
if (!strategy) {
|
||||
throw new Error(`Strategy ${strategyId} not found`);
|
||||
}
|
||||
|
||||
if (strategy.active) {
|
||||
await strategy.stop();
|
||||
}
|
||||
|
||||
strategy.removeAllListeners();
|
||||
this.strategies.delete(strategyId);
|
||||
|
||||
this.logger.info('Strategy unregistered', { strategyId });
|
||||
}
|
||||
|
||||
async startStrategy(strategyId: string): Promise<void> {
|
||||
const strategy = this.strategies.get(strategyId);
|
||||
if (!strategy) {
|
||||
throw new Error(`Strategy ${strategyId} not found`);
|
||||
}
|
||||
|
||||
await strategy.start();
|
||||
}
|
||||
|
||||
async stopStrategy(strategyId: string): Promise<void> {
|
||||
const strategy = this.strategies.get(strategyId);
|
||||
if (!strategy) {
|
||||
throw new Error(`Strategy ${strategyId} not found`);
|
||||
}
|
||||
|
||||
await strategy.stop();
|
||||
}
|
||||
|
||||
async startAll(): Promise<void> {
|
||||
if (this.isRunning) {
|
||||
this.logger.warn('Engine already running');
|
||||
return;
|
||||
}
|
||||
|
||||
const startPromises = Array.from(this.strategies.values())
|
||||
.filter(strategy => strategy.configuration.enabled)
|
||||
.map(strategy => strategy.start());
|
||||
|
||||
await Promise.all(startPromises);
|
||||
this.isRunning = true;
|
||||
this.logger.info('All strategies started');
|
||||
this.emit('started');
|
||||
}
|
||||
|
||||
async stopAll(): Promise<void> {
|
||||
if (!this.isRunning) {
|
||||
this.logger.warn('Engine not running');
|
||||
return;
|
||||
}
|
||||
|
||||
const stopPromises = Array.from(this.strategies.values())
|
||||
.filter(strategy => strategy.active)
|
||||
.map(strategy => strategy.stop());
|
||||
|
||||
await Promise.all(stopPromises);
|
||||
this.isRunning = false;
|
||||
this.logger.info('All strategies stopped');
|
||||
this.emit('stopped');
|
||||
}
|
||||
|
||||
private async handleMarketData(message: any): Promise<void> {
|
||||
const { symbol, ...data } = message.data;
|
||||
|
||||
// Find strategies that trade this symbol
|
||||
const relevantStrategies = Array.from(this.strategies.values())
|
||||
.filter(strategy =>
|
||||
strategy.active &&
|
||||
strategy.configuration.symbols.includes(symbol)
|
||||
);
|
||||
|
||||
for (const strategy of relevantStrategies) {
|
||||
try {
|
||||
// Create context for this strategy
|
||||
const context: StrategyContext = {
|
||||
symbol,
|
||||
timeframe: '1m', // TODO: Get from strategy config
|
||||
data: new DataFrame([data]), // TODO: Use historical data
|
||||
indicators: {},
|
||||
portfolio: {
|
||||
totalValue: 100000, // TODO: Get real portfolio data
|
||||
cash: 50000,
|
||||
positions: [],
|
||||
totalPnL: 0,
|
||||
dayPnL: 0
|
||||
},
|
||||
timestamp: data.timestamp
|
||||
};
|
||||
|
||||
const signals = await strategy.onMarketData(context);
|
||||
|
||||
for (const signal of signals) {
|
||||
await strategy.onSignal(signal);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('Error processing market data for strategy', {
|
||||
error,
|
||||
strategyId: strategy.id,
|
||||
symbol
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async handleOrderUpdate(message: any): Promise<void> {
|
||||
// Handle order updates - notify relevant strategies
|
||||
this.logger.debug('Order update received', { data: message.data });
|
||||
}
|
||||
|
||||
private async handlePortfolioUpdate(message: any): Promise<void> {
|
||||
// Handle portfolio updates - notify relevant strategies
|
||||
this.logger.debug('Portfolio update received', { data: message.data });
|
||||
}
|
||||
|
||||
getStrategy(strategyId: string): BaseStrategy | undefined {
|
||||
return this.strategies.get(strategyId);
|
||||
}
|
||||
|
||||
getStrategies(): BaseStrategy[] {
|
||||
return Array.from(this.strategies.values());
|
||||
}
|
||||
|
||||
getActiveStrategies(): BaseStrategy[] {
|
||||
return this.getStrategies().filter(strategy => strategy.active);
|
||||
}
|
||||
|
||||
async shutdown(): Promise<void> {
|
||||
await this.stopAll();
|
||||
this.strategies.clear();
|
||||
this.removeAllListeners();
|
||||
this.logger.info('Strategy engine shutdown');
|
||||
}
|
||||
}
|
||||
19
libs/strategy-engine/tsconfig.json
Normal file
19
libs/strategy-engine/tsconfig.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"declaration": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"],
|
||||
"references": [
|
||||
{ "path": "../logger" },
|
||||
{ "path": "../types" },
|
||||
{ "path": "../event-bus" },
|
||||
{ "path": "../data-frame" },
|
||||
{ "path": "../http-client" },
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue