improved dashboard
This commit is contained in:
parent
114c280734
commit
90168ba619
8 changed files with 781 additions and 47 deletions
409
apps/intelligence-services/strategy-orchestrator/src/index.ts
Normal file
409
apps/intelligence-services/strategy-orchestrator/src/index.ts
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
import { Hono } from 'hono';
|
||||
import { WebSocketServer } from 'ws';
|
||||
import Redis from 'ioredis';
|
||||
import * as cron from 'node-cron';
|
||||
|
||||
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 });
|
||||
|
||||
// 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>();
|
||||
|
||||
// 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,
|
||||
connections: wss.clients.size
|
||||
});
|
||||
});
|
||||
|
||||
// Get all strategies
|
||||
app.get('/api/strategies', async (c) => {
|
||||
try {
|
||||
const strategiesList = Array.from(strategies.values());
|
||||
return c.json({
|
||||
success: true,
|
||||
data: strategiesList
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching strategies:', error);
|
||||
return c.json({ success: false, error: 'Failed to fetch strategies' }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// Get specific strategy
|
||||
app.get('/api/strategies/:id', 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);
|
||||
}
|
||||
|
||||
return c.json({ success: true, data: strategy });
|
||||
} catch (error) {
|
||||
console.error('Error fetching strategy:', error);
|
||||
return c.json({ success: false, error: 'Failed to fetch strategy' }, 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);
|
||||
}
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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 || '3003');
|
||||
|
||||
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,
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue