ML Engine Updates: - Updated BTCUSD with Polygon API data (2024-2025): 215,699 new records - Re-trained all ML models: Attention (R²: 0.223), Base, Metamodel (87.3% confidence) - Backtest results: +176.71R profit with aggressive_filter strategy Documentation Consolidation: - Created docs/99-analisis/_MAP.md index with 13 new analysis documents - Consolidated inventories: removed duplicates from orchestration/inventarios/ - Updated ML_INVENTORY.yml with BTCUSD metrics and training results - Added execution reports: FASE11-BTCUSD, correction issues, alignment validation Architecture & Integration: - Updated all module documentation with NEXUS v3.4 frontmatter - Fixed _MAP.md indexes across all folders - Updated orchestration plans and traces Files: 229 changed, 5064 insertions(+), 1872 deletions(-) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
34 KiB
34 KiB
| id | title | type | status | priority | epic | project | version | created_date | updated_date |
|---|---|---|---|---|---|---|---|---|---|
| ET-LLM-005 | Arquitectura del Sistema de Tools | Technical Specification | Done | Alta | OQI-007 | trading-platform | 1.0.0 | 2025-12-05 | 2026-01-04 |
ET-LLM-005: Arquitectura del Sistema de Tools
Épica: OQI-007 - LLM Strategy Agent Versión: 1.0 Fecha: 2025-12-05 Estado: Planificado Prioridad: P0 - Crítico
Resumen
Esta especificación define la arquitectura del sistema de tools (herramientas) que el agente LLM puede invocar para obtener información y ejecutar acciones en la plataforma.
Arquitectura General
┌─────────────────────────────────────────────────────────────────────────┐
│ TOOLS SYSTEM │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Tool Registry │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Market │ │ Portfolio │ │ News │ │ ML │ │ │
│ │ │ Tools │ │ Tools │ │ Tools │ │ Tools │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Trading │ │ Alert │ │ Calculate │ │ Education │ │ │
│ │ │ Tools │ │ Tools │ │ Tools │ │ Tools │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Tool Executor │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │ │
│ │ │ Permission │ │ Rate │ │ Execution │ │ │
│ │ │ Checker │ │ Limiter │ │ Engine │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Service Adapters │ │
│ │ │ │
│ │ MarketData │ Portfolio │ News │ ML │ Trading │ Alerts │ ... │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Tool Registry
Estructura de Registro
// src/modules/copilot/tools/tool-registry.ts
interface ToolDefinition {
id: string;
name: string;
description: string;
category: ToolCategory;
requiredPlan: 'free' | 'pro' | 'premium';
rateLimit: RateLimitConfig;
parameters: ToolParameters;
handler: ToolHandler;
validator?: ToolValidator;
postProcessor?: ToolPostProcessor;
}
type ToolCategory =
| 'market'
| 'portfolio'
| 'news'
| 'ml'
| 'trading'
| 'alerts'
| 'calculate'
| 'education';
interface RateLimitConfig {
requestsPerMinute: number;
requestsPerHour?: number;
requestsPerDay?: number;
}
@Injectable()
export class ToolRegistry {
private tools: Map<string, ToolDefinition> = new Map();
constructor(
private readonly marketTools: MarketToolsProvider,
private readonly portfolioTools: PortfolioToolsProvider,
private readonly newsTools: NewsToolsProvider,
private readonly mlTools: MLToolsProvider,
private readonly tradingTools: TradingToolsProvider,
private readonly alertTools: AlertToolsProvider,
private readonly calculateTools: CalculateToolsProvider,
private readonly educationTools: EducationToolsProvider,
) {
this.registerAllTools();
}
private registerAllTools(): void {
// Register market tools
this.marketTools.getTools().forEach(tool => this.register(tool));
// Register portfolio tools
this.portfolioTools.getTools().forEach(tool => this.register(tool));
// Register all other tools...
this.newsTools.getTools().forEach(tool => this.register(tool));
this.mlTools.getTools().forEach(tool => this.register(tool));
this.tradingTools.getTools().forEach(tool => this.register(tool));
this.alertTools.getTools().forEach(tool => this.register(tool));
this.calculateTools.getTools().forEach(tool => this.register(tool));
this.educationTools.getTools().forEach(tool => this.register(tool));
}
register(tool: ToolDefinition): void {
if (this.tools.has(tool.id)) {
throw new Error(`Tool ${tool.id} already registered`);
}
this.tools.set(tool.id, tool);
}
get(toolId: string): ToolDefinition | undefined {
return this.tools.get(toolId);
}
getByCategory(category: ToolCategory): ToolDefinition[] {
return Array.from(this.tools.values()).filter(t => t.category === category);
}
getAvailableForPlan(plan: string): ToolDefinition[] {
const planHierarchy = { free: 0, pro: 1, premium: 2 };
const userPlanLevel = planHierarchy[plan] || 0;
return Array.from(this.tools.values()).filter(tool => {
const toolPlanLevel = planHierarchy[tool.requiredPlan] || 0;
return toolPlanLevel <= userPlanLevel;
});
}
getOpenAIToolDefinitions(plan: string): OpenAITool[] {
return this.getAvailableForPlan(plan).map(tool => ({
type: 'function',
function: {
name: tool.name,
description: tool.description,
parameters: tool.parameters,
},
}));
}
}
Catálogo Completo de Tools
// src/modules/copilot/tools/catalog.ts
export const TOOL_CATALOG: ToolDefinition[] = [
// ============================================
// MARKET TOOLS
// ============================================
{
id: 'market.get_price',
name: 'get_price',
description: 'Obtiene el precio actual de un símbolo con cambio 24h y volumen',
category: 'market',
requiredPlan: 'free',
rateLimit: { requestsPerMinute: 60 },
parameters: {
type: 'object',
properties: {
symbol: {
type: 'string',
description: 'Símbolo del activo (ej: AAPL, BTC/USD, ETH/USD)',
},
},
required: ['symbol'],
},
handler: 'marketService.getPrice',
},
{
id: 'market.get_ohlcv',
name: 'get_ohlcv',
description: 'Obtiene datos históricos OHLCV (velas) de un símbolo',
category: 'market',
requiredPlan: 'free',
rateLimit: { requestsPerMinute: 30 },
parameters: {
type: 'object',
properties: {
symbol: { type: 'string', description: 'Símbolo del activo' },
timeframe: {
type: 'string',
enum: ['1m', '5m', '15m', '1h', '4h', '1d', '1w'],
description: 'Intervalo temporal de las velas',
},
limit: {
type: 'number',
description: 'Número de velas (máximo 500)',
default: 100,
},
},
required: ['symbol', 'timeframe'],
},
handler: 'marketService.getOHLCV',
},
{
id: 'market.get_indicators',
name: 'get_indicators',
description: 'Calcula indicadores técnicos para un símbolo',
category: 'market',
requiredPlan: 'free',
rateLimit: { requestsPerMinute: 30 },
parameters: {
type: 'object',
properties: {
symbol: { type: 'string' },
indicators: {
type: 'array',
items: {
type: 'string',
enum: ['RSI', 'MACD', 'BB', 'SMA', 'EMA', 'ATR', 'VWAP', 'OBV', 'ADX'],
},
},
timeframe: {
type: 'string',
enum: ['1h', '4h', '1d'],
default: '1d',
},
},
required: ['symbol', 'indicators'],
},
handler: 'marketService.getIndicators',
},
{
id: 'market.get_fundamentals',
name: 'get_fundamentals',
description: 'Obtiene datos fundamentales de una acción (P/E, revenue, etc.)',
category: 'market',
requiredPlan: 'free',
rateLimit: { requestsPerMinute: 20 },
parameters: {
type: 'object',
properties: {
symbol: { type: 'string', description: 'Símbolo de la acción' },
},
required: ['symbol'],
},
handler: 'marketService.getFundamentals',
},
// ============================================
// NEWS TOOLS
// ============================================
{
id: 'news.get_news',
name: 'get_news',
description: 'Obtiene noticias recientes con análisis de sentimiento',
category: 'news',
requiredPlan: 'free',
rateLimit: { requestsPerMinute: 10 },
parameters: {
type: 'object',
properties: {
symbol: { type: 'string', description: 'Símbolo para buscar noticias' },
limit: { type: 'number', default: 5, description: 'Número de noticias' },
},
required: ['symbol'],
},
handler: 'newsService.getBySymbol',
},
{
id: 'news.get_sentiment',
name: 'get_sentiment',
description: 'Obtiene análisis de sentimiento agregado de múltiples fuentes',
category: 'news',
requiredPlan: 'pro',
rateLimit: { requestsPerMinute: 20 },
parameters: {
type: 'object',
properties: {
symbol: { type: 'string' },
},
required: ['symbol'],
},
handler: 'newsService.getSentiment',
},
// ============================================
// ML TOOLS (Pro/Premium)
// ============================================
{
id: 'ml.get_prediction',
name: 'get_ml_signals',
description: 'Obtiene predicciones del modelo ML con nivel de confianza',
category: 'ml',
requiredPlan: 'pro',
rateLimit: { requestsPerMinute: 30 },
parameters: {
type: 'object',
properties: {
symbol: { type: 'string' },
},
required: ['symbol'],
},
handler: 'mlService.getPrediction',
},
{
id: 'ml.get_features',
name: 'get_ml_features',
description: 'Obtiene las features principales usadas en la predicción',
category: 'ml',
requiredPlan: 'premium',
rateLimit: { requestsPerMinute: 20 },
parameters: {
type: 'object',
properties: {
symbol: { type: 'string' },
prediction_id: { type: 'string', description: 'ID de predicción previa' },
},
required: ['symbol'],
},
handler: 'mlService.getFeatureImportance',
},
// ============================================
// PORTFOLIO TOOLS (Pro/Premium)
// ============================================
{
id: 'portfolio.get_positions',
name: 'get_portfolio',
description: 'Obtiene las posiciones actuales del usuario',
category: 'portfolio',
requiredPlan: 'pro',
rateLimit: { requestsPerMinute: 30 },
parameters: {
type: 'object',
properties: {},
},
handler: 'portfolioService.getPositions',
},
{
id: 'portfolio.get_history',
name: 'get_trade_history',
description: 'Obtiene el historial de trades del usuario',
category: 'portfolio',
requiredPlan: 'pro',
rateLimit: { requestsPerMinute: 20 },
parameters: {
type: 'object',
properties: {
limit: { type: 'number', default: 20 },
symbol: { type: 'string', description: 'Filtrar por símbolo (opcional)' },
},
},
handler: 'portfolioService.getHistory',
},
// ============================================
// TRADING TOOLS (Pro/Premium)
// ============================================
{
id: 'trading.create_paper_order',
name: 'create_paper_order',
description: 'Crea una orden de paper trading (requiere confirmación)',
category: 'trading',
requiredPlan: 'pro',
rateLimit: { requestsPerMinute: 10 },
parameters: {
type: 'object',
properties: {
symbol: { type: 'string' },
side: { type: 'string', enum: ['buy', 'sell'] },
quantity: { type: 'number' },
order_type: {
type: 'string',
enum: ['market', 'limit', 'stop', 'stop_limit'],
},
limit_price: { type: 'number', description: 'Precio límite (para limit orders)' },
stop_price: { type: 'number', description: 'Precio stop (para stop orders)' },
},
required: ['symbol', 'side', 'quantity', 'order_type'],
},
handler: 'tradingService.createPaperOrder',
validator: 'tradingValidator.validateOrder',
postProcessor: 'tradingService.requireConfirmation',
},
{
id: 'trading.cancel_paper_order',
name: 'cancel_paper_order',
description: 'Cancela una orden de paper trading pendiente',
category: 'trading',
requiredPlan: 'pro',
rateLimit: { requestsPerMinute: 20 },
parameters: {
type: 'object',
properties: {
order_id: { type: 'string' },
},
required: ['order_id'],
},
handler: 'tradingService.cancelPaperOrder',
},
{
id: 'trading.get_pending_orders',
name: 'get_pending_orders',
description: 'Obtiene las órdenes pendientes del usuario',
category: 'trading',
requiredPlan: 'pro',
rateLimit: { requestsPerMinute: 30 },
parameters: {
type: 'object',
properties: {},
},
handler: 'tradingService.getPendingOrders',
},
// ============================================
// ALERT TOOLS
// ============================================
{
id: 'alert.create',
name: 'create_alert',
description: 'Crea una alerta de precio',
category: 'alerts',
requiredPlan: 'pro',
rateLimit: { requestsPerMinute: 20 },
parameters: {
type: 'object',
properties: {
symbol: { type: 'string' },
condition: { type: 'string', enum: ['>=', '<=', '=='] },
price: { type: 'number' },
message: { type: 'string', description: 'Mensaje opcional' },
},
required: ['symbol', 'condition', 'price'],
},
handler: 'alertService.create',
},
{
id: 'alert.list',
name: 'list_alerts',
description: 'Lista las alertas activas del usuario',
category: 'alerts',
requiredPlan: 'pro',
rateLimit: { requestsPerMinute: 30 },
parameters: {
type: 'object',
properties: {
symbol: { type: 'string', description: 'Filtrar por símbolo (opcional)' },
},
},
handler: 'alertService.list',
},
{
id: 'alert.delete',
name: 'delete_alert',
description: 'Elimina una alerta',
category: 'alerts',
requiredPlan: 'pro',
rateLimit: { requestsPerMinute: 20 },
parameters: {
type: 'object',
properties: {
alert_id: { type: 'string' },
},
required: ['alert_id'],
},
handler: 'alertService.delete',
},
// ============================================
// CALCULATE TOOLS
// ============================================
{
id: 'calculate.position_size',
name: 'calculate_position_size',
description: 'Calcula el tamaño de posición basado en riesgo',
category: 'calculate',
requiredPlan: 'free',
rateLimit: { requestsPerMinute: 100 },
parameters: {
type: 'object',
properties: {
capital: { type: 'number', description: 'Capital disponible' },
entry_price: { type: 'number' },
stop_loss_price: { type: 'number' },
risk_percent: { type: 'number', description: 'Porcentaje de riesgo (ej: 0.02 para 2%)' },
},
required: ['capital', 'entry_price', 'stop_loss_price', 'risk_percent'],
},
handler: 'calculateService.positionSize',
},
{
id: 'calculate.risk_reward',
name: 'calculate_risk_reward',
description: 'Calcula el ratio riesgo/beneficio de una operación',
category: 'calculate',
requiredPlan: 'free',
rateLimit: { requestsPerMinute: 100 },
parameters: {
type: 'object',
properties: {
entry_price: { type: 'number' },
stop_loss_price: { type: 'number' },
take_profit_price: { type: 'number' },
},
required: ['entry_price', 'stop_loss_price', 'take_profit_price'],
},
handler: 'calculateService.riskReward',
},
// ============================================
// WATCHLIST TOOLS
// ============================================
{
id: 'watchlist.get',
name: 'get_watchlist',
description: 'Obtiene la watchlist del usuario',
category: 'market',
requiredPlan: 'free',
rateLimit: { requestsPerMinute: 60 },
parameters: {
type: 'object',
properties: {},
},
handler: 'watchlistService.get',
},
{
id: 'watchlist.add',
name: 'add_to_watchlist',
description: 'Agrega un símbolo a la watchlist',
category: 'market',
requiredPlan: 'free',
rateLimit: { requestsPerMinute: 30 },
parameters: {
type: 'object',
properties: {
symbol: { type: 'string' },
},
required: ['symbol'],
},
handler: 'watchlistService.add',
},
{
id: 'watchlist.remove',
name: 'remove_from_watchlist',
description: 'Elimina un símbolo de la watchlist',
category: 'market',
requiredPlan: 'free',
rateLimit: { requestsPerMinute: 30 },
parameters: {
type: 'object',
properties: {
symbol: { type: 'string' },
},
required: ['symbol'],
},
handler: 'watchlistService.remove',
},
];
Tool Executor
// src/modules/copilot/tools/tool-executor.ts
interface ToolExecutionContext {
userId: string;
userPlan: string;
conversationId: string;
toolCallId: string;
}
interface ToolExecutionResult {
success: boolean;
data?: any;
error?: {
code: string;
message: string;
};
metadata: {
executionTime: number;
cached: boolean;
};
}
@Injectable()
export class ToolExecutor {
constructor(
private readonly registry: ToolRegistry,
private readonly permissionChecker: PermissionChecker,
private readonly rateLimiter: RateLimiter,
private readonly cache: CacheService,
private readonly logger: LoggerService,
// Service adapters
private readonly marketService: MarketService,
private readonly portfolioService: PortfolioService,
private readonly newsService: NewsService,
private readonly mlService: MLService,
private readonly tradingService: TradingService,
private readonly alertService: AlertService,
private readonly calculateService: CalculateService,
private readonly watchlistService: WatchlistService,
) {}
async execute(
toolName: string,
parameters: Record<string, any>,
context: ToolExecutionContext,
): Promise<ToolExecutionResult> {
const startTime = Date.now();
// 1. Get tool definition
const tool = this.registry.get(toolName);
if (!tool) {
return {
success: false,
error: { code: 'TOOL_NOT_FOUND', message: `Tool ${toolName} not found` },
metadata: { executionTime: 0, cached: false },
};
}
// 2. Check permissions
const hasPermission = await this.permissionChecker.check(
context.userId,
context.userPlan,
tool.requiredPlan,
);
if (!hasPermission) {
return {
success: false,
error: {
code: 'PLAN_REQUIRED',
message: `Tool ${toolName} requires ${tool.requiredPlan} plan`,
},
metadata: { executionTime: Date.now() - startTime, cached: false },
};
}
// 3. Check rate limit
const rateLimitOk = await this.rateLimiter.check(
context.userId,
tool.id,
tool.rateLimit,
);
if (!rateLimitOk) {
return {
success: false,
error: {
code: 'RATE_LIMIT',
message: `Rate limit exceeded for ${toolName}`,
},
metadata: { executionTime: Date.now() - startTime, cached: false },
};
}
// 4. Validate parameters
if (tool.validator) {
const validationResult = await this.validateParameters(
tool.validator,
parameters,
context,
);
if (!validationResult.valid) {
return {
success: false,
error: {
code: 'VALIDATION_ERROR',
message: validationResult.message,
},
metadata: { executionTime: Date.now() - startTime, cached: false },
};
}
}
// 5. Check cache (for read-only tools)
if (this.isCacheable(tool)) {
const cacheKey = this.buildCacheKey(tool.id, parameters, context.userId);
const cached = await this.cache.get(cacheKey);
if (cached) {
return {
success: true,
data: cached,
metadata: { executionTime: Date.now() - startTime, cached: true },
};
}
}
// 6. Execute tool
try {
const result = await this.executeHandler(tool, parameters, context);
// 7. Post-process if needed
let finalResult = result;
if (tool.postProcessor) {
finalResult = await this.postProcess(tool.postProcessor, result, context);
}
// 8. Cache result if applicable
if (this.isCacheable(tool)) {
const cacheKey = this.buildCacheKey(tool.id, parameters, context.userId);
await this.cache.set(cacheKey, finalResult, this.getCacheTTL(tool));
}
// 9. Log execution
await this.logger.logToolExecution({
userId: context.userId,
toolId: tool.id,
parameters,
success: true,
executionTime: Date.now() - startTime,
});
return {
success: true,
data: finalResult,
metadata: { executionTime: Date.now() - startTime, cached: false },
};
} catch (error) {
await this.logger.logToolExecution({
userId: context.userId,
toolId: tool.id,
parameters,
success: false,
error: error.message,
executionTime: Date.now() - startTime,
});
return {
success: false,
error: {
code: 'EXECUTION_ERROR',
message: error.message,
},
metadata: { executionTime: Date.now() - startTime, cached: false },
};
}
}
private async executeHandler(
tool: ToolDefinition,
parameters: Record<string, any>,
context: ToolExecutionContext,
): Promise<any> {
const [serviceName, methodName] = tool.handler.split('.');
const service = this.getService(serviceName);
if (!service || !service[methodName]) {
throw new Error(`Handler ${tool.handler} not found`);
}
// Add context to parameters for handlers that need it
const enrichedParams = {
...parameters,
_context: {
userId: context.userId,
userPlan: context.userPlan,
},
};
return service[methodName](enrichedParams);
}
private getService(serviceName: string): any {
const services: Record<string, any> = {
marketService: this.marketService,
portfolioService: this.portfolioService,
newsService: this.newsService,
mlService: this.mlService,
tradingService: this.tradingService,
alertService: this.alertService,
calculateService: this.calculateService,
watchlistService: this.watchlistService,
};
return services[serviceName];
}
private isCacheable(tool: ToolDefinition): boolean {
const nonCacheableCategories = ['trading', 'alerts'];
return !nonCacheableCategories.includes(tool.category);
}
private getCacheTTL(tool: ToolDefinition): number {
const ttls: Record<string, number> = {
market: 5, // 5 seconds
news: 300, // 5 minutes
ml: 60, // 1 minute
portfolio: 10, // 10 seconds
calculate: 0, // No cache (pure function)
};
return ttls[tool.category] || 30;
}
private buildCacheKey(
toolId: string,
params: Record<string, any>,
userId: string,
): string {
const paramsHash = JSON.stringify(params);
return `tool:${toolId}:${userId}:${paramsHash}`;
}
}
Rate Limiter
// src/modules/copilot/tools/rate-limiter.ts
@Injectable()
export class RateLimiter {
constructor(private readonly redis: RedisService) {}
async check(
userId: string,
toolId: string,
config: RateLimitConfig,
): Promise<boolean> {
const now = Date.now();
const minute = Math.floor(now / 60000);
const hour = Math.floor(now / 3600000);
const day = Math.floor(now / 86400000);
// Check per-minute limit
if (config.requestsPerMinute) {
const key = `ratelimit:${toolId}:${userId}:min:${minute}`;
const count = await this.redis.incr(key);
if (count === 1) {
await this.redis.expire(key, 60);
}
if (count > config.requestsPerMinute) {
return false;
}
}
// Check per-hour limit
if (config.requestsPerHour) {
const key = `ratelimit:${toolId}:${userId}:hour:${hour}`;
const count = await this.redis.incr(key);
if (count === 1) {
await this.redis.expire(key, 3600);
}
if (count > config.requestsPerHour) {
return false;
}
}
// Check per-day limit
if (config.requestsPerDay) {
const key = `ratelimit:${toolId}:${userId}:day:${day}`;
const count = await this.redis.incr(key);
if (count === 1) {
await this.redis.expire(key, 86400);
}
if (count > config.requestsPerDay) {
return false;
}
}
return true;
}
async getRemainingQuota(
userId: string,
toolId: string,
config: RateLimitConfig,
): Promise<RemainingQuota> {
const now = Date.now();
const minute = Math.floor(now / 60000);
const key = `ratelimit:${toolId}:${userId}:min:${minute}`;
const used = parseInt(await this.redis.get(key) || '0', 10);
return {
remaining: Math.max(0, config.requestsPerMinute - used),
resetAt: (minute + 1) * 60000,
};
}
}
Permission Checker
// src/modules/copilot/tools/permission-checker.ts
@Injectable()
export class PermissionChecker {
private readonly planHierarchy = {
free: 0,
pro: 1,
premium: 2,
};
async check(
userId: string,
userPlan: string,
requiredPlan: string,
): Promise<boolean> {
const userLevel = this.planHierarchy[userPlan] ?? 0;
const requiredLevel = this.planHierarchy[requiredPlan] ?? 0;
return userLevel >= requiredLevel;
}
getUpgradeMessage(currentPlan: string, requiredPlan: string): string {
if (currentPlan === 'free' && requiredPlan === 'pro') {
return 'Esta función requiere el plan Pro. Actualiza para acceder a señales ML, paper trading y más.';
}
if (requiredPlan === 'premium') {
return 'Esta función requiere el plan Premium. Actualiza para acceso completo a todas las funcionalidades.';
}
return `Esta función requiere el plan ${requiredPlan}.`;
}
}
Diagrama de Flujo de Ejecución
┌─────────────────────────────────────────────────────────────┐
│ LLM genera tool_call: get_ml_signals({ symbol: "AAPL" }) │
└──────────────────────────┬──────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 1. Tool Registry: Buscar definición de "get_ml_signals" │
│ → Encontrado: ml.get_prediction │
└──────────────────────────┬──────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 2. Permission Checker: ¿Usuario tiene plan Pro+? │
│ → User plan: "pro" >= required: "pro" ✓ │
└──────────────────────────┬──────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 3. Rate Limiter: ¿Dentro del límite (30/min)? │
│ → Requests this minute: 5 < 30 ✓ │
└──────────────────────────┬──────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 4. Cache Check: ¿Resultado cacheado? │
│ → Cache miss (TTL expirado) │
└──────────────────────────┬──────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 5. Execute Handler: mlService.getPrediction({ symbol }) │
│ → Llamada al ML Engine │
│ → Response: { prediction: "bullish", confidence: 0.72 } │
└──────────────────────────┬──────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 6. Cache Store: Guardar resultado (TTL: 60s) │
└──────────────────────────┬──────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 7. Log Execution: Registrar uso de tool │
└──────────────────────────┬──────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 8. Return to LLM: { success: true, data: {...} } │
└─────────────────────────────────────────────────────────────┘
Dependencias
Servicios Internos
- MarketService (OQI-003)
- PortfolioService (OQI-004)
- MLService (OQI-006)
- AlertService (OQI-003)
- TradingService (OQI-003)
Infraestructura
- Redis (rate limiting, cache)
- PostgreSQL (logs de ejecución)
Referencias
Especificación técnica - Sistema NEXUS Trading Platform