behemoth-cli
Version:
š BEHEMOTH CLIv3.760.4 - Level 50+ POST-SINGULARITY Intelligence Trading AI
384 lines (333 loc) ⢠11.6 kB
text/typescript
/**
* Chart Command - Interactive ASCII charts for crypto data
*/
import { BaseCommand } from '../base.js';
import { Agent } from '../../core/agent.js';
import InteractiveChartManager, { ChartConfig, MultiSymbolDashboard, useChartData } from '../../ui/components/display/InteractiveChartManager.js';
import { render } from 'ink';
import React from 'react';
export interface ChartCommandArgs {
symbol?: string;
type?: 'price' | 'rsi' | 'volume' | 'all';
timeframe?: '1m' | '5m' | '15m' | '1h' | '4h' | '1d';
interactive?: boolean;
dashboard?: boolean;
}
export class ChartCommand extends BaseCommand {
command = 'chart';
description = 'Display interactive ASCII charts for crypto analysis';
examples = [
'/chart --symbol=BTCUSDT --type=price',
'/chart --symbol=ETHUSDT --type=all',
'/chart --dashboard',
'/chart --symbol=BTCUSDT --interactive'
];
handler(context: import('../base.js').CommandContext): void {
// This handler is for the CommandDefinition interface
// The actual chart functionality is in the execute method
console.log('Chart command handler called');
}
async execute(args: ChartCommandArgs, agent: Agent): Promise<string> {
const {
symbol = 'BTCUSDT',
type = 'price',
timeframe = '1h',
interactive = true,
dashboard = false
} = args;
try {
if (dashboard) {
return this.showDashboard(agent);
}
if (interactive) {
return this.showInteractiveCharts(symbol, type, timeframe, agent);
}
// Static chart fallback
return this.generateStaticChart(symbol, type, timeframe, agent);
} catch (error) {
return `ā Failed to generate chart: ${error instanceof Error ? error.message : 'Unknown error'}`;
}
}
private async showDashboard(agent: Agent): Promise<string> {
const popularSymbols = [
'BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'ADAUSDT', 'XRPUSDT',
'SOLUSDT', 'DOTUSDT', 'DOGEUSDT', 'AVAXUSDT', 'MATICUSDT'
];
return new Promise((resolve) => {
const DashboardApp = () => {
return React.createElement(MultiSymbolDashboard, {
symbols: popularSymbols,
onSymbolSelect: async (selectedSymbol: string) => {
// Close dashboard and show charts for selected symbol
const result = await this.showInteractiveCharts(selectedSymbol, 'all', '1h', agent);
resolve(result);
},
onClose: () => {
resolve('š Chart dashboard closed');
}
});
};
// Render the dashboard
const { unmount } = render(React.createElement(DashboardApp));
// Auto-cleanup after 60 seconds
setTimeout(() => {
unmount();
resolve('š Chart dashboard timed out');
}, 60000);
});
}
private async showInteractiveCharts(
symbol: string,
type: string,
timeframe: string,
agent: Agent
): Promise<string> {
// Fetch real data using BEHEMOTH tools
const chartConfigs = await this.generateChartConfigs(symbol, type, timeframe, agent);
return new Promise((resolve) => {
const ChartApp = () => {
return React.createElement(InteractiveChartManager, {
charts: chartConfigs,
onRefresh: async (chartId: string) => {
// Refresh specific chart data
await this.refreshChartData(chartId, symbol, agent);
},
onClose: () => {
resolve(`š Interactive charts for ${symbol} closed`);
}
});
};
// Render the interactive charts
const { unmount } = render(React.createElement(ChartApp));
// Auto-cleanup after 5 minutes
setTimeout(() => {
unmount();
resolve(`š Interactive charts for ${symbol} timed out`);
}, 300000);
});
}
private async generateChartConfigs(
symbol: string,
type: string,
timeframe: string,
agent: Agent
): Promise<ChartConfig[]> {
const configs: ChartConfig[] = [];
try {
if (type === 'price' || type === 'all') {
// Get price data
const priceResult = await agent.executeTool('mcp__behemoth__binance_spot_ticker', { symbol });
const klineResult = await agent.executeTool('mcp__behemoth__bitget_klines', {
symbol,
interval: timeframe,
limit: 20
});
if (klineResult?.success && klineResult.data?.length) {
const prices = klineResult.data.map((candle: any) => parseFloat(candle.close));
const timestamps = klineResult.data.map((candle: any) =>
new Date(candle.timestamp).toLocaleTimeString()
);
configs.push({
id: `price_${symbol}`,
title: `${symbol} Price`,
type: 'price',
symbol,
data: prices,
timestamps,
autoRefresh: true,
refreshInterval: 30
});
}
}
if (type === 'rsi' || type === 'all') {
// Get RSI data
const rsiResult = await agent.executeTool('mcp__behemoth__rsi_analysis', { symbol });
if (rsiResult?.success && rsiResult.data?.rsi_values) {
configs.push({
id: `rsi_${symbol}`,
title: `${symbol} RSI`,
type: 'rsi',
symbol,
data: rsiResult.data.rsi_values,
timestamps: rsiResult.data.timestamps,
autoRefresh: true,
refreshInterval: 30
});
}
}
if (type === 'volume' || type === 'all') {
// Get volume data from klines
const klineResult = await agent.executeTool('mcp__behemoth__bitget_klines', {
symbol,
interval: timeframe,
limit: 20
});
if (klineResult?.success && klineResult.data?.length) {
const volumes = klineResult.data.map((candle: any) => parseFloat(candle.volume));
const timestamps = klineResult.data.map((candle: any) =>
new Date(candle.timestamp).toLocaleTimeString()
);
configs.push({
id: `volume_${symbol}`,
title: `${symbol} Volume`,
type: 'volume',
symbol,
data: volumes,
timestamps,
autoRefresh: true,
refreshInterval: 30
});
}
}
// If no real data, generate mock data for demo
if (configs.length === 0) {
configs.push(...this.generateMockChartConfigs(symbol, type));
}
} catch (error) {
console.error('Error generating chart configs:', error);
// Fallback to mock data
configs.push(...this.generateMockChartConfigs(symbol, type));
}
return configs;
}
private generateMockChartConfigs(symbol: string, type: string): ChartConfig[] {
const configs: ChartConfig[] = [];
const timestamps = Array.from({ length: 20 }, (_, i) => {
const time = new Date(Date.now() - (19 - i) * 60000);
return time.toLocaleTimeString();
});
if (type === 'price' || type === 'all') {
const basePrice = 45000 + Math.random() * 10000;
const prices = Array.from({ length: 20 }, (_, i) => {
return basePrice + (Math.random() - 0.5) * 2000 + Math.sin(i * 0.5) * 1000;
});
configs.push({
id: `price_${symbol}`,
title: `${symbol} Price (Demo)`,
type: 'price',
symbol,
data: prices,
timestamps,
autoRefresh: true,
refreshInterval: 30
});
}
if (type === 'rsi' || type === 'all') {
const rsiValues = Array.from({ length: 20 }, (_, i) => {
return 30 + Math.random() * 40 + Math.sin(i * 0.3) * 10;
});
configs.push({
id: `rsi_${symbol}`,
title: `${symbol} RSI (Demo)`,
type: 'rsi',
symbol,
data: rsiValues,
timestamps,
autoRefresh: true,
refreshInterval: 30
});
}
if (type === 'volume' || type === 'all') {
const volumes = Array.from({ length: 20 }, (_, i) => {
return 1000000 + Math.random() * 5000000;
});
configs.push({
id: `volume_${symbol}`,
title: `${symbol} Volume (Demo)`,
type: 'volume',
symbol,
data: volumes,
timestamps,
autoRefresh: true,
refreshInterval: 30
});
}
return configs;
}
private async refreshChartData(chartId: string, symbol: string, agent: Agent): Promise<void> {
try {
// Refresh data based on chart type
if (chartId.includes('price')) {
await agent.executeTool('mcp__behemoth__bitget_klines', { symbol, limit: 20 });
} else if (chartId.includes('rsi')) {
await agent.executeTool('mcp__behemoth__rsi_analysis', { symbol });
} else if (chartId.includes('volume')) {
await agent.executeTool('mcp__behemoth__bitget_klines', { symbol, limit: 20 });
}
} catch (error) {
console.error(`Failed to refresh chart ${chartId}:`, error);
}
}
private async generateStaticChart(
symbol: string,
type: string,
timeframe: string,
agent: Agent
): Promise<string> {
try {
// Generate a simple text-based chart for non-interactive mode
const result = await agent.executeTool('mcp__behemoth__binance_spot_ticker', { symbol });
if (result?.success) {
const price = parseFloat(result.data.price);
const change = parseFloat(result.data.priceChangePercent);
// Simple sparkline
const sparkline = this.generateSparkline(price, change);
return `š ${symbol} ${type.toUpperCase()} Chart (${timeframe})
Current Price: $${price.toLocaleString()}
24h Change: ${change >= 0 ? '+' : ''}${change.toFixed(2)}%
${sparkline}
š” Use --interactive flag for full interactive charts
Example: /chart --symbol=${symbol} --interactive`;
}
} catch (error) {
console.error('Error generating static chart:', error);
}
return `š Unable to generate chart for ${symbol}. Try /chart --dashboard for available symbols.`;
}
private generateSparkline(price: number, change: number): string {
// Generate a simple sparkline based on price and change
const chars = ['ā', 'ā', 'ā', 'ā', 'ā
', 'ā', 'ā', 'ā'];
let line = '';
for (let i = 0; i < 20; i++) {
const variation = Math.sin(i * 0.5) * 0.1 + (change / 100);
const normalized = Math.max(0, Math.min(1, 0.5 + variation));
const charIndex = Math.floor(normalized * (chars.length - 1));
line += chars[charIndex];
}
return line;
}
getSchema() {
return {
type: 'object',
properties: {
symbol: {
type: 'string',
description: 'Trading pair symbol (e.g., BTCUSDT)',
default: 'BTCUSDT'
},
type: {
type: 'string',
enum: ['price', 'rsi', 'volume', 'all'],
description: 'Chart type to display',
default: 'price'
},
timeframe: {
type: 'string',
enum: ['1m', '5m', '15m', '1h', '4h', '1d'],
description: 'Chart timeframe',
default: '1h'
},
interactive: {
type: 'boolean',
description: 'Enable interactive chart mode',
default: true
},
dashboard: {
type: 'boolean',
description: 'Show multi-symbol dashboard',
default: false
}
}
};
}
}