706 lines
20 KiB
TypeScript
706 lines
20 KiB
TypeScript
import { Hono } from 'hono';
|
|
import { WebSocketServer } from 'ws';
|
|
import Redis from 'ioredis';
|
|
import * as cron from 'node-cron';
|
|
import { BaseStrategy } from './core/Strategy';
|
|
import { StrategyRegistry } from './core/strategies/StrategyRegistry';
|
|
import { BacktestService, BacktestRequest } from './core/backtesting/BacktestService';
|
|
import { BacktestResult } from './core/backtesting/BacktestEngine';
|
|
import { PerformanceAnalytics } from './core/backtesting/PerformanceAnalytics';
|
|
import { StrategyController } from './controllers/StrategyController';
|
|
|
|
const app = new Hono();
|
|
const redis = new Redis({
|
|
host: process.env.REDIS_HOST || 'localhost',
|
|
port: parseInt(process.env.REDIS_PORT || '6379'),
|
|
enableReadyCheck: false,
|
|
maxRetriesPerRequest: null,
|
|
});
|
|
|
|
// WebSocket server for real-time strategy updates
|
|
const wss = new WebSocketServer({ port: 8082 });
|
|
|
|
// Initialize strategy registry and backtest service
|
|
const strategyRegistry = StrategyRegistry.getInstance();
|
|
const backtestService = new BacktestService();
|
|
|
|
// Strategy interfaces
|
|
interface TradingStrategy {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
status: 'ACTIVE' | 'INACTIVE' | 'PAUSED' | 'ERROR';
|
|
type: 'MOMENTUM' | 'MEAN_REVERSION' | 'ARBITRAGE' | 'CUSTOM';
|
|
symbols: string[];
|
|
parameters: Record<string, any>;
|
|
performance: {
|
|
totalTrades: number;
|
|
winRate: number;
|
|
totalReturn: number;
|
|
sharpeRatio: number;
|
|
maxDrawdown: number;
|
|
};
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
interface StrategySignal {
|
|
strategyId: string;
|
|
symbol: string;
|
|
action: 'BUY' | 'SELL' | 'HOLD';
|
|
confidence: number;
|
|
price: number;
|
|
quantity: number;
|
|
timestamp: Date;
|
|
metadata: Record<string, any>;
|
|
}
|
|
|
|
// In-memory strategy registry (in production, this would be persisted)
|
|
const strategies = new Map<string, TradingStrategy>();
|
|
|
|
// Initialize strategy controller
|
|
const strategyController = new StrategyController();
|
|
|
|
// Health check endpoint
|
|
app.get('/health', (c) => {
|
|
return c.json({
|
|
service: 'strategy-orchestrator',
|
|
status: 'healthy',
|
|
timestamp: new Date(),
|
|
version: '1.0.0',
|
|
activeStrategies: Array.from(strategies.values()).filter(s => s.status === 'ACTIVE').length,
|
|
registeredStrategies: strategyRegistry.getAllStrategies().length,
|
|
connections: wss.clients.size
|
|
});
|
|
});
|
|
|
|
// API Routes
|
|
// Strategy management endpoints
|
|
app.get('/api/strategy-types', async (c) => {
|
|
try {
|
|
const types = Object.values(strategyRegistry.getStrategyTypes());
|
|
return c.json({ success: true, data: types });
|
|
} catch (error) {
|
|
console.error('Error getting strategy types:', error);
|
|
return c.json({ success: false, error: 'Failed to get strategy types' }, 500);
|
|
}
|
|
});
|
|
|
|
app.get('/api/strategies', async (c) => {
|
|
try {
|
|
const strategies = strategyRegistry.getAllStrategies();
|
|
const serializedStrategies = strategies.map(strategy => ({
|
|
id: strategy.id,
|
|
name: strategy.name,
|
|
description: strategy.description,
|
|
symbols: strategy.symbols,
|
|
parameters: strategy.parameters,
|
|
type: strategyRegistry.getStrategyType(strategy)
|
|
}));
|
|
|
|
return c.json({ success: true, data: serializedStrategies });
|
|
} catch (error) {
|
|
console.error('Error fetching strategies:', error);
|
|
return c.json({ success: false, error: 'Failed to fetch strategies' }, 500);
|
|
}
|
|
});
|
|
|
|
app.get('/api/strategies/:id', async (c) => {
|
|
try {
|
|
const id = c.req.param('id');
|
|
const strategy = strategyRegistry.getStrategyById(id);
|
|
|
|
if (!strategy) {
|
|
return c.json({ success: false, error: `Strategy with ID ${id} not found` }, 404);
|
|
}
|
|
|
|
const type = strategyRegistry.getStrategyType(strategy);
|
|
|
|
return c.json({
|
|
success: true,
|
|
data: {
|
|
id: strategy.id,
|
|
name: strategy.name,
|
|
description: strategy.description,
|
|
symbols: strategy.symbols,
|
|
parameters: strategy.parameters,
|
|
type
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching strategy:', error);
|
|
return c.json({ success: false, error: 'Failed to fetch strategy' }, 500);
|
|
}
|
|
});
|
|
|
|
app.post('/api/strategies', async (c) => {
|
|
try {
|
|
const { name, description, symbols, parameters, type } = await c.req.json();
|
|
|
|
if (!type) {
|
|
return c.json({
|
|
success: false,
|
|
error: 'Invalid strategy type'
|
|
}, 400);
|
|
}
|
|
|
|
const strategy = strategyRegistry.createStrategy(
|
|
type,
|
|
`strategy_${Date.now()}`, // Generate an ID
|
|
name || `New ${type} Strategy`,
|
|
description || `Generated ${type} strategy`,
|
|
symbols || [],
|
|
parameters || {}
|
|
);
|
|
|
|
return c.json({
|
|
success: true,
|
|
data: {
|
|
id: strategy.id,
|
|
name: strategy.name,
|
|
description: strategy.description,
|
|
symbols: strategy.symbols,
|
|
parameters: strategy.parameters,
|
|
type
|
|
}
|
|
}, 201);
|
|
} catch (error) {
|
|
console.error('Error creating strategy:', error);
|
|
return c.json({ success: false, error: (error as Error).message }, 500);
|
|
}
|
|
});
|
|
|
|
app.put('/api/strategies/:id', async (c) => {
|
|
try {
|
|
const id = c.req.param('id');
|
|
const { name, description, symbols, parameters } = await c.req.json();
|
|
|
|
const strategy = strategyRegistry.getStrategyById(id);
|
|
|
|
if (!strategy) {
|
|
return c.json({ success: false, error: `Strategy with ID ${id} not found` }, 404);
|
|
}
|
|
|
|
// Update properties
|
|
if (name !== undefined) strategy.name = name;
|
|
if (description !== undefined) strategy.description = description;
|
|
if (symbols !== undefined) (strategy as any).symbols = symbols; // Hack since symbols is readonly
|
|
if (parameters !== undefined) strategy.parameters = parameters;
|
|
|
|
return c.json({
|
|
success: true,
|
|
data: {
|
|
id: strategy.id,
|
|
name: strategy.name,
|
|
description: strategy.description,
|
|
symbols: strategy.symbols,
|
|
parameters: strategy.parameters,
|
|
type: strategyRegistry.getStrategyType(strategy)
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Error updating strategy:', error);
|
|
return c.json({ success: false, error: (error as Error).message }, 500);
|
|
}
|
|
});
|
|
|
|
app.delete('/api/strategies/:id', async (c) => {
|
|
try {
|
|
const id = c.req.param('id');
|
|
const success = strategyRegistry.deleteStrategy(id);
|
|
|
|
if (!success) {
|
|
return c.json({ success: false, error: `Strategy with ID ${id} not found` }, 404);
|
|
}
|
|
|
|
return c.json({ success: true, data: { id } });
|
|
} catch (error) {
|
|
console.error('Error deleting strategy:', error);
|
|
return c.json({ success: false, error: (error as Error).message }, 500);
|
|
}
|
|
});
|
|
|
|
// Backtesting endpoints
|
|
app.post('/api/backtest', async (c) => {
|
|
try {
|
|
const backtestRequest = await c.req.json() as BacktestRequest;
|
|
|
|
// Validate request
|
|
if (!backtestRequest.strategyType) {
|
|
return c.json({ success: false, error: 'Strategy type is required' }, 400);
|
|
}
|
|
|
|
if (!backtestRequest.symbols || backtestRequest.symbols.length === 0) {
|
|
return c.json({ success: false, error: 'At least one symbol is required' }, 400);
|
|
}
|
|
|
|
// Run the backtest
|
|
const result = await backtestService.runBacktest(backtestRequest);
|
|
|
|
// Enhance results with additional metrics
|
|
const enhancedResult = PerformanceAnalytics.enhanceResults(result);
|
|
|
|
// Calculate additional analytics
|
|
const monthlyReturns = PerformanceAnalytics.calculateMonthlyReturns(result.dailyReturns);
|
|
const drawdowns = PerformanceAnalytics.analyzeDrawdowns(result.dailyReturns);
|
|
|
|
return c.json({
|
|
success: true,
|
|
data: {
|
|
...enhancedResult,
|
|
monthlyReturns,
|
|
drawdowns
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Backtest error:', error);
|
|
return c.json({ success: false, error: (error as Error).message }, 500);
|
|
}
|
|
});
|
|
|
|
app.post('/api/optimize', async (c) => {
|
|
try {
|
|
const { baseRequest, parameterGrid } = await c.req.json();
|
|
|
|
// Validate request
|
|
if (!baseRequest || !parameterGrid) {
|
|
return c.json({ success: false, error: 'Base request and parameter grid are required' }, 400);
|
|
}
|
|
|
|
// Run optimization
|
|
const results = await backtestService.optimizeStrategy(baseRequest, parameterGrid);
|
|
|
|
return c.json({ success: true, data: results });
|
|
} catch (error) {
|
|
console.error('Strategy optimization error:', error);
|
|
return c.json({ success: false, error: (error as Error).message }, 500);
|
|
}
|
|
});
|
|
|
|
// Create new strategy
|
|
app.post('/api/strategies', async (c) => {
|
|
try {
|
|
const strategyData = await c.req.json();
|
|
|
|
const strategy: TradingStrategy = {
|
|
id: `strategy_${Date.now()}`,
|
|
name: strategyData.name,
|
|
description: strategyData.description || '',
|
|
status: 'INACTIVE',
|
|
type: strategyData.type || 'CUSTOM',
|
|
symbols: strategyData.symbols || [],
|
|
parameters: strategyData.parameters || {},
|
|
performance: {
|
|
totalTrades: 0,
|
|
winRate: 0,
|
|
totalReturn: 0,
|
|
sharpeRatio: 0,
|
|
maxDrawdown: 0
|
|
},
|
|
createdAt: new Date(),
|
|
updatedAt: new Date()
|
|
};
|
|
|
|
strategies.set(strategy.id, strategy);
|
|
|
|
// Store in Redis for persistence
|
|
await redis.setex(
|
|
`strategy:${strategy.id}`,
|
|
86400, // 24 hours TTL
|
|
JSON.stringify(strategy)
|
|
);
|
|
|
|
// Broadcast to connected clients
|
|
broadcastToClients({
|
|
type: 'STRATEGY_CREATED',
|
|
data: strategy,
|
|
timestamp: new Date()
|
|
});
|
|
|
|
return c.json({ success: true, data: strategy });
|
|
} catch (error) {
|
|
console.error('Error creating strategy:', error);
|
|
return c.json({ success: false, error: 'Failed to create strategy' }, 500);
|
|
}
|
|
});
|
|
|
|
// Update strategy
|
|
app.put('/api/strategies/:id', async (c) => {
|
|
try {
|
|
const id = c.req.param('id');
|
|
const updateData = await c.req.json();
|
|
const strategy = strategies.get(id);
|
|
|
|
if (!strategy) {
|
|
return c.json({ success: false, error: 'Strategy not found' }, 404);
|
|
}
|
|
|
|
const updatedStrategy = {
|
|
...strategy,
|
|
...updateData,
|
|
id, // Ensure ID doesn't change
|
|
updatedAt: new Date()
|
|
};
|
|
|
|
strategies.set(id, updatedStrategy);
|
|
|
|
// Update in Redis
|
|
await redis.setex(
|
|
`strategy:${id}`,
|
|
86400,
|
|
JSON.stringify(updatedStrategy)
|
|
);
|
|
|
|
// Broadcast update
|
|
broadcastToClients({
|
|
type: 'STRATEGY_UPDATED',
|
|
data: updatedStrategy,
|
|
timestamp: new Date()
|
|
});
|
|
|
|
return c.json({ success: true, data: updatedStrategy });
|
|
} catch (error) {
|
|
console.error('Error updating strategy:', error);
|
|
return c.json({ success: false, error: 'Failed to update strategy' }, 500);
|
|
}
|
|
});
|
|
|
|
// Start/Stop strategy
|
|
app.post('/api/strategies/:id/:action', async (c) => {
|
|
try {
|
|
const id = c.req.param('id');
|
|
const action = c.req.param('action');
|
|
const strategy = strategies.get(id);
|
|
|
|
if (!strategy) {
|
|
return c.json({ success: false, error: 'Strategy not found' }, 404);
|
|
}
|
|
|
|
if (!['start', 'stop', 'pause'].includes(action)) {
|
|
return c.json({ success: false, error: 'Invalid action' }, 400);
|
|
}
|
|
|
|
const statusMap = {
|
|
start: 'ACTIVE' as const,
|
|
stop: 'INACTIVE' as const,
|
|
pause: 'PAUSED' as const
|
|
};
|
|
|
|
strategy.status = statusMap[action as keyof typeof statusMap];
|
|
strategy.updatedAt = new Date();
|
|
|
|
strategies.set(id, strategy);
|
|
|
|
// Update in Redis
|
|
await redis.setex(
|
|
`strategy:${id}`,
|
|
86400,
|
|
JSON.stringify(strategy)
|
|
);
|
|
|
|
// Broadcast status change
|
|
broadcastToClients({
|
|
type: 'STRATEGY_STATUS_CHANGED',
|
|
data: { id, status: strategy.status, action },
|
|
timestamp: new Date()
|
|
});
|
|
|
|
return c.json({ success: true, data: strategy });
|
|
} catch (error) {
|
|
console.error('Error changing strategy status:', error);
|
|
return c.json({ success: false, error: 'Failed to change strategy status' }, 500);
|
|
}
|
|
});
|
|
|
|
// Get strategy signals
|
|
app.get('/api/strategies/:id/signals', async (c) => {
|
|
try {
|
|
const id = c.req.param('id');
|
|
const limit = parseInt(c.req.query('limit') || '50');
|
|
|
|
const signalKeys = await redis.keys(`signal:${id}:*`);
|
|
const signals: any[] = [];
|
|
|
|
for (const key of signalKeys.slice(0, limit)) {
|
|
const data = await redis.get(key);
|
|
if (data) {
|
|
signals.push(JSON.parse(data));
|
|
}
|
|
}
|
|
|
|
return c.json({
|
|
success: true,
|
|
data: signals.sort((a: any, b: any) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching strategy signals:', error);
|
|
return c.json({ success: false, error: 'Failed to fetch signals' }, 500);
|
|
}
|
|
});
|
|
|
|
// Get strategy trades
|
|
app.get('/api/strategies/:id/trades', async (c) => {
|
|
try {
|
|
const id = c.req.param('id');
|
|
const limit = parseInt(c.req.query('limit') || '50');
|
|
|
|
const tradeKeys = await redis.keys(`trade:${id}:*`);
|
|
const trades: any[] = [];
|
|
|
|
for (const key of tradeKeys.slice(0, limit)) {
|
|
const data = await redis.get(key);
|
|
if (data) {
|
|
trades.push(JSON.parse(data));
|
|
}
|
|
}
|
|
|
|
return c.json({
|
|
success: true,
|
|
data: trades.sort((a: any, b: any) => new Date(b.exitTime || b.timestamp).getTime() - new Date(a.exitTime || a.timestamp).getTime())
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching strategy trades:', error);
|
|
return c.json({ success: false, error: 'Failed to fetch trades' }, 500);
|
|
}
|
|
});
|
|
|
|
// Generate demo signal (for testing)
|
|
app.post('/api/strategies/:id/generate-signal', async (c) => {
|
|
try {
|
|
const id = c.req.param('id');
|
|
const strategy = strategies.get(id);
|
|
|
|
if (!strategy) {
|
|
return c.json({ success: false, error: 'Strategy not found' }, 404);
|
|
}
|
|
|
|
if (strategy.status !== 'ACTIVE') {
|
|
return c.json({ success: false, error: 'Strategy is not active' }, 400);
|
|
}
|
|
|
|
// Generate demo signal
|
|
const symbol = strategy.symbols[Math.floor(Math.random() * strategy.symbols.length)] || 'AAPL';
|
|
const signal: StrategySignal = {
|
|
strategyId: id,
|
|
symbol,
|
|
action: ['BUY', 'SELL', 'HOLD'][Math.floor(Math.random() * 3)] as any,
|
|
confidence: Math.random() * 0.4 + 0.6, // 60-100% confidence
|
|
price: 150 + Math.random() * 50,
|
|
quantity: Math.floor(Math.random() * 100) + 1,
|
|
timestamp: new Date(),
|
|
metadata: {
|
|
indicator1: Math.random(),
|
|
indicator2: Math.random(),
|
|
rsi: Math.random() * 100
|
|
}
|
|
};
|
|
|
|
// Store signal
|
|
await redis.setex(
|
|
`signal:${id}:${Date.now()}`,
|
|
3600, // 1 hour TTL
|
|
JSON.stringify(signal)
|
|
);
|
|
|
|
// Broadcast signal
|
|
broadcastToClients({
|
|
type: 'STRATEGY_SIGNAL',
|
|
data: signal,
|
|
timestamp: new Date()
|
|
});
|
|
|
|
// Publish to trading system
|
|
await redis.publish('trading:signals', JSON.stringify(signal));
|
|
|
|
return c.json({ success: true, data: signal });
|
|
} catch (error) {
|
|
console.error('Error generating signal:', error);
|
|
return c.json({ success: false, error: 'Failed to generate signal' }, 500);
|
|
}
|
|
});
|
|
|
|
// WebSocket connection handling
|
|
wss.on('connection', (ws) => {
|
|
console.log('New strategy monitoring client connected');
|
|
|
|
ws.send(JSON.stringify({
|
|
type: 'CONNECTED',
|
|
message: 'Connected to Strategy Orchestrator',
|
|
timestamp: new Date()
|
|
}));
|
|
|
|
ws.on('close', () => {
|
|
console.log('Strategy monitoring client disconnected');
|
|
});
|
|
|
|
ws.on('error', (error) => {
|
|
console.error('WebSocket error:', error);
|
|
});
|
|
});
|
|
|
|
function broadcastToClients(message: any) {
|
|
const messageStr = JSON.stringify(message);
|
|
wss.clients.forEach(client => {
|
|
if (client.readyState === 1) { // WebSocket.OPEN
|
|
client.send(messageStr);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Scheduled tasks for strategy management
|
|
cron.schedule('*/5 * * * *', async () => {
|
|
// Every 5 minutes: Check strategy health and generate signals for active strategies
|
|
console.log('Running strategy health check...');
|
|
|
|
for (const [id, strategy] of strategies.entries()) {
|
|
if (strategy.status === 'ACTIVE') {
|
|
try {
|
|
// Generate signals for active strategies (demo mode)
|
|
if (Math.random() > 0.7) { // 30% chance to generate a signal
|
|
const symbol = strategy.symbols[Math.floor(Math.random() * strategy.symbols.length)] || 'AAPL';
|
|
const signal: StrategySignal = {
|
|
strategyId: id,
|
|
symbol,
|
|
action: ['BUY', 'SELL'][Math.floor(Math.random() * 2)] as any,
|
|
confidence: Math.random() * 0.3 + 0.7,
|
|
price: 150 + Math.random() * 50,
|
|
quantity: Math.floor(Math.random() * 100) + 1,
|
|
timestamp: new Date(),
|
|
metadata: { scheduled: true }
|
|
};
|
|
|
|
await redis.setex(
|
|
`signal:${id}:${Date.now()}`,
|
|
3600,
|
|
JSON.stringify(signal)
|
|
);
|
|
|
|
broadcastToClients({
|
|
type: 'STRATEGY_SIGNAL',
|
|
data: signal,
|
|
timestamp: new Date()
|
|
});
|
|
|
|
await redis.publish('trading:signals', JSON.stringify(signal));
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error in scheduled task for strategy ${id}:`, error);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Backtesting API endpoints
|
|
app.post('/api/backtest', async (c) => {
|
|
try {
|
|
const request = await c.req.json() as BacktestRequest;
|
|
console.log('Received backtest request:', request);
|
|
|
|
const result = await backtestService.runBacktest(request);
|
|
const enhancedResult = PerformanceAnalytics.enhanceResults(result);
|
|
|
|
// Store backtest result in Redis for persistence
|
|
await redis.setex(
|
|
`backtest:${result.strategyId}`,
|
|
86400 * 7, // 7 days TTL
|
|
JSON.stringify(enhancedResult)
|
|
);
|
|
|
|
return c.json({ success: true, data: enhancedResult });
|
|
} catch (error) {
|
|
console.error('Backtest error:', error);
|
|
return c.json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
}, 500);
|
|
}
|
|
});
|
|
|
|
app.post('/api/backtest/optimize', async (c) => {
|
|
try {
|
|
const { baseRequest, parameterGrid } = await c.req.json() as {
|
|
baseRequest: BacktestRequest,
|
|
parameterGrid: Record<string, any[]>
|
|
};
|
|
|
|
console.log('Received optimization request:', baseRequest, parameterGrid);
|
|
|
|
const results = await backtestService.optimizeStrategy(baseRequest, parameterGrid);
|
|
|
|
return c.json({ success: true, data: results });
|
|
} catch (error) {
|
|
console.error('Optimization error:', error);
|
|
return c.json({
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
}, 500);
|
|
}
|
|
});
|
|
|
|
app.get('/api/backtest/:id', async (c) => {
|
|
try {
|
|
const id = c.req.param('id');
|
|
const data = await redis.get(`backtest:${id}`);
|
|
|
|
if (!data) {
|
|
return c.json({ success: false, error: 'Backtest not found' }, 404);
|
|
}
|
|
|
|
const result = JSON.parse(data) as BacktestResult;
|
|
return c.json({ success: true, data: result });
|
|
} catch (error) {
|
|
console.error('Error fetching backtest:', error);
|
|
return c.json({ success: false, error: 'Failed to fetch backtest' }, 500);
|
|
}
|
|
});
|
|
|
|
app.get('/api/strategy-types', (c) => {
|
|
const types = strategyRegistry.getStrategyTypes();
|
|
return c.json({ success: true, data: types });
|
|
});
|
|
|
|
app.get('/api/strategy-parameters/:type', (c) => {
|
|
try {
|
|
const type = c.req.param('type') as any;
|
|
|
|
if (!strategyRegistry.hasStrategyType(type)) {
|
|
return c.json({ success: false, error: 'Strategy type not found' }, 404);
|
|
}
|
|
|
|
const params = strategyRegistry.getDefaultParameters(type);
|
|
return c.json({ success: true, data: params });
|
|
} catch (error) {
|
|
console.error('Error fetching strategy parameters:', error);
|
|
return c.json({ success: false, error: 'Failed to fetch parameters' }, 500);
|
|
}
|
|
});
|
|
|
|
// Load existing strategies from Redis on startup
|
|
async function loadStrategiesFromRedis() {
|
|
try {
|
|
const strategyKeys = await redis.keys('strategy:*');
|
|
for (const key of strategyKeys) {
|
|
const data = await redis.get(key);
|
|
if (data) {
|
|
const strategy = JSON.parse(data);
|
|
strategies.set(strategy.id, strategy);
|
|
}
|
|
}
|
|
console.log(`Loaded ${strategies.size} strategies from Redis`);
|
|
} catch (error) {
|
|
console.error('Error loading strategies from Redis:', error);
|
|
}
|
|
}
|
|
|
|
const port = parseInt(process.env.PORT || '4001');
|
|
|
|
console.log(`🎯 Strategy Orchestrator starting on port ${port}`);
|
|
console.log(`📡 WebSocket server running on port 8082`);
|
|
|
|
// Load existing strategies
|
|
loadStrategiesFromRedis();
|
|
|
|
export default {
|
|
port,
|
|
fetch: app.fetch,
|
|
};
|